No OneTemporary

File Metadata

Created
Mon, May 6, 3:46 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/.gitignore b/.gitignore
index bd300c1e51..ef81a9e1ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,27 @@
+__pycache__
*.trace
build
qtcreator-build
*.kdev4
*~
.kateconfig
CMakeLists.txt.user*
.directory
*.autosave
*.swp
.gdb_history
.kdev_include_paths
*.config
*.creator
*.creator.user
*.files
*.includes
.DS_Store
*.kate-swap
.idea
GTAGS
GPATH
GRTAGS
GSYMS
BROWSE
*.kate-swp
diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt
index bbed1e91c0..9413fee920 100644
--- a/3rdparty/CMakeLists.txt
+++ b/3rdparty/CMakeLists.txt
@@ -1,126 +1,129 @@
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 (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 (DEFINED EP_PREFIX)
set_directory_properties(PROPERTIES EP_PREFIX ${EP_PREFIX})
endif (DEFINED EP_PREFIX)
if (MSVC)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_EXE_LINKER_FLAGS=/PROFILE -DCMAKE_SHARED_LINKER_FLAGS=/PROFILE)
set(PATCH_COMMAND myptch)
endif()
if (MINGW)
set(PATCH_COMMAND myptch)
endif()
if (MSYS)
set(PATCH_COMMAND patch)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE}
-DCMAKE_TOOLCHAIN_FILE=${MXE_TOOLCHAIN}
-DCMAKE_FIND_PREFIX_PATH=${CMAKE_PREFIX_PATH}
-DCMAKE_SYSTEM_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include
-DCMAKE_INCLUDE_PATH=${CMAKE_PREFIX_PATH}/include
-DCMAKE_LIBRARY_PATH=${CMAKE_PREFIX_PATH}/lib
-DZLIB_ROOT=${CMAKE_PREFIX_PATH}
)
set(GLOBAL_AUTOMAKE_PROFILE --host=i686-pc-mingw32 )
endif()
if (APPLE)
set(GLOBAL_PROFILE ${GLOBAL_PROFILE} -DCMAKE_MACOSX_RPATH=ON -DKDE_SKIP_RPATH_SETTINGS=ON -DBUILD_WITH_INSTALL_RPATH=ON -DAPPLE_SUPPRESS_X11_WARNING=ON)
set(PATCH_COMMAND patch)
endif ()
if (UNIX AND NOT APPLE)
set(LINUX true)
set(PATCH_COMMAND patch)
endif ()
# this list must be dependency-ordered
+add_subdirectory( ext_python )
if (MSVC)
add_subdirectory( ext_patch )
add_subdirectory( ext_png2ico )
endif (MSVC)
if (MINGW)
add_subdirectory( ext_patch )
add_subdirectory( ext_png2ico )
endif (MINGW)
add_subdirectory( ext_iconv )
add_subdirectory( ext_gettext )
add_subdirectory( ext_zlib )
add_subdirectory( ext_libxml2 )
add_subdirectory( ext_libxslt )
add_subdirectory( ext_boost )
add_subdirectory( ext_jpeg )
add_subdirectory( ext_tiff )
add_subdirectory( ext_png )
add_subdirectory( ext_eigen3 )
add_subdirectory( ext_expat ) # for exiv2
add_subdirectory( ext_exiv2 )
add_subdirectory( ext_ilmbase )
add_subdirectory( ext_lcms2 )
add_subdirectory( ext_openexr )
add_subdirectory( ext_vc )
add_subdirectory( ext_gsl )
add_subdirectory( ext_fftw3 )
add_subdirectory( ext_ocio )
if (MSVC)
add_subdirectory( ext_pthreads )
endif (MSVC)
add_subdirectory( ext_fontconfig)
add_subdirectory( ext_freetype)
add_subdirectory( ext_qt )
add_subdirectory( ext_poppler )
add_subdirectory( ext_libraw )
add_subdirectory( ext_frameworks )
+add_subdirectory( ext_sip )
+add_subdirectory( ext_pyqt )
if (MSVC OR MINGW)
add_subdirectory( ext_drmingw )
endif (MSVC OR MINGW)
diff --git a/3rdparty/README.md b/3rdparty/README.md
index c196568a8c..be2ce3f3cc 100644
--- a/3rdparty/README.md
+++ b/3rdparty/README.md
@@ -1,241 +1,255 @@
= 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 Emerge
* you develop on OSX and aren't using Homebrew
* you want to build a generic, distro-agnostic version of Krita for Linux
* you develop on Linux, but some dependencies aren't available for your distribution
and you know what you're doing, you can use the following guide to build
the dependencies that Krita needs.
If you develop on Linux and your distribution has the 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: https://cmake.org/download/. Make sure cmake is in your path.
3. Make sure you have a compiler:
* Linux: gcc, minimum version 4.8
* OSX: clang, you need to install xcode for this
* Windows: mingw-w64 5.4 (by mingw-builds)
- 32-bit (x86) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/5.4.0/threads-posix/dwarf/
- 64-bit (x64) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/5.4.0/threads-posix/seh/
+
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. If you compile Qt on Windows, you will also need Python: https://www.python.org. Make sure to have python.exe in your path.
+
+4. On Windows, you will also need Python 3.6: https://www.python.org. Make sure to have python.exe in your path. This version of python will be used for two things: to configure Qt and to build the python scripting module. Make sure the version you download is exactly python-3.6.1. Make sure Python is in your path.
== Setup your environment ==
== Prepare your directory layout ==
1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace BUILDROOT with $BUILDROOT or %BUILDROOT whenever you copy and paste a command, depending on your operating system.
2. Checkout krita in BUILDROOT
cd BUILDROOT
git clone git://anongit.kde.org/krita.git
3. Create the build directory
mkdir BUILDROOT/b
4. Create the downloads directory
mkdir BUILDROOT/d
5. Create the install directory
mkdir BUILDROOT/i
== Prepare the externals build ==
1. enter the BUILDROOT/b directory
2. 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"
3. run cmake:
* Linux:
export PATH=$BUILDROOT/i/bin
+ 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
+ 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 bits:
TODO
* Windows 64 bits:
+Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d.
+
+ set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH%
+ set PYTHONHOME=%BUILDROOT%/i (only if you want to build your own python)
+ set PATH=BUILDROOT\i\bin\;BUILDROOT\i\lib;%PATH%
+ cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles"
4. build the packages:
With a judicious application of DEPENDS statements, it's possible to build it all in one go, but in my experience that fails always, so it's better to build the dependencies independently.
+If you want to use the included version of Python (can be used on Windows to build Qt instead of installing Python separately):
+
+ cmake --build . --config RelWithDebInfo --target ext_python
+
On Windows:
cmake --build . --config RelWithDebInfo --target ext_patch
cmake --build . --config RelWithDebInfo --target ext_png2ico
cmake --build . --config RelWithDebInfo --target ext_gettext
On all operating systems:
cmake --build . --config RelWithDebInfo --target ext_qt
cmake --build . --config RelWithDebInfo --target ext_zlib
cmake --build . --config RelWithDebInfo --target ext_boost
Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost
cmake --build . --config RelWithDebInfo --target ext_eigen3
cmake --build . --config RelWithDebInfo --target ext_exiv2
cmake --build . --config RelWithDebInfo --target ext_fftw3
On Windows:
set FFTW_LIB_DIR=%BUILDROOT%\i\lib
dlltool.exe -k --output-lib %FFTW_LIB_DIR%\libfftw3-3.a --input-def %FFTW_LIB_DIR%\libfftw3-3.def
dlltool.exe -k --output-lib %FFTW_LIB_DIR%\libfftw3f-3.a --input-def %FFTW_LIB_DIR%\libfftw3f-3.def
dlltool.exe -k --output-lib %FFTW_LIB_DIR%\libfftw3l-3.a --input-def %FFTW_LIB_DIR%\libfftw3l-3.def
On all operating systems
cmake --build . --config RelWithDebInfo --target ext_ilmbase
cmake --build . --config RelWithDebInfo --target ext_jpeg
cmake --build . --config RelWithDebInfo --target ext_lcms2
cmake --build . --config RelWithDebInfo --target ext_ocio
cmake --build . --config RelWithDebInfo --target ext_openexr
Note for OSX:
On OSX, you need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again.
install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable
install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups
On All operating systems:
cmake --build . --config RelWithDebInfo --target ext_png
cmake --build . --config RelWithDebInfo --target ext_tiff
cmake --build . --config RelWithDebInfo --target ext_gsl
cmake --build . --config RelWithDebInfo --target ext_vc
cmake --build . --config RelWithDebInfo --target ext_libraw
On Windows
cmake --build . --config RelWithDebInfo --target ext_freetype
cmake --build . --config RelWithDebInfo --target ext_poppler
On Linux
cmake --build . --config RelWithDebInfo --target ext_kcrash
Everywhere else:
cmake --build . --config RelWithDebInfo --target ext_kwindowsystem
On Windows, if you want to include DrMingw for dumping backtrace on crash:
cmake --build . --config RelWithDebInfo --target ext_drmingw
Note: poppler should be buildable on Linux as well with a home-built freetype
and fontconfig, but I don't know how to make fontconfig find freetype, and on
Linux, fontconfig is needed for poppler. Poppler is needed for PDF import.
Note 2: libcurl still isn't available.
Note 3: if you want to build a release, you need to get the binary gettext
archives from files.kde.org/krita/build/dependencies:
http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-32.zip
http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-64.zip
Take care, these zips contain a libstdc++-6.dll that you don't want in your path when building.
== Build Krita ==
1. Make a krita build directory:
mkdir BUILDROOT/build
2. Enter the BUILDROOT/build
3. Run
On Windows
Depending on what you want to use, run this command for MSBuild:
cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1
Or this to use jom (faster compiling, uses all cores, ships with QtCreator/pre-built Qt binaries):
cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1
On Linux
cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfobg
On OSX
cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo
4. Run
On Linux and OSX
make
make install
On Windows
Either use MSBuild to build (-- /m tells msbuild to use all your cores):
cmake --build . --config RelWithDebInfo --target INSTALL -- /m
Or use jom which should be in a path similar to C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe.
So, from the same folder, instead of running cmake run:
"C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe" install
6. Run krita:
On Linux
BUILDROOT/i/bin/krita
On Windows
BUILDROOT\i\bin\krita.exe
On OSX
BUILDROOT/i/bin/krita.app/Contents/MacOS/krita
== Packaging a Windows Build ==
If you want to create a stripped down version of Krita to distribute, after building everything just copy the makepkg.bat file from the "windows" folder inside krita root source folder to BUILDROOT and run it.
That will copy the necessary files into the specified folder and leave behind developer related files, so the resulting folder will be a smaller install folder.
== Common Issues ==
- On Windows, if you get a 'mspdb140.dll' missing alert window, it means you did not run the bat file. Make sure to include the quotes in the command:
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\amd64\vcvars64.bat"
- On Windows, if you get an error about Qt5Core.dll missing/not found or nmake exit with an error that mention QT_PLUGIN_PATH, you have to copy a couple of dlls in the Qt build directory, look for the N.B. in the Qt instructions at the start of the Readme.
- If you receive an error while compiling about "missing QtCore5.cmake", or something similar, check to make sure qmake is in your PATH. Restart your command line after any changes are made.
diff --git a/3rdparty/ext_pyqt/CMakeLists.txt b/3rdparty/ext_pyqt/CMakeLists.txt
new file mode 100644
index 0000000000..6b15924903
--- /dev/null
+++ b/3rdparty/ext_pyqt/CMakeLists.txt
@@ -0,0 +1,34 @@
+SET(PREFIX_ext_pyqt "${EXTPREFIX}" )
+if (UNIX)
+ ExternalProject_Add( ext_pyqt
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/PyQt5_gpl-5.6.tar.gz
+ URL_MD5 dbfc885c0548e024ba5260c4f44e0481
+
+ CONFIGURE_COMMAND ${PREFIX_ext_pyqt}/bin/python3 <SOURCE_DIR>/configure.py --confirm-license
+ BUILD_COMMAND make
+ INSTALL_COMMAND make install
+
+ BUILD_IN_SOURCE 1
+
+ UPDATE_COMMAND ""
+ ALWAYS 0
+ )
+elseif(MINGW)
+ ExternalProject_Add( ext_pyqt
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/PyQt5_gpl-5.6.zip
+ URL_MD5 916e79bacda1799f8db7bc034043e450
+
+ CONFIGURE_COMMAND python.exe <SOURCE_DIR>/configure.py --confirm-license --target-py-version 3.6 --bindir {PREFIX_ext_pyqt}/bin --qt ${PREFIX_ext_pyqt} --sip ${PREFIX_ext_pyqt}/bin/sip.exe --sip-incdir ${PREFIX_ext_pyqt}/include --target-py-version 3.6 --spec win32-g++ --verbose --sipdir ${PREFIX_ext_pyqt}/share/sip --destdir ${PREFIX_ext_pyqt}/bin --no-qml-plugin --no-python-dbus --no-qsci-api --no-tools
+ BUILD_COMMAND mingw32-make
+ INSTALL_COMMAND mingw32-make install
+
+ BUILD_IN_SOURCE 1
+
+ UPDATE_COMMAND ""
+ ALWAYS 0
+ )
+
+endif()
+
diff --git a/3rdparty/ext_python/CMakeLists.txt b/3rdparty/ext_python/CMakeLists.txt
new file mode 100644
index 0000000000..708924e323
--- /dev/null
+++ b/3rdparty/ext_python/CMakeLists.txt
@@ -0,0 +1,46 @@
+SET(PREFIX_ext_python "${EXTPREFIX}" )
+if (UNIX)
+ ExternalProject_Add( ext_python
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/Python-3.5.2.tar.gz
+ URL_MD5 ea334d398990037a4b8be324bd475c83
+
+ CONFIGURE_COMMAND <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
+ )
+elseif(MINGW)
+ if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+ ExternalProject_Add( ext_python
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/python-3.6.1-embed-amd64.zip
+ URL_MD5 708496ebbe9a730d19d5d288afd216f1
+
+ 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}/bin
+ UPDATE_COMMAND ""
+ )
+ else("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+
+ ExternalProject_Add( ext_python
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/python-3.6.1-embed-win32.zip
+ URL_MD5 8dff09a1b19b7a7dcb915765328484cf
+
+ 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}/bin
+
+
+ UPDATE_COMMAND ""
+ )
+ endif("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+
+endif()
diff --git a/3rdparty/ext_sip/CMakeLists.txt b/3rdparty/ext_sip/CMakeLists.txt
new file mode 100644
index 0000000000..51dc55274f
--- /dev/null
+++ b/3rdparty/ext_sip/CMakeLists.txt
@@ -0,0 +1,33 @@
+SET(PREFIX_ext_sip "${EXTPREFIX}" )
+if (UNIX)
+ ExternalProject_Add( ext_sip
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/sip-4.18.tar.gz
+ URL_MD5 78724bf2a79878201c3bc81a1d8248ea
+
+ CONFIGURE_COMMAND ${PREFIX_ext_sip}/bin/python3 <SOURCE_DIR>/configure.py -b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/sip -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/sip --target-py-version 3.5
+ BUILD_COMMAND make
+ INSTALL_COMMAND make install
+
+ BUILD_IN_SOURCE 1
+
+ UPDATE_COMMAND ""
+ ALWAYS 0
+ )
+elseif (MINGW)
+ ExternalProject_Add( ext_sip
+ DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR}
+ URL http://files.kde.org/krita/build/dependencies/sip-4.18.zip
+ URL_MD5 e860d06782962fa02f81aeecba3d82a7
+
+ CONFIGURE_COMMAND python.exe <SOURCE_DIR>/configure.py --platform win32-g++ -b ${PREFIX_ext_sip}/bin -d ${PREFIX_ext_sip}/sip -e ${PREFIX_ext_sip}/include --sipdir ${PREFIX_ext_sip}/sip --target-py-version 3.6
+ BUILD_COMMAND mingw32-make
+ INSTALL_COMMAND mingw32-make install
+
+ BUILD_IN_SOURCE 1
+
+ UPDATE_COMMAND ""
+ ALWAYS 0
+ )
+endif()
+
diff --git a/3rdparty/ext_sip/README.md b/3rdparty/ext_sip/README.md
new file mode 100644
index 0000000000..1a71ab9ff8
--- /dev/null
+++ b/3rdparty/ext_sip/README.md
@@ -0,0 +1,7 @@
+Notes on SIP
+
+Building
+
+export BUILDROOT=$HOME/dev
+python3 configure.py -b $BUILDROOT/deps/bin -d $BUILDROOT/deps/sip -e $BUILDROOT/deps/include --sipdir $BUILDROOT/deps/sip --target-py-version 3.4
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3dd8e9e006..b77d01c676 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,650 +1,659 @@
project(krita)
message(STATUS "Using CMake version: ${CMAKE_VERSION}")
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
set(MIN_QT_VERSION 5.6.0)
option(OVERRIDE_QT_VERSION "Use this to make it possible to build with Qt < 5.6.0. There will be bugs." OFF)
if (OVERRIDE_QT_VERSION)
set(MIN_QT_VERSION 5.4.0)
endif()
set(MIN_FRAMEWORKS_VERSION 5.7.0)
if (POLICY CMP0002)
cmake_policy(SET CMP0002 OLD)
endif()
if (POLICY CMP0017)
cmake_policy(SET CMP0017 NEW)
endif ()
if (POLICY CMP0022)
cmake_policy(SET CMP0022 OLD)
endif ()
if (POLICY CMP0026)
cmake_policy(SET CMP0026 OLD)
endif()
if (POLICY CMP0042)
cmake_policy(SET CMP0042 NEW)
endif()
if (POLICY CMP0046)
cmake_policy(SET CMP0046 OLD)
endif ()
if (POLICY CMP0059)
cmake_policy(SET CMP0059 OLD)
endif()
if (POLICY CMP0063)
cmake_policy(SET CMP0063 OLD)
endif()
if (POLICY CMP0054)
cmake_policy(SET CMP0054 OLD)
endif()
if (POLICY CMP0064)
cmake_policy(SET CMP0064 OLD)
endif()
if (APPLE)
set(APPLE_SUPPRESS_X11_WARNING TRUE)
set(KDE_SKIP_RPATH_SETTINGS TRUE)
set(CMAKE_MACOSX_RPATH 1)
set(BUILD_WITH_INSTALL_RPATH 1)
add_definitions(-mmacosx-version-min=10.9 -Wno-macro-redefined -Wno-deprecated-register)
endif()
######################
#######################
## Constants defines ##
#######################
######################
# define common versions of Krita applications, used to generate kritaversion.h
# update these version for every release:
-set(KRITA_VERSION_STRING "3.1.88")
-set(KRITA_STABLE_VERSION_MAJOR 3) # 3 for 3.x, 4 for 4.x, etc.
-set(KRITA_STABLE_VERSION_MINOR 1) # 0 for 3.0, 1 for 3.1, etc.
-set(KRITA_VERSION_RELEASE 88) # 88 for pre-alpha, 89 for Alpha, increase for next test releases, set 0 for first Stable, etc.
+set(KRITA_VERSION_STRING "4.0.0-pre-alpha")
+set(KRITA_STABLE_VERSION_MAJOR 4) # 3 for 3.x, 4 for 4.x, etc.
+set(KRITA_STABLE_VERSION_MINOR 0) # 0 for 3.0, 1 for 3.1, etc.
+set(KRITA_VERSION_RELEASE 0) # 88 for pre-alpha, 89 for Alpha, increase for next test releases, set 0 for first Stable, etc.
set(KRITA_ALPHA 1) # uncomment only for Alpha
#set(KRITA_BETA 1) # uncomment only for Beta
#set(KRITA_RC 1) # uncomment only for RC
set(KRITA_YEAR 2017) # update every year
if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC)
set(KRITA_STABLE 1) # do not edit
endif()
message(STATUS "Krita version: ${KRITA_VERSION_STRING}")
# Define the generic version of the Krita libraries here
# This makes it easy to advance it when the next Krita release comes.
# 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series
-# (2.x) so we're starting with 15 in 3.x series.
-if(KRITA_STABLE_VERSION_MAJOR EQUAL 3)
- math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 15")
+# (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 "15"
- message(FATAL_ERROR "Reminder: please update offset == 15 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger")
+ # let's make sure we won't forget to update the "16"
+ message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger")
endif()
set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0")
set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}")
LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro")
# fetch git revision for the current build
set(KRITA_GIT_SHA1_STRING "")
set(KRITA_GIT_BRANCH_STRING "")
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
get_git_branch(GIT_BRANCH)
if(GIT_SHA1 AND GIT_BRANCH)
string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1)
set(KRITA_GIT_SHA1_STRING ${GIT_SHA1})
set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH})
endif()
if(NOT DEFINED RELEASE_BUILD)
# estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline
string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER)
set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel")
list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX)
if (INDEX EQUAL -1)
set(RELEASE_BUILD FALSE)
else()
set(RELEASE_BUILD TRUE)
endif()
endif()
message(STATUS "Release build: ${RELEASE_BUILD}")
# create test make targets
enable_testing()
# collect list of broken tests, empty here to start fresh with each cmake run
set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS")
############
#############
## Options ##
#############
############
include(FeatureSummary)
if (WIN32)
option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON)
add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler")
if (MINGW)
option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON)
add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags")
if (USE_MINGW_HARDENING_LINKER)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base")
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
# Enable high-entropy ASLR for 64-bit
# The image base has to be >4GB for HEASLR to be enabled.
# The values used here are kind of arbitrary.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000")
endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
else (USE_MINGW_HARDENING_LINKER)
message(WARNING "Linker Security Flags not enabled!")
endif (USE_MINGW_HARDENING_LINKER)
endif (MINGW)
endif ()
option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON)
configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h)
add_feature_info("Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.")
option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF)
add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.")
option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF)
add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).")
include(MacroJPEG)
+###########################################################
+## Look for Python3. It is also searched by KF5, ##
+## so we should request the correct version in advance ##
+###########################################################
+
+find_package(PythonInterp 3.0)
+find_package(PythonLibrary 3.0)
+
+
########################
#########################
## Look for KDE and Qt ##
#########################
########################
find_package(ECM 1.7.0 REQUIRED NOMODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
include(ECMOptionalAddSubdirectory)
include(ECMAddAppIcon)
include(ECMSetupVersion)
include(ECMMarkNonGuiExecutable)
include(ECMGenerateHeaders)
include(GenerateExportHeader)
include(ECMMarkAsTest)
include(ECMInstallIcons)
include(CMakePackageConfigHelpers)
include(WriteBasicConfigVersionFile)
include(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings)
# do not reorder to be alphabetical: this is the order in which the frameworks
# depend on each other.
find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS
Archive
Config
WidgetsAddons
Completion
CoreAddons
GuiAddons
I18n
ItemModels
ItemViews
WindowSystem
)
# KConfig deprecated authorizeKAction. In order to be warning free,
# compile with the updated function when the dependency is new enough.
# Remove this (and the uses of the define) when the minimum KF5
# version is >= 5.24.0.
if (${KF5Config_VERSION} VERSION_LESS "5.24.0" )
message("Old KConfig (< 5.24.0) found.")
add_definitions(-DKCONFIG_BEFORE_5_24)
endif()
find_package(Qt5 ${MIN_QT_VERSION}
REQUIRED COMPONENTS
Core
Gui
Widgets
Xml
Network
PrintSupport
Svg
Test
Concurrent
)
include (MacroAddFileDependencies)
include (MacroBoolTo01)
include (MacroEnsureOutOfSourceBuild)
macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions")
# Note: OPTIONAL_COMPONENTS does not seem to be reliable
# (as of ECM 5.15.0, CMake 3.2)
find_package(Qt5Multimedia ${MIN_QT_VERSION})
set_package_properties(Qt5Multimedia PROPERTIES
DESCRIPTION "Qt multimedia integration"
URL "http://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used to provide sound support for animations")
macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA)
configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h )
if (NOT WIN32 AND NOT APPLE)
find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras)
find_package(Qt5DBus ${MIN_QT_VERSION})
set(HAVE_DBUS ${Qt5DBus_FOUND})
set_package_properties(Qt5DBus PROPERTIES
DESCRIPTION "Qt DBUS integration"
URL "http://www.qt.io/"
TYPE OPTIONAL
PURPOSE "Optionally used to provide a dbus api on Linux")
find_package(KF5KIO ${MIN_FRAMEWORKS_VERSION})
macro_bool_to_01(KF5KIO_FOUND HAVE_KIO)
set_package_properties(KF5KIO PROPERTIES
DESCRIPTION "KDE's KIO Framework"
URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html"
TYPE OPTIONAL
PURPOSE "Optionally used for recent document handling")
find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION})
macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH)
set_package_properties(KF5Crash PROPERTIES
DESCRIPTION "KDE's Crash Handler"
URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html"
TYPE OPTIONAL
PURPOSE "Optionally used to provide crash reporting on Linux")
find_package(X11 REQUIRED COMPONENTS Xinput)
set(HAVE_X11 TRUE)
add_definitions(-DHAVE_X11)
find_package(XCB COMPONENTS XCB ATOM)
set(HAVE_XCB ${XCB_FOUND})
else()
set(HAVE_DBUS FALSE)
set(HAVE_X11 FALSE)
set(HAVE_XCB FALSE)
endif()
add_definitions(
-DQT_USE_QSTRINGBUILDER
-DQT_STRICT_ITERATORS
-DQT_NO_SIGNALS_SLOTS_KEYWORDS
-DQT_USE_FAST_OPERATOR_PLUS
-DQT_USE_FAST_CONCATENATION
-DQT_NO_URL_CAST_FROM_STRING
-DQT_DISABLE_DEPRECATED_BEFORE=0
)
add_definitions(-DTRANSLATION_DOMAIN=\"krita\")
#
# The reason for this mode is that the Debug mode disable inlining
#
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS_KRITADEVS "-O3 -g" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals")
endif()
if(UNIX)
set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m")
endif()
if(WIN32)
if(MSVC)
# C4522: 'class' : multiple assignment operators specified
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522")
endif()
endif()
# enable exceptions globally
kde_enable_exceptions()
# only with this definition will all the FOO_TEST_EXPORT macro do something
# TODO: check if this can be moved to only those places which make use of it,
# to reduce global compiler definitions that would trigger a recompile of
# everything on a change (like adding/removing tests to/from the build)
if(BUILD_TESTING)
add_definitions(-DCOMPILING_TESTS)
endif()
set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/)
macro(macro_add_unittest_definitions)
add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/")
add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}")
add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}")
add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/")
endmacro()
# overcome some platform incompatibilities
if(WIN32)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks)
add_definitions(-D_USE_MATH_DEFINES)
add_definitions(-DNOMINMAX)
set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib)
endif()
# set custom krita plugin installdir
set(KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins)
###########################
############################
## Required dependencies ##
############################
###########################
find_package(PNG REQUIRED)
if (APPLE)
# this is not added correctly on OSX -- see http://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242
include_directories(SYSTEM ${PNG_INCLUDE_DIR})
endif()
add_definitions(-DBOOST_ALL_NO_LIB)
find_package(Boost REQUIRED COMPONENTS system) # for pigment and stage
include_directories(${Boost_INCLUDE_DIRS})
##
## Test for GNU Scientific Library
##
find_package(GSL)
set_package_properties(GSL PROPERTIES
URL "http://www.gnu.org/software/gsl"
TYPE RECOMMENDED
PURPOSE "Required by Krita's Transform tool.")
macro_bool_to_01(GSL_FOUND HAVE_GSL)
configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h )
###########################
############################
## Optional dependencies ##
############################
###########################
##
## Check for OpenEXR
##
find_package(ZLIB)
set_package_properties(ZLIB PROPERTIES
DESCRIPTION "Compression library"
URL "http://www.zlib.net/"
TYPE OPTIONAL
PURPOSE "Optionally used by the G'Mic and the PSD plugins")
macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB)
find_package(OpenEXR)
set_package_properties(OpenEXR PROPERTIES
DESCRIPTION "High dynamic-range (HDR) image file format"
URL "http://www.openexr.com"
TYPE OPTIONAL
PURPOSE "Required by the Krita OpenEXR filter")
macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR)
set(LINK_OPENEXR_LIB)
if(OPENEXR_FOUND)
include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR})
set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES})
add_definitions(${OPENEXR_DEFINITIONS})
endif()
find_package(TIFF)
set_package_properties(TIFF PROPERTIES
DESCRIPTION "TIFF Library and Utilities"
URL "http://www.remotesensing.org/libtiff"
TYPE OPTIONAL
PURPOSE "Required by the Krita TIFF filter")
find_package(JPEG)
set_package_properties(JPEG PROPERTIES
DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported."
URL "http://www.libjpeg-turbo.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita JPEG filter")
set(LIBRAW_MIN_VERSION "0.16")
find_package(LibRaw ${LIBRAW_MIN_VERSION})
set_package_properties(LibRaw PROPERTIES
DESCRIPTION "Library to decode RAW images"
URL "http://www.libraw.org"
TYPE OPTIONAL
PURPOSE "Required to build the raw import plugin")
find_package(FFTW3)
set_package_properties(FFTW3 PROPERTIES
DESCRIPTION "A fast, free C FFT library"
URL "http://www.fftw.org/"
TYPE OPTIONAL
PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features")
macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3)
find_package(OCIO)
set_package_properties(OCIO PROPERTIES
DESCRIPTION "The OpenColorIO Library"
URL "http://www.opencolorio.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita LUT docker")
macro_bool_to_01(OCIO_FOUND HAVE_OCIO)
##
## Look for OpenGL
##
# TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes)
if(Qt5Gui_OPENGL_IMPLEMENTATION)
message(STATUS "Found QtGui OpenGL support")
else()
message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.")
endif()
##
## Test for eigen3
##
find_package(Eigen3 REQUIRED "3.0")
set_package_properties(Eigen3 PROPERTIES
DESCRIPTION "C++ template library for linear algebra"
URL "http://eigen.tuxfamily.org"
TYPE REQUIRED)
##
## Test for exiv2
##
set(EXIV2_MIN_VERSION "0.16")
find_package(Exiv2 REQUIRED)
set_package_properties(Exiv2 PROPERTIES
DESCRIPTION "Image metadata library and tools"
URL "http://www.exiv2.org"
PURPOSE "Required by Krita")
##
## Test for lcms
##
find_package(LCMS2 REQUIRED "2.4")
set_package_properties(LCMS2 PROPERTIES
DESCRIPTION "LittleCMS Color management engine"
URL "http://www.littlecms.com"
TYPE REQUIRED
PURPOSE "Will be used for color management and is necessary for Krita")
if(LCMS2_FOUND)
if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 )
set(HAVE_LCMS24 TRUE)
endif()
set(HAVE_REQUIRED_LCMS_VERSION TRUE)
set(HAVE_LCMS2 TRUE)
endif()
##
## Test for Vc
##
set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} )
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules )
set(HAVE_VC FALSE)
if( NOT MSVC)
find_package(Vc 1.1.0)
set_package_properties(Vc PROPERTIES
DESCRIPTION "Portable, zero-overhead SIMD library for C++"
URL "https://github.com/VcDevel/Vc"
TYPE OPTIONAL
PURPOSE "Required by the Krita for vectorization")
macro_bool_to_01(Vc_FOUND HAVE_VC)
endif()
configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h )
if(HAVE_VC)
message(STATUS "Vc found!")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_SOURCE_DIR}/cmake/vc")
include (VcMacros)
if(Vc_COMPILER_IS_CLANG)
set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast -fPIC")
elseif (NOT MSVC)
set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC")
endif()
#Handle Vc master
if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG)
AddCompilerFlag("-std=c++11" _ok)
if(NOT _ok)
AddCompilerFlag("-std=c++0x" _ok)
endif()
endif()
macro(ko_compile_for_all_implementations_no_scalar _objs _src)
vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2)
endmacro()
macro(ko_compile_for_all_implementations _objs _src)
vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2)
endmacro()
endif()
set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} )
add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS})
if(WIN32)
set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR}
RUNTIME DESTINATION ${BIN_INSTALL_DIR}
LIBRARY ${INSTALL_TARGETS_DEFAULT_ARGS}
ARCHIVE ${INSTALL_TARGETS_DEFAULT_ARGS} )
endif()
##
## Test endianess
##
include (TestBigEndian)
test_big_endian(CMAKE_WORDS_BIGENDIAN)
##
## Test for qt-poppler
##
find_package(Poppler)
set_package_properties(Poppler PROPERTIES
DESCRIPTION "A PDF rendering library"
URL "http://poppler.freedesktop.org"
TYPE OPTIONAL
PURPOSE "Required by the Krita PDF filter.")
##
## Test for pthreads (for G'Mic)
##
find_package(Threads)
set_package_properties(Threads PROPERTIES
DESCRIPTION "PThreads - A low-level threading library"
TYPE OPTIONAL
PURPOSE "Optionally used by the G'Mic plugin")
##
## Test for OpenMP (for G'Mic)
##
find_package(OpenMP)
set_package_properties(OpenMP PROPERTIES
DESCRIPTION "A low-level parallel execution library"
URL "http://openmp.org/wp/"
TYPE OPTIONAL
PURPOSE "Optionally used by the G'Mic plugin")
##
## Test for Curl (for G'Mic)
##
find_package(CURL)
set_package_properties(CURL PROPERTIES
DESCRIPTION "A tool to fetch remote data"
URL "http://curl.haxx.se/"
TYPE OPTIONAL
PURPOSE "Optionally used by the G'Mic plugin")
############################
#############################
## Add Krita helper macros ##
#############################
############################
include(MacroKritaAddBenchmark)
####################
#####################
## Define includes ##
#####################
####################
# for config.h and <toplevel/foo.h> includes (if any?)
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_SOURCE_DIR}/interfaces
)
add_subdirectory(libs)
add_subdirectory(plugins)
add_subdirectory(benchmarks)
add_subdirectory(krita)
configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h )
configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h)
configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h )
check_function_exists(powf HAVE_POWF)
configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h)
message("\nBroken tests:")
foreach(tst ${KRITA_BROKEN_TESTS})
message(" * ${tst}")
endforeach()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
diff --git a/Mainpage.dox b/Mainpage.dox
index 5919ea8a75..a00ec262bf 100644
--- a/Mainpage.dox
+++ b/Mainpage.dox
@@ -1,52 +1,52 @@
/*
* 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.
*/
/**
- \mainpage Krita Image manipulation and paint application
+ \mainpage Krita Painting application
- Krita is an advanced and modular paint and image manipulation
- application.
+ Krita is an advanced and modular painting application.
Krita is built around two core libraries: pigment and kritaimage.
The pigment library abstracts colorspaces and color
transformations. ColorSpaces provide functions to manipulate pixels. The
kritcolor library loads colorspace plugins to extend the range of
available colorspaces.
The kritaimage library abstracts the storage, creation, inspection
and manipulation of pixels stored in a rectangular area. It provides
layers, filters, iterators and painters. Filters and paint operations
are provided as service plugins loaded through the appropriate trader
queries.
There are the following types of plugins
<ul>
<li> filters
+ <li> generators
<li> tools
- <li> paintops
+ <li> brush engines
<li> colorspaces
- <li> file filters
- <li> view plugins
+ <li> import/export filters
+ <li> view plugins: plugins that extend krita with dockers, dialogs etcetera
</ul>
*/
// DOXYGEN_SET_PROJECT_NAME = Krita
// DOXYGEN_SET_IGNORE_PREFIX = Kis Ko K
diff --git a/cmake/modules/COPYING-CMAKE-SCRIPTS b/cmake/modules/COPYING-CMAKE-SCRIPTS
new file mode 100644
index 0000000000..dd4e5c5ad0
--- /dev/null
+++ b/cmake/modules/COPYING-CMAKE-SCRIPTS
@@ -0,0 +1,23 @@
+ 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.
+
diff --git a/cmake/modules/FindPyQt5.cmake b/cmake/modules/FindPyQt5.cmake
new file mode 100644
index 0000000000..b4a52e156c
--- /dev/null
+++ b/cmake/modules/FindPyQt5.cmake
@@ -0,0 +1,53 @@
+# Find PyQt5
+# ~~~~~~~~~~
+# Copyright (c) 2014, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+# PyQt5 website: http://www.riverbankcomputing.co.uk/pyqt/index.php
+#
+# Find the installed version of PyQt5. FindPyQt5 should only be called after
+# Python has been found.
+#
+# This file defines the following variables:
+#
+# PYQT5_VERSION - The version of PyQt5 found expressed as a 6 digit hex number
+# suitable for comparison as a string
+#
+# PYQT5_VERSION_STR - The version of PyQt5 as a human readable string.
+#
+# PYQT5_VERSION_TAG - The PyQt version tag using by PyQt's sip files.
+#
+# PYQT5_SIP_DIR - The directory holding the PyQt5 .sip files.
+#
+# PYQT5_SIP_FLAGS - The SIP flags used to build PyQt.
+
+IF(EXISTS PYQT5_VERSION)
+ # Already in cache, be silent
+ SET(PYQT5_FOUND TRUE)
+ELSE(EXISTS PYQT5_VERSION)
+
+ FIND_FILE(_find_pyqt5_py FindPyQt5.py PATHS ${CMAKE_MODULE_PATH})
+
+ EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_pyqt5_py} OUTPUT_VARIABLE pyqt5_config)
+ IF(pyqt5_config)
+ STRING(REGEX REPLACE "^pyqt_version:([^\n]+).*$" "\\1" PYQT5_VERSION ${pyqt5_config})
+ STRING(REGEX REPLACE ".*\npyqt_version_str:([^\n]+).*$" "\\1" PYQT5_VERSION_STR ${pyqt5_config})
+ STRING(REGEX REPLACE ".*\npyqt_version_tag:([^\n]+).*$" "\\1" PYQT5_VERSION_TAG ${pyqt5_config})
+ STRING(REGEX REPLACE ".*\npyqt_sip_dir:([^\n]+).*$" "\\1" PYQT5_SIP_DIR ${pyqt5_config})
+ STRING(REGEX REPLACE ".*\npyqt_sip_flags:([^\n]+).*$" "\\1" PYQT5_SIP_FLAGS ${pyqt5_config})
+
+ SET(PYQT5_FOUND TRUE)
+ ENDIF(pyqt5_config)
+
+ IF(PYQT5_FOUND)
+ IF(NOT PYQT5_FIND_QUIETLY)
+ MESSAGE(STATUS "Found PyQt5 version: ${PYQT5_VERSION_STR}")
+ ENDIF(NOT PYQT5_FIND_QUIETLY)
+ ELSE(PYQT5_FOUND)
+ IF(PYQT5_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find PyQt5.")
+ ENDIF(PYQT5_FIND_REQUIRED)
+ ENDIF(PYQT5_FOUND)
+
+ENDIF(EXISTS PYQT5_VERSION)
diff --git a/cmake/modules/FindPyQt5.py b/cmake/modules/FindPyQt5.py
new file mode 100644
index 0000000000..318b9a3033
--- /dev/null
+++ b/cmake/modules/FindPyQt5.py
@@ -0,0 +1,28 @@
+# Copyright (c) 2014, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+import PyQt5.Qt
+import sys
+import os.path
+
+print("pyqt_version:%06.0x" % PyQt5.Qt.PYQT_VERSION)
+print("pyqt_version_str:%s" % PyQt5.Qt.PYQT_VERSION_STR)
+
+pyqt_version_tag = ""
+in_t = False
+for item in PyQt5.Qt.PYQT_CONFIGURATION["sip_flags"].split(' '):
+ if item=="-t":
+ in_t = True
+ elif in_t:
+ if item.startswith("Qt_5"):
+ pyqt_version_tag = item
+ else:
+ in_t = False
+print("pyqt_version_tag:%s" % pyqt_version_tag)
+
+# FIXME This next line is just a little bit too crude.
+pyqt_sip_dir = os.path.join(sys.prefix, "share", "sip", "PyQt5")
+print("pyqt_sip_dir:%s" % pyqt_sip_dir)
+
+print("pyqt_sip_flags:%s" % PyQt5.Qt.PYQT_CONFIGURATION["sip_flags"])
diff --git a/cmake/modules/FindPythonLibrary.cmake b/cmake/modules/FindPythonLibrary.cmake
new file mode 100644
index 0000000000..78309b7d56
--- /dev/null
+++ b/cmake/modules/FindPythonLibrary.cmake
@@ -0,0 +1,73 @@
+# Find Python
+# ~~~~~~~~~~~
+# Find the Python interpreter and related Python directories.
+#
+# This file defines the following variables:
+#
+# PYTHON_EXECUTABLE - The path and filename of the Python interpreter.
+#
+# PYTHON_SHORT_VERSION - The version of the Python interpreter found,
+# excluding the patch version number. (e.g. 2.5 and not 2.5.1))
+#
+# PYTHON_LONG_VERSION - The version of the Python interpreter found as a human
+# readable string.
+#
+# PYTHON_SITE_PACKAGES_INSTALL_DIR - this cache variable can be used for installing
+# own python modules. You may want to adjust this to be the
+# same as ${PYTHON_SITE_PACKAGES_DIR}, but then admin
+# privileges may be required for installation.
+#
+# PYTHON_SITE_PACKAGES_DIR - Location of the Python site-packages directory.
+#
+# PYTHON_INCLUDE_PATH - Directory holding the python.h include file.
+#
+# PYTHON_LIBRARY, PYTHON_LIBRARIES- Location of the Python library.
+
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Copyright (c) 2012, Luca Beltrame <lbeltrame@kde.org>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+include(FindPackageHandleStandardArgs)
+
+find_package(PythonInterp)
+
+if (PYTHONINTERP_FOUND)
+
+ option(INSTALL_PYTHON_FILES_IN_PYTHON_PREFIX "Install the Python files in the Python packages dir" FALSE)
+
+ # Set the Python libraries to what we actually found for interpreters
+ set(Python_ADDITIONAL_VERSIONS "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}")
+ # These are kept for compatibility
+ set(PYTHON_SHORT_VERSION "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}")
+ set(PYTHON_LONG_VERSION ${PYTHON_VERSION_STRING})
+
+ find_package(PythonLibs QUIET)
+
+ if(PYTHONLIBS_FOUND)
+ set(PYTHON_LIBRARY ${PYTHON_LIBRARIES})
+ endif(PYTHONLIBS_FOUND)
+
+ # Auto detect Python site-packages directory
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(True))"
+ OUTPUT_VARIABLE PYTHON_SITE_PACKAGES_DIR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+
+ message(STATUS "Python system site-packages directory: ${PYTHON_SITE_PACKAGES_DIR}")
+ if(INSTALL_PYTHON_FILES_IN_PYTHON_PREFIX)
+ set(PYTHON_SITE_PACKAGES_INSTALL_DIR ${PYTHON_SITE_PACKAGES_DIR})
+ else()
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(True, prefix='${CMAKE_INSTALL_PREFIX}'))"
+ OUTPUT_VARIABLE PYTHON_SITE_PACKAGES_INSTALL_DIR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ )
+ endif()
+
+ if(NOT PYTHON_SITE_PACKAGES_INSTALL_DIR STREQUAL PYTHON_SITE_PACKAGES_DIR)
+ message(STATUS "The Python files will be installed to ${PYTHON_SITE_PACKAGES_INSTALL_DIR}. Make sure to add them to the Python search path (e.g. by setting PYTHONPATH)")
+ endif()
+
+endif(PYTHONINTERP_FOUND)
+
+find_package_handle_standard_args(PythonLibrary DEFAULT_MSG PYTHON_LIBRARY)
diff --git a/cmake/modules/FindSIP.cmake b/cmake/modules/FindSIP.cmake
new file mode 100644
index 0000000000..0cbc853ae7
--- /dev/null
+++ b/cmake/modules/FindSIP.cmake
@@ -0,0 +1,68 @@
+# Find SIP
+# ~~~~~~~~
+#
+# SIP website: http://www.riverbankcomputing.co.uk/sip/index.php
+#
+# Find the installed version of SIP. FindSIP should be called after Python
+# has been found.
+#
+# This file defines the following variables:
+#
+# SIP_VERSION - The version of SIP found expressed as a 6 digit hex number
+# suitable for comparison as a string.
+#
+# SIP_VERSION_STR - The version of SIP found as a human readable string.
+#
+# SIP_EXECUTABLE - Path and filename of the SIP command line executable.
+#
+# SIP_INCLUDE_DIR - Directory holding the SIP C++ header file.
+#
+# SIP_DEFAULT_SIP_DIR - Default directory where .sip files should be installed
+# into.
+
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+
+
+IF(SIP_VERSION)
+ # Already in cache, be silent
+ SET(SIP_FOUND TRUE)
+ELSE(SIP_VERSION)
+
+ FIND_FILE(_find_sip_py FindSIP.py PATHS ${CMAKE_MODULE_PATH})
+
+ EXECUTE_PROCESS(COMMAND ${PYTHON_EXECUTABLE} ${_find_sip_py} OUTPUT_VARIABLE sip_config)
+
+ IF(sip_config)
+ STRING(REGEX REPLACE "^sip_version:([^\n]+).*$" "\\1" SIP_VERSION ${sip_config})
+ STRING(REGEX REPLACE ".*\nsip_version_str:([^\n]+).*$" "\\1" SIP_VERSION_STR ${sip_config})
+ STRING(REGEX REPLACE ".*\nsip_bin:([^\n]+).*$" "\\1" SIP_EXECUTABLE ${sip_config})
+ IF(NOT SIP_DEFAULT_SIP_DIR)
+ STRING(REGEX REPLACE ".*\ndefault_sip_dir:([^\n]+).*$" "\\1" SIP_DEFAULT_SIP_DIR ${sip_config})
+ ENDIF(NOT SIP_DEFAULT_SIP_DIR)
+ STRING(REGEX REPLACE ".*\nsip_inc_dir:([^\n]+).*$" "\\1" SIP_INCLUDE_DIR ${sip_config})
+ FILE(TO_CMAKE_PATH ${SIP_DEFAULT_SIP_DIR} SIP_DEFAULT_SIP_DIR)
+ FILE(TO_CMAKE_PATH ${SIP_INCLUDE_DIR} SIP_INCLUDE_DIR)
+ if (WIN32)
+ set(SIP_EXECUTABLE ${SIP_EXECUTABLE}.exe)
+ endif()
+ IF(EXISTS ${SIP_EXECUTABLE})
+ SET(SIP_FOUND TRUE)
+ ELSE()
+ MESSAGE(STATUS "Found SIP configuration but the sip executable could not be found.")
+ ENDIF()
+ ENDIF(sip_config)
+
+ IF(SIP_FOUND)
+ IF(NOT SIP_FIND_QUIETLY)
+ MESSAGE(STATUS "Found SIP version: ${SIP_VERSION_STR}")
+ ENDIF(NOT SIP_FIND_QUIETLY)
+ ELSE(SIP_FOUND)
+ IF(SIP_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find SIP")
+ ENDIF(SIP_FIND_REQUIRED)
+ ENDIF(SIP_FOUND)
+
+ENDIF(SIP_VERSION)
diff --git a/cmake/modules/FindSIP.py b/cmake/modules/FindSIP.py
new file mode 100644
index 0000000000..ecb734f2cc
--- /dev/null
+++ b/cmake/modules/FindSIP.py
@@ -0,0 +1,15 @@
+# FindSIP.py
+#
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+import sys
+import sipconfig
+
+sipcfg = sipconfig.Configuration()
+print("sip_version:%06.0x" % sipcfg.sip_version)
+print("sip_version_str:%s" % sipcfg.sip_version_str)
+print("sip_bin:%s" % sipcfg.sip_bin)
+print("default_sip_dir:%s" % sipcfg.default_sip_dir)
+print("sip_inc_dir:%s" % sipcfg.sip_inc_dir)
diff --git a/cmake/modules/PythonCompile.py b/cmake/modules/PythonCompile.py
new file mode 100644
index 0000000000..156fea2884
--- /dev/null
+++ b/cmake/modules/PythonCompile.py
@@ -0,0 +1,4 @@
+# By Simon Edwards <simon@simonzone.com>
+# This file is in the public domain.
+import py_compile, sys
+sys.exit(py_compile.main())
diff --git a/cmake/modules/PythonMacros.cmake b/cmake/modules/PythonMacros.cmake
new file mode 100644
index 0000000000..6a82d88375
--- /dev/null
+++ b/cmake/modules/PythonMacros.cmake
@@ -0,0 +1,82 @@
+# Python macros
+# ~~~~~~~~~~~~~
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Copyright (c) 2012, Luca Beltrame <lbeltrame@kde.org>
+# Copyright (c) 2012, Rolf Eike Beer <eike@sf-mail.de>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+# This file defines the following macros:
+#
+# PYTHON_INSTALL (SOURCE_FILE DESTINATION_DIR)
+# Install the SOURCE_FILE, which is a Python .py file, into the
+# destination directory during install. The file will be byte compiled
+# and both the .py file and .pyc file will be installed.
+
+set(PYTHON_MACROS_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
+
+macro(PYTHON_INSTALL SOURCE_FILE DESTINATION_DIR)
+
+ find_file(_python_compile_py PythonCompile.py PATHS ${CMAKE_MODULE_PATH})
+
+ # Install the source file.
+ install(FILES ${SOURCE_FILE} DESTINATION ${DESTINATION_DIR})
+
+ # Byte compile and install the .pyc file, unless explicitly prevented by env..
+ if("$ENV{PYTHONDONTWRITEBYTECODE}" STREQUAL "")
+ get_filename_component(_absfilename ${SOURCE_FILE} ABSOLUTE)
+ get_filename_component(_filename ${SOURCE_FILE} NAME)
+ get_filename_component(_filenamebase ${SOURCE_FILE} NAME_WE)
+ get_filename_component(_basepath ${SOURCE_FILE} PATH)
+
+ if(WIN32)
+ # remove drive letter
+ string(REGEX REPLACE "^[a-zA-Z]:/" "/" _basepath "${_basepath}")
+ endif(WIN32)
+
+ set(_bin_py ${CMAKE_CURRENT_BINARY_DIR}/${_basepath}/${_filename})
+
+ # Python 3.2 changed the pyc file location
+ if(PYTHON_VERSION_STRING VERSION_GREATER 3.1)
+ # To get the right version for suffix
+ set(_bin_pyc "${CMAKE_CURRENT_BINARY_DIR}/${_basepath}/__pycache__/${_filenamebase}.cpython-${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}.pyc")
+ set(_py_install_dir "${DESTINATION_DIR}/__pycache__/")
+ else()
+ set(_bin_pyc "${CMAKE_CURRENT_BINARY_DIR}/${_basepath}/${_filenamebase}.pyc")
+ set(_py_install_dir "${DESTINATION_DIR}")
+ endif()
+
+ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${_basepath})
+
+ # Setting because it will be displayed later, in compile_python_files
+ set(_message "Byte-compiling ${_bin_py} to ${_bin_pyc}")
+
+ string(REPLACE "/" "_" _rule_name "${_basepath}/${_bin_pyc}")
+ add_custom_target("${_rule_name}" ALL)
+
+ get_filename_component(_abs_bin_py ${_bin_py} ABSOLUTE)
+ if(_abs_bin_py STREQUAL _absfilename) # Don't copy the file onto itself.
+ add_custom_command(
+ TARGET "${_rule_name}"
+ COMMAND "${CMAKE_COMMAND}" -E echo "${_message}"
+ COMMAND "${PYTHON_EXECUTABLE}" "${_python_compile_py}" "${_bin_py}"
+ DEPENDS "${_absfilename}"
+ )
+ else()
+ add_custom_command(
+ TARGET "${_rule_name}"
+ COMMAND "${CMAKE_COMMAND}" -E echo "${_message}"
+ COMMAND "${CMAKE_COMMAND}" -E copy "${_absfilename}" "${_bin_py}"
+ COMMAND "${PYTHON_EXECUTABLE}" "${_python_compile_py}" "${_bin_py}"
+ DEPENDS "${_absfilename}"
+ )
+ endif()
+
+ install(FILES ${_bin_pyc} DESTINATION "${_py_install_dir}")
+ unset(_py_install_dir)
+ unset(_message)
+
+ endif("$ENV{PYTHONDONTWRITEBYTECODE}" STREQUAL "")
+
+endmacro(PYTHON_INSTALL)
diff --git a/cmake/modules/SIPMacros.cmake b/cmake/modules/SIPMacros.cmake
new file mode 100644
index 0000000000..28c1cf4ca2
--- /dev/null
+++ b/cmake/modules/SIPMacros.cmake
@@ -0,0 +1,124 @@
+# Macros for SIP
+# ~~~~~~~~~~~~~~
+# Copyright (c) 2007, Simon Edwards <simon@simonzone.com>
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+#
+# SIP website: http://www.riverbankcomputing.co.uk/sip/index.php
+#
+# This file defines the following macros:
+#
+# ADD_SIP_PYTHON_MODULE (MODULE_NAME MODULE_SIP [library1, libaray2, ...])
+# Specifies a SIP file to be built into a Python module and installed.
+# MODULE_NAME is the name of Python module including any path name. (e.g.
+# os.sys, Foo.bar etc). MODULE_SIP the path and filename of the .sip file
+# to process and compile. libraryN are libraries that the Python module,
+# which is typically a shared library, should be linked to. The built
+# module will also be install into Python's site-packages directory.
+#
+# The behaviour of the ADD_SIP_PYTHON_MODULE macro can be controlled by a
+# number of variables:
+#
+# SIP_INCLUDES - List of directories which SIP will scan through when looking
+# for included .sip files. (Corresponds to the -I option for SIP.)
+#
+# SIP_TAGS - List of tags to define when running SIP. (Corresponds to the -t
+# option for SIP.)
+#
+# SIP_CONCAT_PARTS - An integer which defines the number of parts the C++ code
+# of each module should be split into. Defaults to 8. (Corresponds to the
+# -j option for SIP.)
+#
+# SIP_DISABLE_FEATURES - List of feature names which should be disabled
+# running SIP. (Corresponds to the -x option for SIP.)
+#
+# SIP_EXTRA_OPTIONS - Extra command line options which should be passed on to
+# SIP.
+
+SET(SIP_INCLUDES)
+SET(SIP_TAGS)
+SET(SIP_CONCAT_PARTS 8)
+SET(SIP_DISABLE_FEATURES)
+SET(SIP_EXTRA_OPTIONS)
+
+MACRO(ADD_SIP_PYTHON_MODULE MODULE_NAME MODULE_SIP)
+
+ SET(EXTRA_LINK_LIBRARIES ${ARGN})
+
+ STRING(REPLACE "." "/" _x ${MODULE_NAME})
+ GET_FILENAME_COMPONENT(_parent_module_path ${_x} PATH)
+ GET_FILENAME_COMPONENT(_child_module_name ${_x} NAME)
+
+ GET_FILENAME_COMPONENT(_module_path ${MODULE_SIP} PATH)
+
+ if(_module_path STREQUAL "")
+ set(CMAKE_CURRENT_SIP_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+ else(_module_path STREQUAL "")
+ set(CMAKE_CURRENT_SIP_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/${_module_path}")
+ endif(_module_path STREQUAL "")
+
+ GET_FILENAME_COMPONENT(_abs_module_sip ${MODULE_SIP} ABSOLUTE)
+
+ # We give this target a long logical target name.
+ # (This is to avoid having the library name clash with any already
+ # install library names. If that happens then cmake dependancy
+ # tracking get confused.)
+ STRING(REPLACE "." "_" _logical_name ${MODULE_NAME})
+ SET(_logical_name "python_module_${_logical_name}")
+
+ FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_SIP_OUTPUT_DIR}) # Output goes in this dir.
+
+ SET(_sip_includes)
+ FOREACH (_inc ${SIP_INCLUDES})
+ GET_FILENAME_COMPONENT(_abs_inc ${_inc} ABSOLUTE)
+ LIST(APPEND _sip_includes -I ${_abs_inc})
+ ENDFOREACH (_inc )
+
+ SET(_sip_tags)
+ FOREACH (_tag ${SIP_TAGS})
+ LIST(APPEND _sip_tags -t ${_tag})
+ ENDFOREACH (_tag)
+
+ SET(_sip_x)
+ FOREACH (_x ${SIP_DISABLE_FEATURES})
+ LIST(APPEND _sip_x -x ${_x})
+ ENDFOREACH (_x ${SIP_DISABLE_FEATURES})
+
+ SET(_message "-DMESSAGE=Generating CPP code for module ${MODULE_NAME}")
+ SET(_sip_output_files)
+ FOREACH(CONCAT_NUM RANGE 0 ${SIP_CONCAT_PARTS} )
+ IF( ${CONCAT_NUM} LESS ${SIP_CONCAT_PARTS} )
+ SET(_sip_output_files ${_sip_output_files} ${CMAKE_CURRENT_SIP_OUTPUT_DIR}/sip${_child_module_name}part${CONCAT_NUM}.cpp )
+ ENDIF( ${CONCAT_NUM} LESS ${SIP_CONCAT_PARTS} )
+ ENDFOREACH(CONCAT_NUM RANGE 0 ${SIP_CONCAT_PARTS} )
+
+ IF(NOT WIN32)
+ SET(TOUCH_COMMAND touch)
+ ELSE(NOT WIN32)
+ SET(TOUCH_COMMAND echo)
+ # instead of a touch command, give out the name and append to the files
+ # this is basically what the touch command does.
+ FOREACH(filename ${_sip_output_files})
+ FILE(APPEND filename "")
+ ENDFOREACH(filename ${_sip_output_files})
+ ENDIF(NOT WIN32)
+ ADD_CUSTOM_COMMAND(
+ OUTPUT ${_sip_output_files}
+ COMMAND ${CMAKE_COMMAND} -E echo ${message}
+ COMMAND ${TOUCH_COMMAND} ${_sip_output_files}
+ COMMAND ${SIP_EXECUTABLE} ${_sip_tags} ${_sip_x} ${SIP_EXTRA_OPTIONS} -j ${SIP_CONCAT_PARTS} -c ${CMAKE_CURRENT_SIP_OUTPUT_DIR} ${_sip_includes} ${_abs_module_sip}
+ DEPENDS ${_abs_module_sip} ${SIP_EXTRA_FILES_DEPEND}
+ )
+ # not sure if type MODULE could be uses anywhere, limit to cygwin for now
+ IF (CYGWIN)
+ ADD_LIBRARY(${_logical_name} MODULE ${_sip_output_files} )
+ ELSE (CYGWIN)
+ ADD_LIBRARY(${_logical_name} SHARED ${_sip_output_files} )
+ ENDIF (CYGWIN)
+ TARGET_LINK_LIBRARIES(${_logical_name} ${PYTHON_LIBRARY})
+ TARGET_LINK_LIBRARIES(${_logical_name} ${EXTRA_LINK_LIBRARIES})
+ SET_TARGET_PROPERTIES(${_logical_name} PROPERTIES PREFIX "" OUTPUT_NAME ${_child_module_name})
+
+ INSTALL(TARGETS ${_logical_name} DESTINATION "${PYTHON_SITE_PACKAGES_INSTALL_DIR}/${_parent_module_path}")
+
+ENDMACRO(ADD_SIP_PYTHON_MODULE)
diff --git a/krita/data/actions/InteractionTool.action b/krita/data/actions/InteractionTool.action
index e610ba4191..0ca5cdf4a4 100644
--- a/krita/data/actions/InteractionTool.action
+++ b/krita/data/actions/InteractionTool.action
@@ -1,126 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Tools">
<Actions category="Interaction Tool">
<text>Interaction Tool</text>
<Action name="object_order_raise">
<iconText>Raise</iconText>
<shortcut>Ctrl+]</shortcut>
<toolTip>Raise</toolTip>
<icon>object-order-raise-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>&amp;Raise</text>
</Action>
<Action name="object_align_horizontal_right">
<iconText>Align Right</iconText>
<shortcut></shortcut>
<toolTip>Align Right</toolTip>
<icon>object-align-horizontal-right-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Align Right</text>
</Action>
<Action name="object_ungroup">
<iconText>Ungroup</iconText>
<shortcut></shortcut>
<toolTip>Ungroup</toolTip>
<icon>object-ungroup-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Ungroup</text>
</Action>
<Action name="object_order_back">
<iconText>Send to Back</iconText>
<shortcut>Ctrl+Shift+[</shortcut>
<toolTip>Send to Back</toolTip>
<icon>object-order-back-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Send to &amp;Back</text>
</Action>
<Action name="object_order_front">
<iconText>Bring to Front</iconText>
<shortcut>Ctrl+Shift+]</shortcut>
<toolTip>Bring to Front</toolTip>
<icon>object-order-front-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Bring to &amp;Front</text>
</Action>
<Action name="object_align_vertical_center">
<iconText>Vertically Center</iconText>
<shortcut></shortcut>
<toolTip>Vertically Center</toolTip>
<icon>object-align-vertical-center-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Vertically Center</text>
</Action>
<Action name="object_group">
<iconText>Group</iconText>
<shortcut></shortcut>
<toolTip>Group</toolTip>
<icon>object-group-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Group</text>
</Action>
<Action name="object_align_horizontal_left">
<iconText>Align Left</iconText>
<shortcut></shortcut>
<toolTip>Align Left</toolTip>
<icon>object-align-horizontal-left-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Align Left</text>
</Action>
<Action name="object_align_vertical_top">
<iconText>Align Top</iconText>
<shortcut></shortcut>
<toolTip>Align Top</toolTip>
<icon>object-align-vertical-top-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Align Top</text>
</Action>
<Action name="object_align_horizontal_center">
<iconText>Horizontally Center</iconText>
<shortcut></shortcut>
<toolTip>Horizontally Center</toolTip>
<icon>object-align-horizontal-center-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Horizontally Center</text>
</Action>
<Action name="object_order_lower">
<iconText>Lower</iconText>
<shortcut>Ctrl+[</shortcut>
<toolTip>Lower</toolTip>
<icon>object-order-lower-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>&amp;Lower</text>
</Action>
<Action name="object_align_vertical_bottom">
<iconText>Align Bottom</iconText>
<shortcut></shortcut>
<toolTip>Align Bottom</toolTip>
<icon>object-align-vertical-bottom-calligra</icon>
<whatsThis></whatsThis>
<statusTip></statusTip>
<isCheckable>false</isCheckable>
<text>Align Bottom</text>
</Action>
+
+ <Action name="object_distribute_horizontal_left">
+ <text>Distribute Left</text>
+ <toolTip>Distribute left edges equidistantly</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-horizontal-left</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+ <Action name="object_distribute_horizontal_center">
+ <text>Distribute Centers Horizontally</text>
+ <toolTip>Distribute centers equidistantly horizontally</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-horizontal-center</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+ <Action name="object_distribute_horizontal_right">
+ <text>Distribute Right</text>
+ <toolTip>Distribute right edges equidistantly</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-horizontal-right</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+ <Action name="object_distribute_horizontal_gaps">
+ <text>Distribute Horizontal Gap</text>
+ <toolTip>Make horizontal gaps between objects equal</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-horizontal</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+
+ <Action name="object_distribute_vertical_top">
+ <text>Distribute Top</text>
+ <toolTip>Distribute top edges equidistantly</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-vertical-top</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+ <Action name="object_distribute_vertical_center">
+ <text>Distribute Centers Vertically</text>
+ <toolTip>Distribute centers equidistantly vertically</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-vertical-center</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+ <Action name="object_distribute_vertical_bottom">
+ <text>Distribute Bottom</text>
+ <toolTip>Distribute bottom edges equidistantly</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-vertical-bottom</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
+ <Action name="object_distribute_vertical_gaps">
+ <text>Distribute Vertical Gap</text>
+ <toolTip>Make vertical gaps between objects equal</toolTip>
+ <shortcut></shortcut>
+ <icon>distribute-vertical</icon>
+ <whatsThis></whatsThis>
+ <statusTip></statusTip>
+ <isCheckable>false</isCheckable>
+ </Action>
</Actions>
</ActionCollection>
diff --git a/krita/doc/krita_svg_extensions.md b/krita/doc/krita_svg_extensions.md
new file mode 100644
index 0000000000..1c1574294b
--- /dev/null
+++ b/krita/doc/krita_svg_extensions.md
@@ -0,0 +1,23 @@
+= Krita SVG Extensions =
+
+Krita has a few extensions over SVG format to ensure correct saving and
+loading of Krita custom elements.
+
+== Attribute: krita:marker-fill-method ==
+
+Possible values:
+
+ * `default` -- markers are filled according to SVG standard rules, that is
+ each marker has its own fill, which is filled in the marker's local
+ coordinates.
+
+ * `auto` -- markers are considered to be a part of the path. The outline of the
+ path is combined with the outline of the markers and filled with a single pass
+ of the object's fill strategy.
+
+Default value: `default`
+
+== [DEPRECATED] Attribute: krita:arc='arc' and krita:arcType='chord' ==
+
+That is a temporary namespace that was used before introduction of sodipodi:arc-type='chord'
+option. Krita never saves files with this option and the support of it will be removed soon.
diff --git a/krita/krita.action b/krita/krita.action
index 7d8c9a6cca..353592a756 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,2948 +1,2936 @@
<?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="rename_composition">
<icon></icon>
<text>Rename Composition...</text>
<whatsThis></whatsThis>
<toolTip>Rename Composition</toolTip>
<iconText>Rename Composition</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="update_composition">
<icon></icon>
<text>Update Composition</text>
<whatsThis></whatsThis>
<toolTip>Update Composition</toolTip>
<iconText>Update Composition</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Painting">
<text>Painting</text>
<Action name="make_brush_color_lighter">
<icon></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></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></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></icon>
<text>Decrease opacity</text>
<whatsThis></whatsThis>
<toolTip>Decrease opacity</toolTip>
<iconText>Decrease opacity</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="erase_action">
<icon>draw-eraser</icon>
<text>Set eraser mode</text>
<whatsThis></whatsThis>
<toolTip>Set eraser mode</toolTip>
<iconText>Set eraser mode</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>E</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reload_preset_action">
<icon>view-refresh</icon>
<text>Reload Original Preset</text>
<whatsThis></whatsThis>
<toolTip>Reload Original Preset</toolTip>
<iconText>Reload Original Preset</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="preserve_alpha">
<icon>transparency-unlocked</icon>
<text>Preserve Alpha</text>
<whatsThis></whatsThis>
<toolTip>Preserve Alpha</toolTip>
<iconText>Preserve Alpha</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="disable_pressure">
<icon>transform_icons_penPressure</icon>
<text>Use Pen Pressure</text>
<whatsThis></whatsThis>
<toolTip>Use Pen Pressure</toolTip>
<iconText>Use Pen Pressure</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="hmirror_action">
<icon>symmetry-horizontal</icon>
<text>Horizontal Mirror Tool</text>
<whatsThis></whatsThis>
<toolTip>Horizontal Mirror Tool</toolTip>
<iconText>Horizontal Mirror Tool</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="vmirror_action">
<icon>symmetry-vertical</icon>
<text>Vertical Mirror Tool</text>
<whatsThis></whatsThis>
<toolTip>Vertical Mirror Tool</toolTip>
<iconText>Vertical Mirror Tool</iconText>
<activationFlags>10000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
- <Action name="paste_at">
- <icon></icon>
- <text>Paste at cursor</text>
- <whatsThis></whatsThis>
- <toolTip>Paste at cursor</toolTip>
- <iconText>Paste at cursor</iconText>
- <activationFlags>0</activationFlags>
- <activationConditions>0</activationConditions>
- <shortcut></shortcut>
- <isCheckable>false</isCheckable>
- <statusTip></statusTip>
- </Action>
<Action name="invert">
<icon></icon>
<text>&amp;Invert Selection</text>
<whatsThis></whatsThis>
<toolTip>Invert current selection</toolTip>
<iconText>Invert Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut>Ctrl+Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="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="toggle-selection-overlay-mode">
<icon></icon>
<text>&amp;Toggle Selection Display Mode</text>
<whatsThis></whatsThis>
<toolTip>Toggle Selection Display Mode</toolTip>
<iconText>Toggle Selection Display Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_favorite_preset">
<icon></icon>
<text>Next Favourite Preset</text>
<whatsThis></whatsThis>
<toolTip>Next Favourite Preset</toolTip>
<iconText>Next Favourite Preset</iconText>
<shortcut>,</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_favorite_preset">
<icon></icon>
<text>Previous Favourite Preset</text>
<whatsThis></whatsThis>
<toolTip>Previous Favourite Preset</toolTip>
<iconText>Previous Favourite Preset</iconText>
<shortcut>.</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_preset">
<icon>preset-switcher</icon>
<text>Switch to Previous Preset</text>
<whatsThis></whatsThis>
<toolTip>Switch to Previous Preset</toolTip>
<iconText>Switch to Previous Preset</iconText>
<shortcut>/</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="BrushesAndStuff">
<icon></icon>
<text>Hide Brushes and Stuff Toolbar</text>
<whatsThis></whatsThis>
<toolTip>Hide Brushes and Stuff Toolbar</toolTip>
<iconText>Hide Brushes and Stuff Toolbar</iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reset_fg_bg">
<icon></icon>
<text>Reset Foreground and Background Color</text>
<whatsThis></whatsThis>
<toolTip>Reset Foreground and Background Color</toolTip>
<iconText>Reset Foreground and Background Color</iconText>
<shortcut>D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_fg_bg">
<icon></icon>
<text>Swap Foreground and Background Color</text>
<whatsThis></whatsThis>
<toolTip>Swap Foreground and Background Color</toolTip>
<iconText>Swap Foreground and Background Color</iconText>
<shortcut>X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_weighted_brush_smoothing">
<icon>smoothing-weighted</icon>
<text>Brush Smoothing: Weighted</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Weighted</toolTip>
<iconText>Brush Smoothing: Weighted</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_no_brush_smoothing">
<icon>smoothing-no</icon>
<text>Brush Smoothing: Disabled</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Disabled</toolTip>
<iconText>Brush Smoothing: Disabled</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="set_stabilizer_brush_smoothing">
<icon>smoothing-stabilizer</icon>
<text>Brush Smoothing: Stabilizer</text>
<whatsThis></whatsThis>
<toolTip>Brush Smoothing: Stabilizer</toolTip>
<iconText>Brush Smoothing: Stabilizer</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="decrease_brush_size">
<icon></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></icon>
<text>Increase Brush Size</text>
<whatsThis></whatsThis>
<toolTip>Increase Brush Size</toolTip>
<iconText>Increase Brush Size</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>]</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_assistant">
<icon></icon>
<text>Toggle Assistant</text>
<whatsThis></whatsThis>
<toolTip>Toggle Assistant</toolTip>
<iconText>ToggleAssistant</iconText>
<shortcut>Ctrl+Shift+L</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="undo_polygon_selection">
<icon></icon>
<text>Undo Polygon Selection Points</text>
<whatsThis></whatsThis>
<toolTip>Undo Polygon Selection Points</toolTip>
<iconText>Undo Polygon Selection Points</iconText>
<shortcut>Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_foreground_color_opacity">
<icon></icon>
<text>Fill with Foreground Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color (Opacity)</toolTip>
<iconText>Fill with Foreground Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color_opacity">
<icon></icon>
<text>Fill with Background Color (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color (Opacity)</toolTip>
<iconText>Fill with Background Color (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Ctrl+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern_opacity">
<icon></icon>
<text>Fill with Pattern (Opacity)</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern (Opacity)</toolTip>
<iconText>Fill with Pattern (Opacity)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_selection_to_shape">
<icon></icon>
<text>Convert &amp;to Shape</text>
<whatsThis></whatsThis>
<toolTip>Convert to Shape</toolTip>
<iconText>Convert to Shape</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_opaque">
<icon></icon>
<text>&amp;Select Opaque</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque</toolTip>
<iconText>Select Opaque</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show-global-selection-mask">
<icon></icon>
<text>&amp;Show Global Selection Mask</text>
<whatsThis></whatsThis>
<toolTip>Shows global selection as a usual selection mask in &lt;interface>Layers&lt;/interface> docker</toolTip>
<iconText>Show Global Selection Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Filters">
<text>Filters</text>
<Action name="krita_filter_colortoalpha">
<icon>color-to-alpha</icon>
<text>&amp;Color to Alpha...</text>
<whatsThis></whatsThis>
<toolTip>Color to Alpha</toolTip>
<iconText>Color to Alpha</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_top edge detections">
<icon></icon>
<text>&amp;Top Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Top Edge Detection</toolTip>
<iconText>Top Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_indexcolors">
<icon></icon>
<text>&amp;Index Colors...</text>
<whatsThis></whatsThis>
<toolTip>Index Colors</toolTip>
<iconText>Index Colors</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss horizontal only">
<icon></icon>
<text>Emboss Horizontal &amp;Only</text>
<whatsThis></whatsThis>
<toolTip>Emboss Horizontal Only</toolTip>
<iconText>Emboss Horizontal Only</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_dodge">
<icon></icon>
<text>D&amp;odge</text>
<whatsThis></whatsThis>
<toolTip>Dodge</toolTip>
<iconText>Dodge</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_sharpen">
<icon></icon>
<text>&amp;Sharpen</text>
<whatsThis></whatsThis>
<toolTip>Sharpen</toolTip>
<iconText>Sharpen</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_burn">
<icon></icon>
<text>B&amp;urn</text>
<whatsThis></whatsThis>
<toolTip>Burn</toolTip>
<iconText>Burn</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_mean removal">
<icon></icon>
<text>&amp;Mean Removal</text>
<whatsThis></whatsThis>
<toolTip>Mean Removal</toolTip>
<iconText>Mean Removal</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_gaussian blur">
<icon></icon>
<text>&amp;Gaussian Blur...</text>
<whatsThis></whatsThis>
<toolTip>Gaussian Blur</toolTip>
<iconText>Gaussian Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss all directions">
<icon></icon>
<text>Emboss &amp;in All Directions</text>
<whatsThis></whatsThis>
<toolTip>Emboss in All Directions</toolTip>
<iconText>Emboss in All Directions</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_smalltiles">
<icon></icon>
<text>&amp;Small Tiles...</text>
<whatsThis></whatsThis>
<toolTip>Small Tiles</toolTip>
<iconText>Small Tiles</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_levels">
<icon></icon>
<text>&amp;Levels...</text>
<whatsThis></whatsThis>
<toolTip>Levels</toolTip>
<iconText>Levels</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_sobel">
<icon></icon>
<text>&amp;Sobel...</text>
<whatsThis></whatsThis>
<toolTip>Sobel</toolTip>
<iconText>Sobel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_wave">
<icon></icon>
<text>&amp;Wave...</text>
<whatsThis></whatsThis>
<toolTip>Wave</toolTip>
<iconText>Wave</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_motion blur">
<icon></icon>
<text>&amp;Motion Blur...</text>
<whatsThis></whatsThis>
<toolTip>Motion Blur</toolTip>
<iconText>Motion Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_perchannel">
<icon></icon>
<text>&amp;Color Adjustment curves...</text>
<whatsThis></whatsThis>
<toolTip>Color Adjustment curves</toolTip>
<iconText>Color Adjustment curves</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_pixelize">
<icon></icon>
<text>Pi&amp;xelize...</text>
<whatsThis></whatsThis>
<toolTip>Pixelize</toolTip>
<iconText>Pixelize</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss laplascian">
<icon></icon>
<text>Emboss (&amp;Laplacian)</text>
<whatsThis></whatsThis>
<toolTip>Emboss (Laplacian)</toolTip>
<iconText>Emboss (Laplacian)</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_left edge detections">
<icon></icon>
<text>&amp;Left Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Left Edge Detection</toolTip>
<iconText>Left Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_blur">
<icon></icon>
<text>&amp;Blur...</text>
<whatsThis></whatsThis>
<toolTip>Blur</toolTip>
<iconText>Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_raindrops">
<icon></icon>
<text>&amp;Raindrops...</text>
<whatsThis></whatsThis>
<toolTip>Raindrops</toolTip>
<iconText>Raindrops</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_bottom edge detections">
<icon></icon>
<text>&amp;Bottom Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Bottom Edge Detection</toolTip>
<iconText>Bottom Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_noise">
<icon></icon>
<text>&amp;Random Noise...</text>
<whatsThis></whatsThis>
<toolTip>Random Noise</toolTip>
<iconText>Random Noise</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_brightnesscontrast">
<icon></icon>
<text>&amp;Brightness/Contrast curve...</text>
<whatsThis></whatsThis>
<toolTip>Brightness/Contrast curve</toolTip>
<iconText>Brightness/Contrast curve</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_colorbalance">
<icon></icon>
<text>Colo&amp;r Balance..</text>
<whatsThis></whatsThis>
<toolTip>Color Balance..</toolTip>
<iconText>Color Balance..</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_phongbumpmap">
<icon></icon>
<text>&amp;Phong Bumpmap...</text>
<whatsThis></whatsThis>
<toolTip>Phong Bumpmap</toolTip>
<iconText>Phong Bumpmap</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_desaturate">
<icon></icon>
<text>&amp;Desaturate</text>
<whatsThis></whatsThis>
<toolTip>Desaturate</toolTip>
<iconText>Desaturate</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_colortransfer">
<icon></icon>
<text>Color &amp;Transfer...</text>
<whatsThis></whatsThis>
<toolTip>Color Transfer</toolTip>
<iconText>Color Transfer</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss vertical only">
<icon></icon>
<text>Emboss &amp;Vertical Only</text>
<whatsThis></whatsThis>
<toolTip>Emboss Vertical Only</toolTip>
<iconText>Emboss Vertical Only</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_lens blur">
<icon></icon>
<text>&amp;Lens Blur...</text>
<whatsThis></whatsThis>
<toolTip>Lens Blur</toolTip>
<iconText>Lens Blur</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_minimize">
<icon></icon>
<text>M&amp;inimize Channel</text>
<whatsThis></whatsThis>
<toolTip>Minimize Channel</toolTip>
<iconText>Minimize Channel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_maximize">
<icon></icon>
<text>M&amp;aximize Channel</text>
<whatsThis></whatsThis>
<toolTip>Maximize Channel</toolTip>
<iconText>Maximize Channel</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_oilpaint">
<icon></icon>
<text>&amp;Oilpaint...</text>
<whatsThis></whatsThis>
<toolTip>Oilpaint</toolTip>
<iconText>Oilpaint</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_right edge detections">
<icon></icon>
<text>&amp;Right Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Right Edge Detection</toolTip>
<iconText>Right Edge Detection</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_autocontrast">
<icon></icon>
<text>&amp;Auto Contrast</text>
<whatsThis></whatsThis>
<toolTip>Auto Contrast</toolTip>
<iconText>Auto Contrast</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_roundcorners">
<icon></icon>
<text>&amp;Round Corners...</text>
<whatsThis></whatsThis>
<toolTip>Round Corners</toolTip>
<iconText>Round Corners</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_unsharp">
<icon></icon>
<text>&amp;Unsharp Mask...</text>
<whatsThis></whatsThis>
<toolTip>Unsharp Mask</toolTip>
<iconText>Unsharp Mask</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss">
<icon></icon>
<text>&amp;Emboss with Variable Depth...</text>
<whatsThis></whatsThis>
<toolTip>Emboss with Variable Depth</toolTip>
<iconText>Emboss with Variable Depth</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_emboss horizontal and vertical">
<icon></icon>
<text>Emboss &amp;Horizontal &amp;&amp; Vertical</text>
<whatsThis></whatsThis>
<toolTip>Emboss Horizontal &amp; Vertical</toolTip>
<iconText>Emboss Horizontal &amp; Vertical</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_randompick">
<icon></icon>
<text>Random &amp;Pick...</text>
<whatsThis></whatsThis>
<toolTip>Random Pick</toolTip>
<iconText>Random Pick</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_gaussiannoisereducer">
<icon></icon>
<text>&amp;Gaussian Noise Reduction...</text>
<whatsThis></whatsThis>
<toolTip>Gaussian Noise Reduction</toolTip>
<iconText>Gaussian Noise Reduction</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_posterize">
<icon></icon>
<text>&amp;Posterize...</text>
<whatsThis></whatsThis>
<toolTip>Posterize</toolTip>
<iconText>Posterize</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_waveletnoisereducer">
<icon></icon>
<text>&amp;Wavelet Noise Reducer...</text>
<whatsThis></whatsThis>
<toolTip>Wavelet Noise Reducer</toolTip>
<iconText>Wavelet Noise Reducer</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_hsvadjustment">
<icon></icon>
<text>&amp;HSV Adjustment...</text>
<whatsThis></whatsThis>
<toolTip>HSV Adjustment</toolTip>
<iconText>HSV Adjustment</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="tool-shortcuts">
<text>Tool Shortcuts</text>
<Action name="KritaShape/KisToolDyna">
<icon></icon>
<text>Dynamic Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Dynamic Brush Tool</toolTip>
<iconText>Dynamic Brush Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolCrop">
<icon></icon>
<text>Crop Tool</text>
<whatsThis></whatsThis>
<toolTip>Crop the image to an area</toolTip>
<iconText>Crop the image to an area</iconText>
<shortcut>C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPolygon">
<icon></icon>
<text>Polygon Tool</text>
<whatsThis></whatsThis>
<toolTip>Polygon Tool. Shift-mouseclick ends the polygon.</toolTip>
<iconText>Polygon Tool. Shift-mouseclick ends the polygon.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ReferencesTool">
<icon></icon>
<text>References</text>
<whatsThis></whatsThis>
<toolTip>References</toolTip>
<iconText>References</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolRectangle">
<icon></icon>
<text>Rectangle Tool</text>
<whatsThis></whatsThis>
<toolTip>Rectangle Tool</toolTip>
<iconText>Rectangle Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolMultiBrush">
<icon></icon>
<text>Multibrush Tool</text>
<whatsThis></whatsThis>
<toolTip>Multibrush Tool</toolTip>
<iconText>Multibrush Tool</iconText>
<shortcut>Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="InteractionTool">
<icon></icon>
<text>Shape Manipulation Tool</text>
<whatsThis></whatsThis>
<toolTip>Shape Manipulation Tool</toolTip>
<iconText>Shape Manipulation Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaSelected/KisToolColorPicker">
<icon></icon>
<text>Color Picker</text>
<whatsThis></whatsThis>
<toolTip>Select a color from the image or current layer</toolTip>
<iconText>Select a color from the image or current layer</iconText>
<shortcut>P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="TextTool">
<icon></icon>
<text>Text Editing Tool</text>
<whatsThis></whatsThis>
<toolTip>Text editing</toolTip>
<iconText>Text editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectOutline">
<icon></icon>
<text>Outline Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Outline Selection Tool</toolTip>
<iconText>Outline Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ArtisticTextTool">
<icon></icon>
<text>Artistic Text Tool</text>
<whatsThis></whatsThis>
<toolTip>Artistic text editing</toolTip>
<iconText>Artistic text editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectPath">
<icon></icon>
<text>Bezier Curve Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Select a </toolTip>
<iconText>Bezier Curve Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectSimilar">
<icon></icon>
<text>Similar Color Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Select a </toolTip>
<iconText>Similar Color Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaFill/KisToolFill">
<icon></icon>
<text>Fill Tool</text>
<whatsThis></whatsThis>
<toolTip>Fill a contiguous area of color with a color, or fill a selection.</toolTip>
<iconText>Fill a contiguous area of color with a color, or fill a selection.</iconText>
<shortcut>F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolLine">
<icon></icon>
<text>Line Tool</text>
<whatsThis></whatsThis>
<toolTip>Line Tool</toolTip>
<iconText>Line Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPencil">
<icon></icon>
<text>Freehand Path Tool</text>
<whatsThis></whatsThis>
<toolTip>Freehand Path Tool</toolTip>
<iconText>Freehand Path Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPath">
<icon></icon>
<text>Bezier Curve Tool</text>
<whatsThis></whatsThis>
<toolTip>Bezier Curve Tool. Shift-mouseclick ends the curve.</toolTip>
<iconText>Bezier Curve Tool. Shift-mouseclick ends the curve.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolEllipse">
<icon></icon>
<text>Ellipse Tool</text>
<whatsThis></whatsThis>
<toolTip>Ellipse Tool</toolTip>
<iconText>Ellipse Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolBrush">
<icon></icon>
<text>Freehand Brush Tool</text>
<whatsThis></whatsThis>
<toolTip>Freehand Brush Tool</toolTip>
<iconText>Freehand Brush Tool</iconText>
<shortcut>B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="CreateShapesTool">
<icon></icon>
<text>Create object</text>
<whatsThis></whatsThis>
<toolTip>Create object</toolTip>
<iconText>Create object</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectElliptical">
<icon></icon>
<text>Elliptical Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Elliptical Selection Tool</toolTip>
<iconText>Elliptical Selection Tool</iconText>
<shortcut>J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectContiguous">
<icon></icon>
<text>Contiguous Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Contiguous Selection Tool</toolTip>
<iconText>Contiguous Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonPatternTool">
<icon></icon>
<text>Pattern editing</text>
<whatsThis></whatsThis>
<toolTip>Pattern editing</toolTip>
<iconText>Pattern editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="ReviewTool">
<icon></icon>
<text>Review</text>
<whatsThis></whatsThis>
<toolTip>Review</toolTip>
<iconText>Review</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaFill/KisToolGradient">
<icon></icon>
<text>Draw a gradient.</text>
<whatsThis></whatsThis>
<toolTip>Draw a gradient.</toolTip>
<iconText>Draw a gradient.</iconText>
<shortcut>G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectPolygonal">
<icon></icon>
<text>Polygonal Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Polygonal Selection Tool</toolTip>
<iconText>Polygonal Selection Tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolMeasure">
<icon></icon>
<text>Measurement Tool</text>
<whatsThis></whatsThis>
<toolTip>Measure the distance between two points</toolTip>
<iconText>Measure the distance between two points</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolSelectRectangular">
<icon></icon>
<text>Rectangular Selection Tool</text>
<whatsThis></whatsThis>
<toolTip>Rectangular Selection Tool</toolTip>
<iconText>Rectangular Selection Tool</iconText>
<shortcut>Ctrl+R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaTransform/KisToolMove">
<icon></icon>
<text>Move Tool</text>
<whatsThis></whatsThis>
<toolTip>Move a layer</toolTip>
<iconText>Move a layer</iconText>
<shortcut>T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="VectorTool">
<icon></icon>
<text>Vector Image Tool</text>
<whatsThis></whatsThis>
<toolTip>Vector Image (EMF/WMF/SVM/SVG) tool</toolTip>
<iconText>Vector Image (EMF/WMF/SVM/SVG) tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonCalligraphyTool">
<icon></icon>
<text>Calligraphy</text>
<whatsThis></whatsThis>
<toolTip>Calligraphy</toolTip>
<iconText>Calligraphy</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="PathTool">
<icon></icon>
<text>Path editing</text>
<whatsThis></whatsThis>
<toolTip>Path editing</toolTip>
<iconText>Path editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolPolyline">
<icon></icon>
<text>Polyline Tool</text>
<whatsThis></whatsThis>
<toolTip>Polyline Tool. Shift-mouseclick ends the polyline.</toolTip>
<iconText>Polyline Tool. Shift-mouseclick ends the polyline.</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisToolTransform">
<icon></icon>
<text>Transform Tool</text>
<whatsThis></whatsThis>
<toolTip>Transform a layer or a selection</toolTip>
<iconText>Transform a layer or a selection</iconText>
<shortcut>Ctrl+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KisRulerAssistantTool">
<icon></icon>
<text>Ruler assistant editor tool</text>
<whatsThis></whatsThis>
<toolTip>Ruler assistant editor tool</toolTip>
<iconText>Ruler assistant editor tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KritaShape/KisToolText">
<icon></icon>
<text>Text tool</text>
<whatsThis></whatsThis>
<toolTip>Text tool</toolTip>
<iconText>Text tool</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="KarbonGradientTool">
<icon></icon>
<text>Gradient Editing Tool</text>
<whatsThis></whatsThis>
<toolTip>Gradient editing</toolTip>
<iconText>Gradient editing</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Blending Modes">
<text>Blending Modes</text>
<!-- commented out in the code right now
<Action name="Next Blending Mode">
<icon></icon>
<text>Next Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Next Blending Mode</toolTip>
<iconText>Next Blending Mode</iconText>
<shortcut>Alt+Shift++</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Previous Blending Mode">
<icon></icon>
<text>Previous Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Previous Blending Mode</toolTip>
<iconText>Previous Blending Mode</iconText>
<shortcut>Alt+Shift+-</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
-->
<Action name="Select Normal Blending Mode">
<icon></icon>
<text>Select Normal Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Normal Blending Mode</toolTip>
<iconText>Select Normal Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Dissolve Blending Mode">
<icon></icon>
<text>Select Dissolve Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Dissolve Blending Mode</toolTip>
<iconText>Select Dissolve Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Behind Blending Mode">
<icon></icon>
<text>Select Behind Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Behind Blending Mode</toolTip>
<iconText>Select Behind Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Clear Blending Mode">
<icon></icon>
<text>Select Clear Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Clear Blending Mode</toolTip>
<iconText>Select Clear Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+R</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Darken Blending Mode">
<icon></icon>
<text>Select Darken Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Darken Blending Mode</toolTip>
<iconText>Select Darken Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+K</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Multiply Blending Mode">
<icon></icon>
<text>Select Multiply Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Multiply Blending Mode</toolTip>
<iconText>Select Multiply Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Burn Blending Mode">
<icon></icon>
<text>Select Color Burn Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Burn Blending Mode</toolTip>
<iconText>Select Color Burn Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+B</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Burn Blending Mode">
<icon></icon>
<text>Select Linear Burn Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Burn Blending Mode</toolTip>
<iconText>Select Linear Burn Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Lighten Blending Mode">
<icon></icon>
<text>Select Lighten Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Lighten Blending Mode</toolTip>
<iconText>Select Lighten Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Screen Blending Mode">
<icon></icon>
<text>Select Screen Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Screen Blending Mode</toolTip>
<iconText>Select Screen Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Dodge Blending Mode">
<icon></icon>
<text>Select Color Dodge Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Dodge Blending Mode</toolTip>
<iconText>Select Color Dodge Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Dodge Blending Mode">
<icon></icon>
<text>Select Linear Dodge Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Dodge Blending Mode</toolTip>
<iconText>Select Linear Dodge Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Overlay Blending Mode">
<icon></icon>
<text>Select Overlay Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Overlay Blending Mode</toolTip>
<iconText>Select Overlay Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Soft Light Blending Mode">
<icon></icon>
<text>Select Soft Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Soft Light Blending Mode</toolTip>
<iconText>Select Soft Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Light Blending Mode">
<icon></icon>
<text>Select Hard Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Light Blending Mode</toolTip>
<iconText>Select Hard Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+H</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Vivid Light Blending Mode">
<icon></icon>
<text>Select Vivid Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Vivid Light Blending Mode</toolTip>
<iconText>Select Vivid Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Linear Light Blending Mode">
<icon></icon>
<text>Select Linear Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Linear Light Blending Mode</toolTip>
<iconText>Select Linear Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+J</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Pin Light Blending Mode">
<icon></icon>
<text>Select Pin Light Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Pin Light Blending Mode</toolTip>
<iconText>Select Pin Light Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hard Mix Blending Mode">
<icon></icon>
<text>Select Hard Mix Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hard Mix Blending Mode</toolTip>
<iconText>Select Hard Mix Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+L</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Difference Blending Mode">
<icon></icon>
<text>Select Difference Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Difference Blending Mode</toolTip>
<iconText>Select Difference Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Exclusion Blending Mode">
<icon></icon>
<text>Select Exclusion Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Exclusion Blending Mode</toolTip>
<iconText>Select Exclusion Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Hue Blending Mode">
<icon></icon>
<text>Select Hue Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Hue Blending Mode</toolTip>
<iconText>Select Hue Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+U</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Saturation Blending Mode">
<icon></icon>
<text>Select Saturation Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Saturation Blending Mode</toolTip>
<iconText>Select Saturation Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+T</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Color Blending Mode">
<icon></icon>
<text>Select Color Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Color Blending Mode</toolTip>
<iconText>Select Color Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Select Luminosity Blending Mode">
<icon></icon>
<text>Select Luminosity Blending Mode</text>
<whatsThis></whatsThis>
<toolTip>Select Luminosity Blending Mode</toolTip>
<iconText>Select Luminosity Blending Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Alt+Shift+Y</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Animation">
<text>Animation</text>
<Action name="previous_frame">
<icon></icon>
<text>Previous frame</text>
<whatsThis></whatsThis>
<toolTip>Move to previous frame</toolTip>
<iconText>Move to previous frame</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_frame">
<icon></icon>
<text>Next frame</text>
<whatsThis></whatsThis>
<toolTip>Move to next frame</toolTip>
<iconText>Move to next frame</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_playback">
<icon></icon>
<text>Play / pause animation</text>
<whatsThis></whatsThis>
<toolTip>Play / pause animation</toolTip>
<iconText>Play / pause animation</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_blank_frame">
<icon></icon>
<text>Add blank frame</text>
<whatsThis></whatsThis>
<toolTip>Add blank frame</toolTip>
<iconText>Add blank frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_duplicate_frame">
<icon></icon>
<text>Copy Frame</text>
<whatsThis></whatsThis>
<toolTip>Add duplicate frame</toolTip>
<iconText>Add duplicate frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_onion_skin">
<icon></icon>
<text>Toggle onion skin</text>
<whatsThis></whatsThis>
<toolTip>Toggle onion skin</toolTip>
<iconText>Toggle onion skin</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="previous_keyframe">
<icon></icon>
<text>Previous Keyframe</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="next_keyframe">
<icon></icon>
<text>Next Keyframe</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="first_frame">
<icon></icon>
<text>First Frame</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="last_frame">
<icon></icon>
<text>Last Frame</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="lazy_frame">
<icon></icon>
<text>Auto Frame Mode</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="drop_frames">
<icon></icon>
<text></text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_blank_frame">
<icon></icon>
<text>Add blank frame</text>
<whatsThis></whatsThis>
<toolTip>Add blank frame</toolTip>
<iconText>Add blank frame</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_in_timeline">
<icon></icon>
<text>Show in Timeline</text>
<whatsThis></whatsThis>
<toolTip></toolTip>
<iconText></iconText>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Layers">
<text>Layers</text>
<Action name="activateNextLayer">
<icon></icon>
<text>Activate next layer</text>
<whatsThis></whatsThis>
<toolTip>Activate next layer</toolTip>
<iconText>Activate next layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>PgUp</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="activatePreviousLayer">
<icon></icon>
<text>Activate previous layer</text>
<whatsThis></whatsThis>
<toolTip>Activate previous layer</toolTip>
<iconText>Activate previous layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>PgDown</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switchToPreviouslyActiveNode">
<icon></icon>
<text>Activate previously selected layer</text>
<whatsThis></whatsThis>
<toolTip>Activate previously selected layer</toolTip>
<iconText>Activate previously selected layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>;</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_group_layer">
<icon>groupLayer</icon>
<text>&amp;Group Layer</text>
<whatsThis></whatsThis>
<toolTip>Group Layer</toolTip>
<iconText>Group Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_clone_layer">
<icon>cloneLayer</icon>
<text>&amp;Clone Layer</text>
<whatsThis></whatsThis>
<toolTip>Clone Layer</toolTip>
<iconText>Clone Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_shape_layer">
<icon>vectorLayer</icon>
<text>&amp;Vector Layer</text>
<whatsThis></whatsThis>
<toolTip>Vector Layer</toolTip>
<iconText>Vector Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_adjustment_layer">
<icon>filterLayer</icon>
<text>&amp;Filter Layer...</text>
<whatsThis></whatsThis>
<toolTip>Filter Layer</toolTip>
<iconText>Filter Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_fill_layer">
<icon>fillLayer</icon>
<text>&amp;Fill Layer...</text>
<whatsThis></whatsThis>
<toolTip>Fill Layer</toolTip>
<iconText>Fill Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_file_layer">
<icon>fileLayer</icon>
<text>&amp;File Layer...</text>
<whatsThis></whatsThis>
<toolTip>File Layer</toolTip>
<iconText>File Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_transparency_mask">
<icon>transparencyMask</icon>
<text>&amp;Transparency Mask</text>
<whatsThis></whatsThis>
<toolTip>Transparency Mask</toolTip>
<iconText>Transparency Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_filter_mask">
<icon>filterMask</icon>
<text>&amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>Filter Mask</toolTip>
<iconText>Filter Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_colorize_mask">
<icon>filterMask</icon>
<text>&amp;Colorize Mask</text>
<whatsThis></whatsThis>
<toolTip>Colorize Mask</toolTip>
<iconText>Colorize Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_transform_mask">
<icon>transformMask</icon>
<text>&amp;Transform Mask...</text>
<whatsThis></whatsThis>
<toolTip>Transform Mask</toolTip>
<iconText>Transform Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="add_new_selection_mask">
<icon>selectionMask</icon>
<text>&amp;Local Selection</text>
<whatsThis></whatsThis>
<toolTip>Local Selection</toolTip>
<iconText>Local Selection</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="isolate_layer">
<icon>view-filter</icon>
<text>&amp;Isolate Layer</text>
<whatsThis></whatsThis>
<toolTip>Isolate Layer</toolTip>
<iconText>Isolate Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="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>10000</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>10000</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>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_quick_group">
<icon></icon>
<text>Quick Group</text>
<whatsThis></whatsThis>
<toolTip>Create a group layer containing selected layers</toolTip>
<iconText>Quick Group</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="quick_ungroup">
<icon></icon>
<text>Quick Ungroup</text>
<whatsThis></whatsThis>
<toolTip>Remove grouping of the layers or remove one layer out of the group</toolTip>
<iconText>Quick Ungroup</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_quick_clipping_group">
<icon></icon>
<text>Quick Clipping Group</text>
<whatsThis></whatsThis>
<toolTip>Group selected layers and add a layer with clipped alpha channel</toolTip>
<iconText>Quick Clipping Group</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+G</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_all_layers">
<icon></icon>
<text>All Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all layers</toolTip>
<iconText>Select all layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_visible_layers">
<icon></icon>
<text>Visible Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all visible layers</toolTip>
<iconText>Select all visible layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_locked_layers">
<icon></icon>
<text>Locked Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all locked layers</toolTip>
<iconText>Select all locked layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_invisible_layers">
<icon></icon>
<text>Invisible Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all invisible layers</toolTip>
<iconText>Select all invisible layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="select_unlocked_layers">
<icon></icon>
<text>Unlocked Layers</text>
<whatsThis></whatsThis>
<toolTip>Select all unlocked layers</toolTip>
<iconText>Select all unlocked layers</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_node_as_image">
<icon>document-save</icon>
<text>&amp;Save Layer/Mask...</text>
<whatsThis></whatsThis>
<toolTip>Save Layer/Mask</toolTip>
<iconText>Save Layer/Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_groups_as_images">
<icon>document-save</icon>
<text>Save &amp;Group Layers...</text>
<whatsThis></whatsThis>
<toolTip>Save Group Layers</toolTip>
<iconText>Save Group Layers</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_group_to_animated">
<icon></icon>
<text>Convert group to &amp;animated layer</text>
<whatsThis></whatsThis>
<toolTip>Convert child layers into animation frames</toolTip>
<iconText>Convert child layers into animation frames</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_from_file">
<icon></icon>
<text>I&amp;mport Layer...</text>
<whatsThis></whatsThis>
<toolTip>Import Layer</toolTip>
<iconText>Import Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_paint_layer">
<icon>paintLayer</icon>
<text>&amp;as Paint Layer...</text>
<whatsThis></whatsThis>
<toolTip>as Paint Layer</toolTip>
<iconText>as Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_transparency_mask">
<icon>transparencyMask</icon>
<text>as &amp;Transparency Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Transparency Mask</toolTip>
<iconText>as Transparency Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_filter_mask">
<icon>filterMask</icon>
<text>as &amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Filter Mask</toolTip>
<iconText>as Filter Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="import_layer_as_selection_mask">
<icon>selectionMask</icon>
<text>as &amp;Selection Mask...</text>
<whatsThis></whatsThis>
<toolTip>as Selection Mask</toolTip>
<iconText>as Selection Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_paint_layer">
<icon>paintLayer</icon>
<text>to &amp;Paint Layer</text>
<whatsThis></whatsThis>
<toolTip>to Paint Layer</toolTip>
<iconText>to Paint Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_transparency_mask">
<icon>transparencyMask</icon>
<text>to &amp;Transparency Mask</text>
<whatsThis></whatsThis>
<toolTip>to Transparency Mask</toolTip>
<iconText>to Transparency Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_filter_mask">
<icon>filterMask</icon>
<text>to &amp;Filter Mask...</text>
<whatsThis></whatsThis>
<toolTip>to Filter Mask</toolTip>
<iconText>to Filter Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_selection_mask">
<icon>selectionMask</icon>
<text>to &amp;Selection Mask</text>
<whatsThis></whatsThis>
<toolTip>to Selection Mask</toolTip>
<iconText>to Selection Mask</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_into_mask">
<icon>transparencyMask</icon>
<text>&amp;Alpha into Mask</text>
<whatsThis></whatsThis>
<toolTip>Alpha into Mask</toolTip>
<iconText>Alpha into Mask</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>10</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_write">
<icon>transparency-enabled</icon>
<text>&amp;Write as Alpha</text>
<whatsThis></whatsThis>
<toolTip>Write as Alpha</toolTip>
<iconText>Write as Alpha</iconText>
<activationFlags>1000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="split_alpha_save_merged">
<icon>document-save</icon>
<text>&amp;Save Merged...</text>
<whatsThis></whatsThis>
<toolTip>Save Merged</toolTip>
<iconText>Save Merged</iconText>
<activationFlags>1000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layersplit">
<icon>split-layer</icon>
<text>Split Layer...</text>
<whatsThis></whatsThis>
<toolTip>Split Layer</toolTip>
<iconText>Split Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="waveletdecompose">
<icon></icon>
<text>Wavelet Decompose ...</text>
<whatsThis></whatsThis>
<toolTip>Wavelet Decompose</toolTip>
<iconText>Wavelet Decompose</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorNodeX">
<icon>symmetry-horizontal</icon>
<text>Mirror Layer Hori&amp;zontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror Layer Horizontally</toolTip>
<iconText>Mirror Layer Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorNodeY">
<icon>symmetry-vertical</icon>
<text>Mirror Layer &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror Layer Vertically</toolTip>
<iconText>Mirror Layer Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotatelayer">
<icon></icon>
<text>&amp;Rotate Layer...</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer</toolTip>
<iconText>Rotate Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayerCW90">
<icon>object-rotate-right</icon>
<text>Rotate &amp;Layer 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 90° to the Right</toolTip>
<iconText>Rotate Layer 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayerCCW90">
<icon>object-rotate-left</icon>
<text>Rotate Layer &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 90° to the Left</toolTip>
<iconText>Rotate Layer 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateLayer180">
<icon></icon>
<text>Rotate Layer &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate Layer 180°</toolTip>
<iconText>Rotate Layer 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layersize">
<icon></icon>
<text>Scale &amp;Layer to new Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale Layer to new Size</toolTip>
<iconText>Scale Layer to new Size</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearlayer">
<icon></icon>
<text>&amp;Shear Layer...</text>
<whatsThis></whatsThis>
<toolTip>Shear Layer</toolTip>
<iconText>Shear Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="offsetlayer">
<icon></icon>
<text>&amp;Offset Layer...</text>
<whatsThis></whatsThis>
<toolTip>Offset Layer</toolTip>
<iconText>Offset Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="clones_array">
<icon></icon>
<text>Clones &amp;Array...</text>
<whatsThis></whatsThis>
<toolTip>Clones Array</toolTip>
<iconText>Clones Array</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="EditLayerMetaData">
<icon></icon>
<text>&amp;Edit metadata...</text>
<whatsThis></whatsThis>
<toolTip>Edit metadata</toolTip>
<iconText>Edit metadata</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="histogram">
<icon></icon>
<text>&amp;Histogram...</text>
<whatsThis></whatsThis>
<toolTip>Histogram</toolTip>
<iconText>Histogram</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layercolorspaceconversion">
<icon></icon>
<text>&amp;Convert Layer Color Space...</text>
<whatsThis></whatsThis>
<toolTip>Convert Layer Color Space</toolTip>
<iconText>Convert Layer Color Space</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="merge_layer">
<icon>merge-layer-below</icon>
<text>&amp;Merge with Layer Below</text>
<whatsThis></whatsThis>
<toolTip>Merge with Layer Below</toolTip>
<iconText>Merge with Layer Below</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="flatten_layer">
<icon></icon>
<text>&amp;Flatten Layer</text>
<whatsThis></whatsThis>
<toolTip>Flatten Layer</toolTip>
<iconText>Flatten Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rasterize_layer">
<icon></icon>
<text>Ras&amp;terize Layer</text>
<whatsThis></whatsThis>
<toolTip>Rasterize Layer</toolTip>
<iconText>Rasterize Layer</iconText>
<activationFlags>10000000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="flatten_image">
<icon></icon>
<text>Flatten ima&amp;ge</text>
<whatsThis></whatsThis>
<toolTip>Flatten image</toolTip>
<iconText>Flatten image</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+E</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layer_style">
<icon></icon>
<text>La&amp;yer Style...</text>
<whatsThis></whatsThis>
<toolTip>Layer Style</toolTip>
<iconText>Layer Style</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="LayerGroupSwitcher/previous">
<icon></icon>
<text>Move into previous group</text>
<whatsThis></whatsThis>
<toolTip>Move into previous group</toolTip>
<iconText>Move into previous group</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="LayerGroupSwitcher/next">
<icon></icon>
<text>Move into next group</text>
<whatsThis></whatsThis>
<toolTip>Move into next group</toolTip>
<iconText>Move into next group</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="RenameCurrentLayer">
<icon></icon>
<text>Rename current layer</text>
<whatsThis></whatsThis>
<toolTip>Rename current layer</toolTip>
<iconText>Rename current layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>F2</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="remove_layer">
<icon>deletelayer</icon>
<text>&amp;Remove Layer</text>
<whatsThis></whatsThis>
<toolTip>Remove Layer</toolTip>
<iconText>Remove Layer</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Shift+Delete</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="move_layer_up">
<icon>arrowupblr</icon>
<text>Move Layer or Mask Up</text>
<whatsThis></whatsThis>
<toolTip>Move Layer or Mask Up</toolTip>
<iconText></iconText>
<shortcut>Ctrl+PgUp</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="move_layer_down">
<icon>arrowdown</icon>
<text>Move Layer or Mask Down</text>
<whatsThis></whatsThis>
<toolTip>Move Layer or Mask Down</toolTip>
<iconText></iconText>
<shortcut>Ctrl+PgDown</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="layer_properties">
<icon>properties</icon>
<text>&amp;Properties...</text>
<whatsThis></whatsThis>
<toolTip>Properties</toolTip>
<iconText>Properties</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>F3</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
diff --git a/krita/krita.xmlgui b/krita/krita.xmlgui
index 13afe46a44..a05742d291 100644
--- a/krita/krita.xmlgui
+++ b/krita/krita.xmlgui
@@ -1,374 +1,376 @@
<?xml version="1.0"?>
<kpartgui xmlns="http://www.kde.org/standards/kxmlgui/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
name="Krita"
-version="103"
+version="110"
xsi:schemaLocation="http://www.kde.org/standards/kxmlgui/1.0 http://www.kde.org/standards/kxmlgui/1.0/kxmlgui.xsd">
<MenuBar>
<Menu name="file">
<text>&amp;File</text>
<Action name="file_new"/>
<Action name="file_open"/>
<Action name="file_open_recent"/>
<Separator/>
<Action name="file_save"/>
<Action name="file_save_as"/>
<Action name="file_reload_file"/>
<Separator/>
<Action name="file_import_file"/>
<Action name="file_export_file"/>
<Separator/>
<Action name="file_export_pdf"/>
<Separator/>
<Action name="file_import_animation"/>
<Action name="render_animation"/>
<Separator/>
<Action name="save_incremental_version"/>
<Action name="save_incremental_backup"/>
<Separator/>
<Action name="create_template"/>
<Action name="create_copy"/>
<!--Separator/>
<Action name="file_print"/>
<Action name="file_print_preview"/-->
<Separator/>
<Action name="file_documentinfo"/>
<Separator/>
<Action name="file_close"/>
<Action name="file_close_all"/>
<Action name="file_quit"/>
</Menu>
<Menu name="edit">
<text>&amp;Edit</text>
<Action name="edit_undo"/>
<Action name="edit_redo"/>
<Separator/>
<Action name="edit_cut"/>
<Action name="edit_copy"/>
<Action name="cut_sharp"/>
<Action name="copy_sharp"/>
<Action name="copy_merged"/>
<Action name="edit_paste"/>
+ <Action name="paste_at"/>
<Action name="paste_new"/>
<Action name="clear"/>
<Action name="fill_selection_foreground_color"/>
<Action name="fill_selection_background_color"/>
<Action name="fill_selection_pattern"/>
<Action name="stroke_shapes"/>
<Action name="stroke_selection"/>
<Action name="delete"/>
<Separator/>
<Action name="revert"/>
</Menu>
<Menu name="view">
<text>&amp;View</text>
<Action name="view_show_canvas_only"/>
<Action name="fullscreen"/>
<Action name="wrap_around_mode"/>
<Action name="level_of_detail_mode"/>
<Action name="softProof"/>
<Action name="gamutCheck"/>
<Separator/>
<Menu name="Canvas">
<text>&amp;Canvas</text>
<Action name="mirror_canvas"/>
<Separator/>
<Action name="zoom_to_100pct" />
<Action name="rotate_canvas_right" />
<Action name="rotate_canvas_left" />
<Action name="reset_canvas_rotation" />
<!-- TODO: Something is not right with the way zoom actions are hooked up. These are in the KoZoomController.
It seems they are not being properly placed in the view manager since the MDI changes were implemented
-->
<Action name="view_zoom_in"/>
<Action name="view_zoom_out"/>
</Menu>
<!-- TODO: None of these actions are showing. There names must have been changed to something with the MDI changes?...
<Action name="actual_pixels"/>
<Action name="actual_size"/>
<Action name="fit_to_canvas"/>
-->
<Separator/>
<Action name="view_ruler"/>
<Action name="rulers_track_mouse"/>
<Action name="view_show_guides"/>
<Action name="view_lock_guides"/>
<Action name="showStatusBar" />
<Separator/>
<Action name="view_grid"/>
<Separator/>
<Menu name="SnapTo">
<text>&amp;Snap To</text>
<Action name="view_snap_to_guides"/>
<Action name="view_snap_to_grid"/>
<Action name="view_snap_orthogonal" />
<Action name="view_snap_node" />
<Action name="view_snap_extension" />
<Action name="view_snap_intersection" />
<Action name="view_snap_bounding_box" />
<Action name="view_snap_image_bounds" />
<Action name="view_snap_image_center" />
</Menu>
<Separator/>
<Action name="view_toggle_painting_assistants"/>
<Action name="view_toggle_assistant_previews"/>
<Separator/>
<Action name="view_palette_action_menu"/>
<Separator/>
<Action name="refresh_canvas"/>
</Menu>
<Menu name="Image">
<text>&amp;Image</text>
<Action name="image_properties"/>
<Action name="image_color"/>
<Action name="imagecolorspaceconversion"/>
<Action name="duplicate_image"/>
<Separator/>
<Action name="trim_to_image"/>
<Action name="resizeimagetolayer"/>
<Action name="resizeimagetoselection"/>
<Separator/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotateimage"/>
<Separator/>
<Action name="rotateImageCW90"/>
<Action name="rotateImageCCW90"/>
<Action name="rotateImage180"/>
</Menu>
<Action name="shearimage"/>
<Separator/>
<Action name="mirrorImageHorizontal"/>
<Action name="mirrorImageVertical"/>
<Separator/>
<Action name="imagesize"/>
<Action name="offsetimage"/>
<Action name="imageresolution"/>
<Action name="canvassize"/>
<Separator/>
<Action name="imagesplit"/>
<Action name="waveletdecompose"/>
<Action name="separate"/>
</Menu>
<Menu name="Layer">
<text>&amp;Layer</text>
<Action name="cut_layer_clipboard"/>
<Action name="copy_layer_clipboard"/>
<Action name="paste_layer_from_clipboard"/>
<Separator/>
<Menu name="LayerNew">
<text>New</text>
<Action name="add_new_paint_layer"/>
<Action name="new_from_visible"/>
<Action name="duplicatelayer"/>
<Separator/>
<Action name="cut_selection_to_new_layer"/>
<Action name="copy_selection_to_new_layer"/>
</Menu>
<Menu name="LayerImportExport">
<text>&amp;Import/Export</text>
<Action name="save_node_as_image"/>
<Action name="save_groups_as_images"/>
<Separator/>
<Action name="import_layer_from_file"/>
<Menu name="LayerImportAs">
<text>Import</text>
<Action name="import_layer_as_paint_layer"/>
<Action name="import_layer_as_transparency_mask"/>
<Action name="import_layer_as_filter_mask"/>
<Action name="import_layer_as_selection_mask"/>
</Menu>
</Menu>
<Menu name="LayerConvert">
<text>&amp;Convert</text>
<Action name="convert_to_paint_layer"/>
<Action name="convert_to_transparency_mask"/>
<Action name="convert_to_filter_mask"/>
<Action name="convert_to_selection_mask"/>
<Action name="convert_group_to_animated"/>
<Action name="layercolorspaceconversion"/>
</Menu>
<Separator/>
<Menu name="LayerSelect">
<text>&amp;Select</text>
<Action name="select_all_layers"/>
<Action name="select_visible_layers"/>
<Action name="select_invisible_layers"/>
<Action name="select_locked_layers"/>
<Action name="select_unlocked_layers"/>
</Menu>
<Menu name="LayerGroup">
<text>&amp;Group</text>
<Action name="create_quick_group"/>
<Action name="create_quick_clipping_group"/>
<Action name="quick_ungroup"/>
</Menu>
<Menu name="LayerTransform">
<text>&amp;Transform</text>
<Action name="mirrorNodeX"/>
<Action name="mirrorNodeY"/>
<Action name="layersize"/>
<Menu name="Rotate">
<text>&amp;Rotate</text>
<Action name="rotatelayer"/>
<Separator/>
<Action name="rotateLayerCW90"/>
<Action name="rotateLayerCCW90"/>
<Action name="rotateLayer180"/>
</Menu>
<Action name="shearlayer"/>
<Action name="offsetlayer"/>
</Menu>
<Menu name="LayerSplitAlpha">
<text>S&amp;plit</text>
<Menu name="LayerSplitAlpha">
<text>S&amp;plit Alpha</text>
<Action name="split_alpha_into_mask"/>
<Action name="split_alpha_write"/>
<Action name="split_alpha_save_merged"/>
</Menu>
<Action name="layersplit"/>
<Action name="clones_array"/>
</Menu>
<Separator/>
<Action name="EditLayerMetaData"/>
<Action name="histogram"/>
<Separator/>
<Action name="merge_layer"/>
<Action name="flatten_layer"/>
<Action name="rasterize_layer"/>
<Action name="merge_all_shape_layers"/>
<Action name="flatten_image"/>
<Action name="merge_selected_layers"/>
<Separator/>
<Action name="layer_style"/>
</Menu>
<Menu name="Select">
<text>&amp;Select</text>
<Action name="select_all"/>
<Action name="deselect"/>
<Action name="reselect"/>
<Action name="invert"/>
<Action name="convert_to_vector_selection"/>
<Action name="convert_shapes_to_vector_selection"/>
<Action name="convert_selection_to_shape"/>
<Separator/>
<Action name="feather"/>
<Action name="similar"/>
<Separator/>
<Action name="toggle_display_selection"/>
<Action name="show-global-selection-mask"/>
<Action name="selectionscale"/>
<Separator/>
<Action name="colorrange"/>
<Action name="selectopaque"/>
<Separator/>
<Action name="featherselection"/>
<Action name="growselection"/>
<Action name="shrinkselection"/>
<Action name="borderselection"/>
<Action name="smoothselection"/>
</Menu>
<Menu name="Filter">
<text>Filte&amp;r</text>
<Action name="filter_apply_again"/>
<Action name="filter_gallery"/>
<Separator/>
<Action name="adjust_filters"/>
<Action name="artistic_filters"/>
<Action name="blur_filters"/>
<Action name="color_filters"/>
<Action name="decor_filters"/>
<Action name="edge_filters"/>
<Action name="enhance_filters"/>
<Action name="emboss_filters"/>
<Action name="map_filters"/>
<Action name="nonphotorealistic_filters"/>
<Action name="other_filters"/>
<Separator/>
<Action name="gmic"/>
</Menu>
<Menu name="tools">
<text>&amp;Tools</text>
+ <Action name="scripts"/>
<Menu name="Recording">
<text>Recording</text>
<Action name="Recording_Start_Recording_Macro"/>
<Action name="Recording_Stop_Recording_Macro"/>
</Menu>
<Menu name="Macros">
<text>Macros</text>
<Action name="Macro_Open_Play"/>
<Action name="Macro_Open_Edit"/>
</Menu>
</Menu>
<Menu name="settings">
<text>Setti&amp;ngs</text>
<Action name="options_configure"/>
<Action name="manage_bundles"/>
<Separator/>
<Action name="options_configure_toolbars"/>
<Merge name="StandardToolBarMenuHandler" />
<Separator/>
<Action name="view_toggledockers"/>
<Action name="view_toggledockertitlebars"/>
<Action name="settings_dockers_menu"/>
<Separator/>
<Action name="theme_menu"/>
<Separator/>
<!-- `Configure Shortcuts` was moved into main configuration menu -->
<!-- <Action name="options_configure_keybinding"/> -->
<Separator/>
<Action name="switch_application_language"/>
<Action name="settings_active_author"/>
<Separator/>
</Menu>
<Action name="window"/>
<Separator/>
<Menu name="help">
<text>&amp;Help</text>
<Action name="help_contents"/>
<Action name="help_whats_this"/>
<Separator/>
<MergeLocal/>
<Action name="help_show_tip"/>
<Separator/>
<Action name="help_report_bug"/>
<Separator/>
<Action name="help_about_app"/>
<Action name="help_about_kde"/>
</Menu>
</MenuBar>
<ToolBar name="mainToolBar" fullWidth="false" noMerge="1">
<Text>File</Text>
<Action name="file_new"/>
<Action name="file_open"/>
<Action name="file_save"/>
<!--Separator/>
<Action name="edit_undo"/>
<Action name="edit_redo"/-->
</ToolBar>
<ToolBar name="BrushesAndStuff" position="top">
<Text>Brushes and Stuff</Text>
<Action name="gradients"/>
<Action name="patterns"/>
<Separator/>
<Action name="dual"/>
<Separator/>
<Action name="paintops"/>
<Action name="paintop_options"/>
<Action name="composite_actions"/>
<Action name="brushslider1"/>
<Action name="brushslider2"/>
<Separator/>
<Action name="mirror_actions"/>
<Action name="expanding_spacer_1"/>
<Action name="workspaces"/>
</ToolBar>
</kpartgui>
diff --git a/krita/kritamenu.action b/krita/kritamenu.action
index 3ca097eb1f..85266fe675 100644
--- a/krita/kritamenu.action
+++ b/krita/kritamenu.action
@@ -1,1720 +1,1732 @@
<?xml version="1.0" encoding="UTF-8"?>
<ActionCollection version="2" name="Menu">
<Actions category="File">
<text>File</text>
<Action name="file_new">
<icon>document-new</icon>
<text>&amp;New</text>
<whatsThis></whatsThis>
<toolTip>Create new document</toolTip>
<iconText>New</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_open">
<icon>document-open</icon>
<text>&amp;Open...</text>
<whatsThis></whatsThis>
<toolTip>Open an existing document</toolTip>
<iconText>Open</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+O</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_open_recent">
<icon>document-open-recent</icon>
<text>Open &amp;Recent</text>
<whatsThis></whatsThis>
<toolTip>Open a document which was recently opened</toolTip>
<iconText>Open Recent</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_save">
<icon>document-save</icon>
<text>&amp;Save</text>
<whatsThis></whatsThis>
<toolTip>Save</toolTip>
<iconText>Save</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_save_as">
<icon>document-save-as</icon>
<text>Save &amp;As...</text>
<whatsThis></whatsThis>
<toolTip>Save document under a new name</toolTip>
<iconText>Save As</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<!-- commented out in the code right now
<Action name="file_reload_file">
<icon></icon>
<text>Reload</text>
<whatsThis></whatsThis>
<toolTip>Reload</toolTip>
<iconText>Reload</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
-->
<Action name="file_import_file">
<icon>document-import</icon>
<text>Open ex&amp;isting Document as Untitled Document...</text>
<whatsThis></whatsThis>
<toolTip>Open existing Document as Untitled Document</toolTip>
<iconText>Open existing Document as Untitled Document</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_export_file">
<icon>document-export</icon>
<text>E&amp;xport...</text>
<whatsThis></whatsThis>
<toolTip>Export</toolTip>
<iconText>Export</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_export_pdf">
<icon>application-pdf</icon>
<text>&amp;Export as PDF...</text>
<whatsThis></whatsThis>
<toolTip>Export as PDF</toolTip>
<iconText>Export as PDF</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_import_animation">
<icon></icon>
<text>Import animation frames...</text>
<whatsThis></whatsThis>
<toolTip>Import animation frames</toolTip>
<iconText>Import animation frames</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="render_animation">
<icon></icon>
<text>&amp;Render Animation...</text>
<whatsThis></whatsThis>
<toolTip>Render Animation to GIF, Image Sequence or Video</toolTip>
<iconText>Render Animation</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="render_image_sequence_again">
<icon></icon>
<text>&amp;Render Image Sequence Again</text>
<whatsThis></whatsThis>
<toolTip>Render Animation to Image Sequence Again</toolTip>
<iconText>Render Animation</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_incremental_version">
<icon></icon>
<text>Save Incremental &amp;Version</text>
<whatsThis></whatsThis>
<toolTip>Save Incremental Version</toolTip>
<iconText>Save Incremental Version</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+S</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="save_incremental_backup">
<icon></icon>
<text>Save Incremental &amp;Backup</text>
<whatsThis></whatsThis>
<toolTip>Save Incremental Backup</toolTip>
<iconText>Save Incremental Backup</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>F4</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_template">
<icon></icon>
<text>&amp;Create Template From Image...</text>
<whatsThis></whatsThis>
<toolTip>Create Template From Image</toolTip>
<iconText>Create Template From Image</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="create_copy">
<icon></icon>
<text>Create Copy &amp;From Current Image</text>
<whatsThis></whatsThis>
<toolTip>Create Copy From Current Image</toolTip>
<iconText>Create Copy From Current Image</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_print">
<icon>document-print</icon>
<text>&amp;Print...</text>
<whatsThis></whatsThis>
<toolTip>Print document</toolTip>
<iconText>Print</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+P</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_print_preview">
<icon>document-print-preview</icon>
<text>Print Previe&amp;w</text>
<whatsThis></whatsThis>
<toolTip>Show a print preview of document</toolTip>
<iconText>Print Preview</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_documentinfo">
<icon>configure</icon>
<text>&amp;Document Information</text>
<whatsThis></whatsThis>
<toolTip>Document Information</toolTip>
<iconText>Document Information</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_close_all">
<icon></icon>
<text>&amp;Close All</text>
<whatsThis></whatsThis>
<toolTip>Close All</toolTip>
<iconText>Close All</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+W</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_close">
<icon></icon>
<text>C&amp;lose</text>
<whatsThis></whatsThis>
<toolTip>Close</toolTip>
<iconText>Close</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="file_quit">
<icon></icon>
<text>&amp;Quit</text>
<whatsThis></whatsThis>
<toolTip>Quit application</toolTip>
<iconText>Quit</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Q</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Edit">
<text>Edit</text>
<Action name="edit_undo">
<icon>edit-undo</icon>
<text>Undo</text>
<whatsThis></whatsThis>
<toolTip>Undo last action</toolTip>
<iconText>Undo</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_redo">
<icon>edit-redo</icon>
<text>Redo</text>
<whatsThis></whatsThis>
<toolTip>Redo last undone action</toolTip>
<iconText>Redo</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+Z</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_cut">
<icon>edit-cut</icon>
<text>Cu&amp;t</text>
<whatsThis></whatsThis>
<toolTip>Cut selection to clipboard</toolTip>
<iconText>Cut</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+X</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_copy">
<icon>edit-copy</icon>
<text>&amp;Copy</text>
<whatsThis></whatsThis>
<toolTip>Copy selection to clipboard</toolTip>
<iconText>Copy</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_sharp">
<icon></icon>
<text>C&amp;opy (sharp)</text>
<whatsThis></whatsThis>
<toolTip>Copy (sharp)</toolTip>
<iconText>Copy (sharp)</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="cut_sharp">
<icon></icon>
<text>Cut (&amp;sharp)</text>
<whatsThis></whatsThis>
<toolTip>Cut (sharp)</toolTip>
<iconText>Cut (sharp)</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="copy_merged">
<icon></icon>
<text>Copy &amp;merged</text>
<whatsThis></whatsThis>
<toolTip>Copy merged</toolTip>
<iconText>Copy merged</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edit_paste">
<icon>edit-paste</icon>
<text>&amp;Paste</text>
<whatsThis></whatsThis>
<toolTip>Paste clipboard content</toolTip>
<iconText>Paste</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+V</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
+ <Action name="paste_at">
+ <icon></icon>
+ <text>Paste at Cursor</text>
+ <whatsThis></whatsThis>
+ <toolTip>Paste at cursor</toolTip>
+ <iconText>Paste at cursor</iconText>
+ <activationFlags>0</activationFlags>
+ <activationConditions>0</activationConditions>
+ <shortcut>Ctrl+Alt+V</shortcut>
+ <isCheckable>false</isCheckable>
+ <statusTip></statusTip>
+ </Action>
<Action name="paste_new">
<icon></icon>
<text>Paste into &amp;New Image</text>
<whatsThis></whatsThis>
<toolTip>Paste into New Image</toolTip>
<iconText>Paste into New Image</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+N</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="clear">
<icon>edit-clear</icon>
<text>C&amp;lear</text>
<whatsThis></whatsThis>
<toolTip>Clear</toolTip>
<iconText>Clear</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Del</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_foreground_color">
<icon></icon>
<text>&amp;Fill with Foreground Color</text>
<whatsThis></whatsThis>
<toolTip>Fill with Foreground Color</toolTip>
<iconText>Fill with Foreground Color</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Shift+Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_background_color">
<icon></icon>
<text>Fill &amp;with Background Color</text>
<whatsThis></whatsThis>
<toolTip>Fill with Background Color</toolTip>
<iconText>Fill with Background Color</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut>Backspace</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fill_selection_pattern">
<icon></icon>
<text>F&amp;ill with Pattern</text>
<whatsThis></whatsThis>
<toolTip>Fill with Pattern</toolTip>
<iconText>Fill with Pattern</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>1</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="stroke_shapes">
<icon></icon>
<text>Stro&amp;ke selected shapes</text>
<whatsThis></whatsThis>
<toolTip>Stroke selected shapes</toolTip>
<iconText>Stroke selected shapes</iconText>
<activationFlags>1000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="stroke_selection">
<icon></icon>
<text>Stroke Selec&amp;tion...</text>
<whatsThis></whatsThis>
<toolTip>Stroke selection</toolTip>
<iconText>Stroke selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="delete_keyframe">
<icon></icon>
<text>Delete keyframe</text>
<whatsThis></whatsThis>
<toolTip>Delete keyframe</toolTip>
<iconText>Delete keyframe</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Window">
<text>Window</text>
<Action name="view_newwindow">
<icon>window-new</icon>
<text>&amp;New Window</text>
<whatsThis></whatsThis>
<toolTip>New Window</toolTip>
<iconText>New Window</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_next">
<icon></icon>
<text>N&amp;ext</text>
<whatsThis></whatsThis>
<toolTip>Next</toolTip>
<iconText>Next</iconText>
<activationFlags>10</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="windows_previous">
<icon></icon>
<text>Previous</text>
<whatsThis></whatsThis>
<toolTip>Previous</toolTip>
<iconText>Previous</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="View">
<text>View</text>
<Action name="view_show_canvas_only">
<icon></icon>
<text>&amp;Show Canvas Only</text>
<whatsThis></whatsThis>
<toolTip>Show just the canvas or the whole window</toolTip>
<iconText>Show Canvas Only</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Tab</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="fullscreen">
<icon>view-fullscreen</icon>
<text>F&amp;ull Screen Mode</text>
<whatsThis></whatsThis>
<toolTip>Display the window in full screen</toolTip>
<iconText>Full Screen Mode</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+F</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="wrap_around_mode">
<icon></icon>
<text>&amp;Wrap Around Mode</text>
<whatsThis></whatsThis>
<toolTip>Wrap Around Mode</toolTip>
<iconText>Wrap Around Mode</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>W</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="level_of_detail_mode">
<icon></icon>
<text>&amp;Instant Preview Mode</text>
<whatsThis></whatsThis>
<toolTip>Instant Preview Mode</toolTip>
<iconText>Instant Preview Mode</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Shift+L</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="softProof">
<icon></icon>
<text>Soft Proofing</text>
<whatsThis></whatsThis>
<toolTip>Turns on Soft Proofing</toolTip>
<iconText>Turns on Soft Proofing</iconText>
<shortcut>Ctrl+Y</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="gamutCheck">
<icon></icon>
<text>Out of Gamut Warnings</text>
<whatsThis></whatsThis>
<toolTip>Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.</toolTip>
<iconText>Turns on warnings for colors out of proofed gamut, needs soft proofing to be turned on.</iconText>
<shortcut>Ctrl+Shift+Y</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_canvas">
<icon>mirror-view</icon>
<text>Mirror View</text>
<whatsThis></whatsThis>
<toolTip>Mirror View</toolTip>
<iconText>Mirror View</iconText>
<shortcut>M</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="zoom_to_100pct">
<icon>zoom-original</icon>
<text>&amp;Reset zoom</text>
<whatsThis></whatsThis>
<toolTip>Reset zoom</toolTip>
<iconText>Reset zoom</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+0</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_zoom_in">
<icon>zoom-in</icon>
<text>Zoom &amp;In</text>
<whatsThis></whatsThis>
<toolTip>Zoom In</toolTip>
<iconText></iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl++</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_zoom_out">
<icon>zoom-out</icon>
<text>Zoom &amp;Out</text>
<whatsThis></whatsThis>
<toolTip>Zoom Out</toolTip>
<iconText></iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+-</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotate_canvas_right">
<icon>rotate-canvas-right</icon>
<text>Rotate &amp;Canvas Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Canvas Right</toolTip>
<iconText>Rotate Canvas Right</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+]</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotate_canvas_left">
<icon>rotate-canvas-left</icon>
<text>Rotate Canvas &amp;Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Canvas Left</toolTip>
<iconText>Rotate Canvas Left</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+[</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reset_canvas_rotation">
<icon>rotation-reset</icon>
<text>Reset Canvas Rotation</text>
<whatsThis></whatsThis>
<toolTip>Reset Canvas Rotation</toolTip>
<iconText>Reset Canvas Rotation</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_ruler">
<icon></icon>
<text>Show &amp;Rulers</text>
<whatsThis>The rulers show the horizontal and vertical positions of the mouse on the image and can be used to position your mouse at the right place on the canvas. &lt;p>Uncheck this to hide the rulers.&lt;/p></whatsThis>
<toolTip>Show Rulers</toolTip>
<iconText>Show Rulers</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rulers_track_mouse">
<icon></icon>
<text>Rulers Track Pointer</text>
<whatsThis>The rulers will track current mouse position and show it on screen. It can cause suptle performance slowdown</whatsThis>
<toolTip>Rulers Track Pointer</toolTip>
<iconText>Rulers Track Pointer</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_show_guides">
<icon></icon>
<text>Show Guides</text>
<whatsThis></whatsThis>
<toolTip>Show or hide guides</toolTip>
<iconText>Show Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_lock_guides">
<icon></icon>
<text>Lock Guides</text>
<whatsThis></whatsThis>
<toolTip>Lock or unlock guides</toolTip>
<iconText>Lock Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_guides">
<icon></icon>
<text>Snap to Guides</text>
<whatsThis></whatsThis>
<toolTip>Snap cursor to guides position</toolTip>
<iconText>Snap to Guides</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="showStatusBar">
<icon></icon>
<text>Show Status &amp;Bar</text>
<whatsThis></whatsThis>
<toolTip>Show or hide the status bar</toolTip>
<iconText>Show Status Bar</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_grid">
<icon>view-grid</icon>
<text>Show &amp;Grid</text>
<whatsThis></whatsThis>
<toolTip>Show Grid</toolTip>
<iconText>Show Grid</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+'</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_to_grid">
<icon></icon>
<text>Snap To Grid</text>
<whatsThis></whatsThis>
<toolTip>Snap To Grid</toolTip>
<iconText>Snap To Grid</iconText>
<activationFlags>1000</activationFlags>
<shortcut>Ctrl+Shift+;</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="show_snap_options_popup">
<icon></icon>
<text>Show Snap Options Popup</text>
<whatsThis></whatsThis>
<toolTip>Show Snap Options Popup</toolTip>
<iconText>Show Snap Options Popup</iconText>
<activationFlags>1000</activationFlags>
<shortcut>Shift+s</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_orthogonal">
<icon></icon>
<text>Snap Orthogonal</text>
<whatsThis></whatsThis>
<toolTip>Snap Orthogonal</toolTip>
<iconText>Snap Orthogonal</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_node">
<icon></icon>
<text>Snap Node</text>
<whatsThis></whatsThis>
<toolTip>Snap Node</toolTip>
<iconText>Snap Node</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_extension">
<icon></icon>
<text>Snap Extension</text>
<whatsThis></whatsThis>
<toolTip>Snap Extension</toolTip>
<iconText>Snap Extension</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_intersection">
<icon></icon>
<text>Snap Intersection</text>
<whatsThis></whatsThis>
<toolTip>Snap Intersection</toolTip>
<iconText>Snap Intersection</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_bounding_box">
<icon></icon>
<text>Snap Bounding Box</text>
<whatsThis></whatsThis>
<toolTip>Snap Bounding Box</toolTip>
<iconText>Snap Bounding Box</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_image_bounds">
<icon></icon>
<text>Snap Image Bounds</text>
<whatsThis></whatsThis>
<toolTip>Snap Image Bounds</toolTip>
<iconText>Snap Image Bounds</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_snap_image_center">
<icon></icon>
<text>Snap Image Center</text>
<whatsThis></whatsThis>
<toolTip>Snap Image Center</toolTip>
<iconText>Snap Image Center</iconText>
<activationFlags>1000</activationFlags>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_painting_assistants">
<icon></icon>
<text>S&amp;how Painting Assistants</text>
<whatsThis></whatsThis>
<toolTip>Show Painting Assistants</toolTip>
<iconText>Show Painting Assistants</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggle_assistant_previews">
<icon></icon>
<text>Show &amp;Assistant Previews</text>
<whatsThis></whatsThis>
<toolTip>Show Assistant Previews</toolTip>
<iconText>Show Assistant Previews</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Image">
<text>Image</text>
<Action name="image_properties">
<icon>document-properties</icon>
<text>&amp;Properties...</text>
<whatsThis></whatsThis>
<toolTip>Properties</toolTip>
<iconText>Properties</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="image_color">
<icon>format-stroke-color</icon>
<text>&amp;Image Background Color and Transparency...</text>
<whatsThis></whatsThis>
<toolTip>Change the background color of the image</toolTip>
<iconText>Image Background Color and Transparency</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagecolorspaceconversion">
<icon></icon>
<text>&amp;Convert Image Color Space...</text>
<whatsThis></whatsThis>
<toolTip>Convert Image Color Space</toolTip>
<iconText>Convert Image Color Space</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="trim_to_image">
<icon>trim-to-image</icon>
<text>&amp;Trim to Image Size</text>
<whatsThis></whatsThis>
<toolTip>Trim to Image Size</toolTip>
<iconText>Trim to Image Size</iconText>
<activationFlags>1</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="resizeimagetolayer">
<icon></icon>
<text>Trim to Current &amp;Layer</text>
<whatsThis></whatsThis>
<toolTip>Trim to Current Layer</toolTip>
<iconText>Trim to Current Layer</iconText>
<activationFlags>100000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="resizeimagetoselection">
<icon></icon>
<text>Trim to S&amp;election</text>
<whatsThis></whatsThis>
<toolTip>Trim to Selection</toolTip>
<iconText>Trim to Selection</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateimage">
<icon></icon>
<text>&amp;Rotate Image...</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image</toolTip>
<iconText>Rotate Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImageCW90">
<icon>object-rotate-right</icon>
<text>Rotate &amp;Image 90° to the Right</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 90° to the Right</toolTip>
<iconText>Rotate Image 90° to the Right</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImageCCW90">
<icon>object-rotate-left</icon>
<text>Rotate Image &amp;90° to the Left</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 90° to the Left</toolTip>
<iconText>Rotate Image 90° to the Left</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="rotateImage180">
<icon></icon>
<text>Rotate Image &amp;180°</text>
<whatsThis></whatsThis>
<toolTip>Rotate Image 180°</toolTip>
<iconText>Rotate Image 180°</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shearimage">
<icon></icon>
<text>&amp;Shear Image...</text>
<whatsThis></whatsThis>
<toolTip>Shear Image</toolTip>
<iconText>Shear Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorImageHorizontal">
<icon>symmetry-horizontal</icon>
<text>&amp;Mirror Image Horizontally</text>
<whatsThis></whatsThis>
<toolTip>Mirror Image Horizontally</toolTip>
<iconText>Mirror Image Horizontally</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirrorImageVertical">
<icon>symmetry-vertical</icon>
<text>Mirror Image &amp;Vertically</text>
<whatsThis></whatsThis>
<toolTip>Mirror Image Vertically</toolTip>
<iconText>Mirror Image Vertically</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagesize">
<icon></icon>
<text>Scale Image To &amp;New Size...</text>
<whatsThis></whatsThis>
<toolTip>Scale Image To New Size</toolTip>
<iconText>Scale Image To New Size</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="offsetimage">
<icon></icon>
<text>&amp;Offset Image...</text>
<whatsThis></whatsThis>
<toolTip>Offset Image</toolTip>
<iconText>Offset Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="canvassize">
<icon></icon>
<text>R&amp;esize Canvas...</text>
<whatsThis></whatsThis>
<toolTip>Resize Canvas</toolTip>
<iconText>Resize Canvas</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Alt+C</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="imagesplit">
<icon></icon>
<text>Im&amp;age Split </text>
<whatsThis></whatsThis>
<toolTip>Image Split</toolTip>
<iconText>Image Split</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="separate">
<icon></icon>
<text>Separate Ima&amp;ge...</text>
<whatsThis></whatsThis>
<toolTip>Separate Image</toolTip>
<iconText>Separate Image</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Select">
<text>Select</text>
<Action name="select_all">
<icon>edit-select-all</icon>
<text>Select &amp;All</text>
<whatsThis></whatsThis>
<toolTip>Select All</toolTip>
<iconText>Select All</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="deselect">
<icon>edit-select-all</icon>
<text>&amp;Deselect</text>
<whatsThis></whatsThis>
<toolTip>Deselect</toolTip>
<iconText>Deselect</iconText>
<activationFlags>1100000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+A</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="reselect">
<icon></icon>
<text>&amp;Reselect</text>
<whatsThis></whatsThis>
<toolTip>Reselect</toolTip>
<iconText>Reselect</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+Shift+D</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="krita_filter_invert">
<icon></icon>
<text>&amp;Invert</text>
<whatsThis></whatsThis>
<toolTip>Invert</toolTip>
<iconText>Invert</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+I</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_to_vector_selection">
<icon></icon>
<text>&amp;Convert to Vector Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert to Vector Selection</toolTip>
<iconText>Convert to Vector Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="convert_shapes_to_vector_selection">
<icon></icon>
<text>Convert Shapes to &amp;Vector Selection</text>
<whatsThis></whatsThis>
<toolTip>Convert Shapes to Vector Selection</toolTip>
<iconText>Convert Shapes to Vector Selection</iconText>
<activationFlags>1000000000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="featherselection">
<icon></icon>
<text>&amp;Feather Selection...</text>
<whatsThis></whatsThis>
<toolTip>Feather Selection</toolTip>
<iconText>Feather Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut>Shift+F6</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="toggle_display_selection">
<icon></icon>
<text>Dis&amp;play Selection</text>
<whatsThis></whatsThis>
<toolTip>Display Selection</toolTip>
<iconText>Display Selection</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+H</shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectionscale">
<icon></icon>
<text>Sca&amp;le...</text>
<whatsThis></whatsThis>
<toolTip>Scale</toolTip>
<iconText>Scale</iconText>
<activationFlags>100000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="colorrange">
<icon></icon>
<text>S&amp;elect from Color Range...</text>
<whatsThis></whatsThis>
<toolTip>Select from Color Range</toolTip>
<iconText>Select from Color Range</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="selectopaque">
<icon></icon>
<text>Select &amp;Opaque</text>
<whatsThis></whatsThis>
<toolTip>Select Opaque</toolTip>
<iconText>Select Opaque</iconText>
<activationFlags>10000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="growselection">
<icon></icon>
<text>&amp;Grow Selection...</text>
<whatsThis></whatsThis>
<toolTip>Grow Selection</toolTip>
<iconText>Grow Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="shrinkselection">
<icon></icon>
<text>S&amp;hrink Selection...</text>
<whatsThis></whatsThis>
<toolTip>Shrink Selection</toolTip>
<iconText>Shrink Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="borderselection">
<icon></icon>
<text>&amp;Border Selection...</text>
<whatsThis></whatsThis>
<toolTip>Border Selection</toolTip>
<iconText>Border Selection</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="smoothselection">
<icon></icon>
<text>S&amp;mooth</text>
<whatsThis></whatsThis>
<toolTip>Smooth</toolTip>
<iconText>Smooth</iconText>
<activationFlags>10000000000</activationFlags>
<activationConditions>100</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Filter">
<text>Filter</text>
<Action name="filter_apply_again">
<icon></icon>
<text>&amp;Apply Filter Again</text>
<whatsThis></whatsThis>
<toolTip>Apply Filter Again</toolTip>
<iconText>Apply Filter Again</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut>Ctrl+F</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="adjust_filters">
<icon></icon>
<text>Adjust</text>
<whatsThis></whatsThis>
<toolTip>Adjust</toolTip>
<iconText>Adjust</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="artistic_filters">
<icon></icon>
<text>Artistic</text>
<whatsThis></whatsThis>
<toolTip>Artistic</toolTip>
<iconText>Artistic</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="blur_filters">
<icon></icon>
<text>Blur</text>
<whatsThis></whatsThis>
<toolTip>Blur</toolTip>
<iconText>Blur</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="color_filters">
<icon></icon>
<text>Colors</text>
<whatsThis></whatsThis>
<toolTip>Colors</toolTip>
<iconText>Colors</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="edge_filters">
<icon></icon>
<text>Edge Detection</text>
<whatsThis></whatsThis>
<toolTip>Edge Detection</toolTip>
<iconText>Edge Detection</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="enhance_filters">
<icon></icon>
<text>Enhance</text>
<whatsThis></whatsThis>
<toolTip>Enhance</toolTip>
<iconText>Enhance</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="emboss_filters">
<icon></icon>
<text>Emboss</text>
<whatsThis></whatsThis>
<toolTip>Emboss</toolTip>
<iconText>Emboss</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="map_filters">
<icon></icon>
<text>Map</text>
<whatsThis></whatsThis>
<toolTip>Map</toolTip>
<iconText>Map</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="other_filters">
<icon></icon>
<text>Other</text>
<whatsThis></whatsThis>
<toolTip>Other</toolTip>
<iconText>Other</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="gmic">
<icon>gmic</icon>
<text>G'MIC</text>
<whatsThis></whatsThis>
<toolTip>Apply G'Mic Action</toolTip>
<iconText>Apply G'Mic Action</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Tools">
<text>Tools</text>
<Action name="Recording_Start_Recording_Macro">
<icon>media-record</icon>
<text>&amp;Start recording macro</text>
<whatsThis></whatsThis>
<toolTip>Start recording macro</toolTip>
<iconText>Start recording macro</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Recording_Stop_Recording_Macro">
<icon>media-playback-stop</icon>
<text>Stop &amp;recording actions</text>
<whatsThis></whatsThis>
<toolTip>Stop recording actions</toolTip>
<iconText>Stop recording actions</iconText>
<activationFlags>1000</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Macro_Open_Play">
<icon>media-playback-start</icon>
<text>&amp;Open and play...</text>
<whatsThis></whatsThis>
<toolTip>Open and play</toolTip>
<iconText>Open and play</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="Macro_Open_Edit">
<icon>document-edit</icon>
<text>Open &amp;and edit...</text>
<whatsThis></whatsThis>
<toolTip>Open and edit</toolTip>
<iconText>Open and edit</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Settings">
<text>Settings</text>
<Action name="options_configure">
<icon>configure</icon>
<text>&amp;Configure Krita...</text>
<whatsThis></whatsThis>
<toolTip>Configure Krita</toolTip>
<iconText>Configure Krita</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="manage_bundles">
<icon></icon>
<text>&amp;Manage Resources...</text>
<whatsThis></whatsThis>
<toolTip>Manage Resources</toolTip>
<iconText>Manage Resources</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="switch_application_language">
<icon>preferences-desktop-locale</icon>
<text>Switch Application &amp;Language...</text>
<whatsThis></whatsThis>
<toolTip>Switch Application Language</toolTip>
<iconText>Switch Application Language</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggledockers">
<icon></icon>
<text>&amp;Show Dockers</text>
<whatsThis></whatsThis>
<toolTip>Show Dockers</toolTip>
<iconText>Show Dockers</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="view_toggledockertitlebars">
<icon></icon>
<text>Sho&amp;w Docker Titlebars</text>
<whatsThis></whatsThis>
<toolTip>Show Docker Titlebars</toolTip>
<iconText>Show Docker Titlebars</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>true</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="options_configure_toolbars">
<icon>configure</icon>
<text>Configure Tool&amp;bars...</text>
<whatsThis></whatsThis>
<toolTip>Configure Toolbars</toolTip>
<iconText>Configure Toolbars</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="settings_dockers_menu">
<icon></icon>
<text>Dockers</text>
<whatsThis></whatsThis>
<toolTip>Dockers</toolTip>
<iconText>Dockers</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="theme_menu">
<icon></icon>
<text>&amp;Themes</text>
<whatsThis></whatsThis>
<toolTip>Themes</toolTip>
<iconText>Themes</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="settings_active_author">
<icon>im-user</icon>
<text>Active Author Profile</text>
<whatsThis></whatsThis>
<toolTip>Active Author Profile</toolTip>
<iconText>Active Author Profile</iconText>
<shortcut></shortcut>
<isCheckable></isCheckable>
<statusTip></statusTip>
</Action>
<Action name="options_configure_keybinding">
<icon>configure-shortcuts</icon>
<text>Configure S&amp;hortcuts...</text>
<whatsThis></whatsThis>
<toolTip>Configure Shortcuts</toolTip>
<iconText>Configure Shortcuts</iconText>
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="window">
<icon></icon>
<text>&amp;Window</text>
<whatsThis></whatsThis>
<toolTip>Window</toolTip>
<iconText>Window</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="Help">
<text>Help</text>
<Action name="help_contents">
<icon>help-contents</icon>
<text>Krita &amp;Handbook</text>
<whatsThis></whatsThis>
<toolTip>Krita Handbook</toolTip>
<iconText>Krita Handbook</iconText>
<shortcut>F1</shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_report_bug">
<icon>tools-report-bug</icon>
<text>&amp;Report Bug...</text>
<whatsThis></whatsThis>
<toolTip>Report Bug</toolTip>
<iconText>Report Bug</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_about_app">
<icon>calligrakrita</icon>
<text>&amp;About Krita</text>
<whatsThis></whatsThis>
<toolTip>About Krita</toolTip>
<iconText>About Krita</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="help_about_kde">
<icon>kde</icon>
<text>About &amp;KDE</text>
<whatsThis></whatsThis>
<toolTip>About KDE</toolTip>
<iconText>About KDE</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
<Actions category="BrushesAndStuff">
<text>Brushes and Stuff</text>
<Action name="gradients">
<icon></icon>
<text>&amp;Gradients</text>
<whatsThis></whatsThis>
<toolTip>Gradients</toolTip>
<iconText>Gradients</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="patterns">
<icon></icon>
<text>&amp;Patterns</text>
<whatsThis></whatsThis>
<toolTip>Patterns</toolTip>
<iconText>Patterns</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="dual">
<icon></icon>
<text>&amp;Color</text>
<whatsThis></whatsThis>
<toolTip>Color</toolTip>
<iconText>Color</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="paintops">
<icon></icon>
<text>&amp;Painter's Tools</text>
<whatsThis></whatsThis>
<toolTip>Painter's Tools</toolTip>
<iconText>Painter's Tools</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="composite_actions">
<icon></icon>
<text>Brush composite</text>
<whatsThis></whatsThis>
<toolTip>Brush composite</toolTip>
<iconText>Brush composite</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider1">
<icon></icon>
<text>Brush option slider 1</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 1</toolTip>
<iconText>Brush option slider 1</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider2">
<icon></icon>
<text>Brush option slider 2</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 2</toolTip>
<iconText>Brush option slider 2</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="brushslider3">
<icon></icon>
<text>Brush option slider 3</text>
<whatsThis></whatsThis>
<toolTip>Brush option slider 3</toolTip>
<iconText>Brush option slider 3</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="mirror_actions">
<icon></icon>
<text>Mirror</text>
<whatsThis></whatsThis>
<toolTip>Mirror</toolTip>
<iconText>Mirror</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
<Action name="workspaces">
<icon></icon>
<text>Workspaces</text>
<whatsThis></whatsThis>
<toolTip>Workspaces</toolTip>
<iconText>Workspaces</iconText>
<shortcut></shortcut>
<isCheckable>false</isCheckable>
<statusTip></statusTip>
</Action>
</Actions>
</ActionCollection>
diff --git a/krita/pics/misc-dark/dark_distribute-horizontal-center.svg b/krita/pics/misc-dark/dark_distribute-horizontal-center.svg
new file mode 100644
index 0000000000..92fdeb45e7
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-horizontal-center.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-horizontal-center.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 6,0 3,2 6,4 V 0 m 4,0 V 4 L 13,2 10,0 M 0,5 v 7 h 3 v 4 H 4 V 12 H 7 V 5 m 2,0 v 9 h 3 v 2 h 1 v -2 h 3 V 5 M 1,6 h 5 v 5 H 1 V 6 m 9,0 h 5 v 7 H 10 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-horizontal-left.svg b/krita/pics/misc-dark/dark_distribute-horizontal-left.svg
new file mode 100644
index 0000000000..4e02557d00
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-horizontal-left.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-horizontal-left.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 4,0 1,2 4,4 V 0 M 6,0 V 4 L 9,2 6,0 M 0,3 v 3 6 4 H 1 V 12 H 7 V 5 H 1 V 3 H 0 m 9,0 v 3 8 2 h 1 v -2 h 6 V 5 H 10 V 3 H 9 M 1,6 h 5 v 5 H 1 V 6 m 9,0 h 5 v 7 H 10 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-horizontal-right.svg b/krita/pics/misc-dark/dark_distribute-horizontal-right.svg
new file mode 100644
index 0000000000..e266fc07c9
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-horizontal-right.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-horizontal-right.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-7.2245763"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 10,0 7,2 10,4 V 0 m 2,0 V 4 L 15,2 12,0 M 6,3 V 5 H 0 v 9 h 6 v 2 H 7 V 13 5 3 H 6 m 9,0 V 5 H 9 v 7 h 6 v 4 h 1 V 11 5 3 H 15 M 1,6 h 5 v 7 H 1 V 6 m 9,0 h 5 v 5 H 10 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-horizontal.svg b/krita/pics/misc-dark/dark_distribute-horizontal.svg
new file mode 100644
index 0000000000..11473f3a08
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-horizontal.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-horizontal.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="m 0,4 v 8 H 5 V 4 H 0 m 11,0 v 8 h 5 V 4 H 11 M 1,5 h 3 v 6 H 1 V 5 m 11,0 h 3 v 6 H 12 V 5 M 6,6 v 4 H 7 V 9 h 2 v 1 h 1 V 6 H 9 V 7 H 7 V 6 H 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-vertical-bottom.svg b/krita/pics/misc-dark/dark_distribute-vertical-bottom.svg
new file mode 100644
index 0000000000..19a0e5a051
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-vertical-bottom.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-vertical-bottom.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="m 16,10 -2,-3 -2,3 h 4 m 0,2 h -4 l 2,3 2,-3 M 13,6 H 11 V 0 H 2 V 6 H 0 v 1 h 3 8 2 V 6 m 0,9 H 11 V 9 H 4 v 6 H 0 v 1 h 5 6 2 V 15 M 10,1 V 6 H 3 V 1 h 7 m 0,9 v 5 H 5 v -5 h 5"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-vertical-center.svg b/krita/pics/misc-dark/dark_distribute-vertical-center.svg
new file mode 100644
index 0000000000..3421c096b0
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-vertical-center.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.1 r15371"
+ sodipodi:docname="dark_distribute-vertical-center.svg"
+ width="16"
+ height="16">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-23.817797"
+ inkscape:cy="10.254237"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 16,6 14,3 12,6 h 4 m 0,4 h -4 l 2,3 2,-3 M 11,0 H 4 V 3 H 0 v 1 h 4 v 3 h 7 m 0,2 H 2 v 3 H 0 v 1 h 2 v 3 h 9 M 10,1 V 6 H 5 V 1 h 5 m 0,9 v 5 H 3 v -5 h 7"
+ class="ColorScheme-Text"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-vertical-top.svg b/krita/pics/misc-dark/dark_distribute-vertical-top.svg
new file mode 100644
index 0000000000..488738f718
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-vertical-top.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-vertical-top.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-7.2245763"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 16,4 14,1 12,4 h 4 m 0,2 h -4 l 2,3 2,-3 M 13,0 H 10 4 0 v 1 h 4 v 6 h 7 V 1 h 2 V 0 m 0,9 H 10 2 0 v 1 h 2 v 6 h 9 v -6 h 2 V 9 M 10,1 V 6 H 5 V 1 h 5 m 0,9 v 5 H 3 v -5 h 7"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_distribute-vertical.svg b/krita/pics/misc-dark/dark_distribute-vertical.svg
new file mode 100644
index 0000000000..04d89eb9d3
--- /dev/null
+++ b/krita/pics/misc-dark/dark_distribute-vertical.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="dark_distribute-vertical.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 12,0 H 4 v 5 h 8 V 0 m 0,11 H 4 v 5 h 8 V 11 M 11,1 V 4 H 5 V 1 h 6 m 0,11 v 3 H 5 v -3 h 6 M 10,6 H 6 V 7 H 7 V 9 H 6 v 1 h 4 V 9 H 9 V 7 h 1 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_geometry.svg b/krita/pics/misc-dark/dark_geometry.svg
new file mode 100644
index 0000000000..50279a79bc
--- /dev/null
+++ b/krita/pics/misc-dark/dark_geometry.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_krita_geometry.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="6.3389831"
+ inkscape:cy="6.5254237"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#373737;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="M 0 0 L 0 2 L 2 2 L 2 0 L 0 0 z M 0 4 L 0 6 L 2 6 L 2 4 L 0 4 z M 0 8 L 0 10 L 2 10 L 2 8 L 0 8 z M 0 12 L 0 14 L 0 16 L 2 16 L 4 16 L 4 14 L 2 14 L 2 12 L 0 12 z M 6 14 L 6 16 L 8 16 L 8 14 L 6 14 z M 10 14 L 10 16 L 12 16 L 12 14 L 10 14 z M 14 14 L 14 16 L 16 16 L 16 14 L 14 14 z "
+ id="rect4508" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_path-break-point.svg b/krita/pics/misc-dark/dark_path-break-point.svg
new file mode 100644
index 0000000000..1841f2e4a2
--- /dev/null
+++ b/krita/pics/misc-dark/dark_path-break-point.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_path-break-point.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="-2.4936442"
+ inkscape:cy="7.690678"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 6,0 V 1 H 0 v 1 h 6 v 2 h 4 V 2 h 6 V 1 H 10 V 0 Z M 7,1 H 9 V 2 3 H 7 Z M 0,13 v 3 H 3 V 15 H 6 V 14 H 3 v -1 z m 13,0 v 1 h -3 v 1 h 3 v 1 h 3 V 13 Z M 1,14 h 1 v 1 H 1 Z m 13,0 h 1 v 1 h -1 z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_path-break-segment.svg b/krita/pics/misc-dark/dark_path-break-segment.svg
new file mode 100644
index 0000000000..3d3bdabd4c
--- /dev/null
+++ b/krita/pics/misc-dark/dark_path-break-segment.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_path-break-segment.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="15.170655"
+ inkscape:cx="-12.234179"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 0,0 V 3 H 3 V 2 h 3 4 3 v 1 h 3 V 0 H 13 V 1 H 10 6 3 V 0 Z M 1,1 H 2 V 2 H 1 Z m 13,0 h 1 V 2 H 14 Z M 0,13 v 3 H 3 V 15 H 6 V 14 H 3 v -1 z m 13,0 v 1 h -3 v 1 h 3 v 1 h 3 V 13 Z M 1,14 h 1 v 1 H 1 Z m 13,0 h 1 v 1 h -1 z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-corner.svg b/krita/pics/misc-dark/dark_pathpoint-corner.svg
new file mode 100644
index 0000000000..9356a14f42
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-corner.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-corner.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="-0.22015423"
+ inkscape:cy="-0.16058394"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4506">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ <style
+ id="current-color-scheme-6"
+ type="text/css">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <g
+ id="g4506"
+ transform="translate(-3,-3.5)">
+ <path
+ id="path4493"
+ class="ColorScheme-Text"
+ d="M 3.0019531,3.4960931 3,3.4980462 V 4.49414 L 3.00195,4.49609 C 6,4.5 8,12.5 8,15.5 l 1,-1 C 9,11.5 7,3.5 3.0019531,3.4960931 Z M 9,4.5 v 3 h 1 v 1 h 1 v -1 h 1 v -3 z m 9,-1 c 0,7 -4,11 -7,12 v 1 c 4,-1 8.000357,-6.000843 8,-13 z m -8,2 h 1 v 1 h -1 z m 0,4 v 1 h 1 v -1 z m 0,2 v 1 h 1 v -1 z m 6,4 v 1 h -1 v 1 h 1 v 1 h 3 v -3 z m -3,1 v 1 h 1 v -1 z m 4,0 h 1 v 1 h -1 z"
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <path
+ id="path4495"
+ class="ColorScheme-Highlight"
+ d="m 7,13.5 v 5 h 5 v -5 z m 2,2 h 1 v 1 H 9 Z"
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+ </g>
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-curve.svg b/krita/pics/misc-dark/dark_pathpoint-curve.svg
new file mode 100644
index 0000000000..89d6d58f5b
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-curve.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-curve.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="30.341309"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#373737;fill-opacity:1;stroke:none"
+ d="M 13,0 V 1 A 12,12 0 0 0 4.0742188,5 H 5 V 5.4707031 A 11,11 0 0 1 13,2 v 1 h 3 V 0 Z m 1,1 h 1 V 2 H 14 Z M 4,5.0859375 A 12,12 0 0 0 1,13 H 0 v 3 H 3 V 13 H 2 A 11,11 0 0 1 4.5214844,6 H 4 Z M 1,14 h 1 v 1 H 1 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2,3 V 8 H 7 V 3 Z M 4,5 H 5 V 6 H 4 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-insert.svg b/krita/pics/misc-dark/dark_pathpoint-insert.svg
new file mode 100644
index 0000000000..a4e5f7a4e8
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-insert.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-insert.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 6,5 V 6 H 0 v 1 h 6 v 2 h 4 V 7 h 6 V 6 H 10 V 5 Z M 7,6 H 9 V 8 H 7 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccc" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 12,10 v 2 h -2 v 2 h 2 v 2 h 2 v -2 h 2 v -2 h -2 v -2 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-join.svg b/krita/pics/misc-dark/dark_pathpoint-join.svg
new file mode 100644
index 0000000000..90bae446f9
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-join.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-join.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 0,0 V 3 H 3 V 2 H 6 V 1 H 3 V 0 Z m 13,0 v 1 h -3 v 1 h 3 v 1 h 3 V 0 Z M 1,1 H 2 V 2 H 1 Z m 13,0 h 1 V 2 H 14 Z M 0,13 v 3 h 3 v -1 h 3 4 3 v 1 h 3 v -3 h -3 v 1 H 10 6 3 v -1 z m 1,1 h 1 v 1 H 1 Z m 13,0 h 1 v 1 h -1 z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-line.svg b/krita/pics/misc-dark/dark_pathpoint-line.svg
new file mode 100644
index 0000000000..33100af899
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-line.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-line.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 13,0 V 1 2 2.3125 L 7.3125,8 H 8 V 8.6875 L 13.6875,3 H 16 V 0 Z m 1,1 h 1 V 2 H 14 Z M 7,8.3125 2.3125,13 H 2 1 0 v 3 H 3 V 13.6875 L 7.6875,9 H 7 Z M 1,14 h 1 v 1 H 1 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 v 5 h 5 V 6 Z M 7,8 H 8 V 9 H 7 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-merge.svg b/krita/pics/misc-dark/dark_pathpoint-merge.svg
new file mode 100644
index 0000000000..6d03d116ae
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-merge.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-merge.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 0,0 V 3 H 3 V 2 h 3 4 3 v 1 h 3 V 0 H 13 V 1 H 10 6 3 V 0 Z M 1,1 H 2 V 2 H 1 Z m 13,0 h 1 V 2 H 14 Z M 6,12 v 2 H 0 v 1 h 6 v 1 h 4 v -1 h 6 v -1 h -6 v -2 z m 1,1 h 2 v 1 1 H 7 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-remove.svg b/krita/pics/misc-dark/dark_pathpoint-remove.svg
new file mode 100644
index 0000000000..30ba2e6397
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-remove.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-remove.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4502">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ </style>
+ <style
+ id="current-color-scheme-5"
+ type="text/css">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <g
+ id="g4502"
+ transform="translate(-20)">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4-6"
+ class="ColorScheme-Text"
+ d="m 26,5 v 1 h -6 v 1 h 6 v 2 h 4 V 7 h 6 V 6 H 30 V 5 Z m 1,1 h 2 v 2 h -2 z"
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none" />
+ <path
+ sodipodi:nodetypes="ccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path6-2"
+ class="ColorScheme-Highlight"
+ d="m 30,11 2,2 -2,2 1,1 2,-2 2,2 1,-1 -2,-2 2,-2 -1,-1 -2,2 -2,-2 z"
+ style="color:#3daee9;fill:#da4453;fill-opacity:1;stroke:none" />
+ </g>
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-smooth.svg b/krita/pics/misc-dark/dark_pathpoint-smooth.svg
new file mode 100644
index 0000000000..77d45cd6e1
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-smooth.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-smooth.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.70268007"
+ inkscape:cy="11.461417"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 0,0 C 0,8 3,13 5,13 V 12 C 4,12 1,8 1,0 Z m 15,0 c 0,6 -4,12 -8,12 v 1 c 4,0 9,-6 9,-13 z M 0,12 v 3 H 3 V 14 H 4 V 13 H 3 v -1 z m 13,0 v 1 h -1 v 1 h 1 v 1 h 3 V 12 Z M 1,13 h 1 v 1 H 1 Z m 7,0 v 1 h 1 v -1 z m 2,0 v 1 h 1 v -1 z m 4,0 h 1 v 1 h -1 z"
+ id="path4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 3,11 v 5 h 5 v -5 z m 2,2 h 1 v 1 H 5 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathpoint-symmetric.svg b/krita/pics/misc-dark/dark_pathpoint-symmetric.svg
new file mode 100644
index 0000000000..8415e449cd
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathpoint-symmetric.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathpoint-symmetric.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="-0.36935383"
+ inkscape:cy="3.3208832"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4525">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ <style
+ id="current-color-scheme-6"
+ type="text/css">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <g
+ id="g4525"
+ transform="translate(-1,-2)">
+ <path
+ id="path4512"
+ class="ColorScheme-Text"
+ d="m 1,2 c 0,8 3,13 7,12.984375 V 13.980469 C 5,14 2,9 2,2 Z m 15,0 c 0,7 -3,12 -6,11.970703 V 15 c 4,0 7,-5 7,-13 z M 1,14 v 3 H 4 V 16 H 5 V 15 H 4 v -1 z m 13,0 v 1 h -1 v 1 h 1 v 1 h 3 V 14 Z M 2,15 h 1 v 1 H 2 Z m 4,0 v 1 h 1 v -1 z m 5,0 v 1 h 1 v -1 z m 4,0 h 1 v 1 h -1 z"
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <path
+ id="path4514"
+ class="ColorScheme-Highlight"
+ d="m 9,11 -4,7 h 8 z m 0,3 1,2 H 8 Z"
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccc" />
+ </g>
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathsegment-curve.svg b/krita/pics/misc-dark/dark_pathsegment-curve.svg
new file mode 100644
index 0000000000..7a325a8bcd
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathsegment-curve.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathsegment-curve.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 13,0 v 3 h 3 V 0 Z m 1,1 h 1 V 2 H 14 Z M 0,13 v 3 h 3 v -3 z m 1,1 h 1 v 1 H 1 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 13,1 A 12,12 0 0 0 1,13 H 2 A 11,11 0 0 1 13,2 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/dark_pathsegment-line.svg b/krita/pics/misc-dark/dark_pathsegment-line.svg
new file mode 100644
index 0000000000..c8583a0c01
--- /dev/null
+++ b/krita/pics/misc-dark/dark_pathsegment-line.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="dark_pathsegment-line.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-17.572033"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#3daee9;fill-opacity:1;stroke:none"
+ d="M 13.646484,1.6464844 1.6464844,13.646484 2.3535156,14.353516 14.353516,2.3535156 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:#373737;fill-opacity:1;stroke:none"
+ d="m 13,0 v 3 h 3 V 0 Z m 1,1 h 1 V 2 H 14 Z M 0,13 v 3 h 3 v -3 z m 1,1 h 1 v 1 H 1 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-dark/misc-dark-icons.qrc b/krita/pics/misc-dark/misc-dark-icons.qrc
index 9b1007a880..2bb29be62b 100644
--- a/krita/pics/misc-dark/misc-dark-icons.qrc
+++ b/krita/pics/misc-dark/misc-dark-icons.qrc
@@ -1,32 +1,55 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>dark_draw-eraser.svg</file>
+ <file>dark_geometry.svg</file>
<file alias="dark_object-align-horizontal-center-calligra.svg">dark_ox16-action-object-align-horizontal-center-calligra.svg</file>
<file alias="dark_object-align-horizontal-left-calligra.svg">dark_ox16-action-object-align-horizontal-left-calligra.svg</file>
<file alias="dark_object-align-horizontal-right-calligra.svg">dark_ox16-action-object-align-horizontal-right-calligra.svg</file>
<file alias="dark_object-align-vertical-bottom-calligra.svg">dark_ox16-action-object-align-vertical-bottom-calligra.svg</file>
<file alias="dark_object-align-vertical-center-calligra.svg">dark_ox16-action-object-align-vertical-center-calligra.svg</file>
<file alias="dark_object-align-vertical-top-calligra.svg">dark_ox16-action-object-align-vertical-top-calligra.svg</file>
<file alias="dark_object-order-back-calligra.svg">dark_ox16-action-object-order-back-calligra.svg</file>
<file alias="dark_object-order-front-calligra.svg">dark_ox16-action-object-order-front-calligra.svg</file>
<file alias="dark_object-order-lower-calligra.svg">dark_ox16-action-object-order-lower-calligra.svg</file>
<file alias="dark_object-order-raise-calligra.svg">dark_ox16-action-object-order-raise-calligra.svg</file>
<file alias="dark_object-group-calligra.svg">dark_ox16-action-object-group-calligra.svg</file>
<file alias="dark_object-ungroup-calligra.svg">dark_ox16-action-object-ungroup-calligra.svg</file>
+ <file>dark_distribute-horizontal-center.svg</file>
+ <file>dark_distribute-horizontal-left.svg</file>
+ <file>dark_distribute-horizontal-right.svg</file>
+ <file>dark_distribute-horizontal.svg</file>
+ <file>dark_distribute-vertical-bottom.svg</file>
+ <file>dark_distribute-vertical-center.svg</file>
+ <file>dark_distribute-vertical-top.svg</file>
+ <file>dark_distribute-vertical.svg</file>
<file>dark_paintop_settings_01.svg</file>
<file>dark_paintop_settings_02.svg</file>
<file>dark_pivot-point.svg</file>
<file>dark_stroke-cap-butt.svg</file>
<file>dark_stroke-cap-round.svg</file>
<file>dark_stroke-cap-square.svg</file>
<file>dark_stroke-join-bevel.svg</file>
<file>dark_stroke-join-miter.svg</file>
<file>dark_stroke-join-round.svg</file>
<file>dark_symmetry-horizontal.svg</file>
<file>dark_symmetry-vertical.svg</file>
<file>dark_onionOff.svg</file>
<file>dark_onionOn.svg</file>
<file>dark_onion_skin_options.svg</file>
+ <file>dark_onion_skin_options.svg</file>
+ <file>dark_path-break-point.svg</file>
+ <file>dark_path-break-segment.svg</file>
+ <file>dark_pathpoint-corner.svg</file>
+ <file>dark_pathpoint-curve.svg</file>
+ <file>dark_pathpoint-insert.svg</file>
+ <file>dark_pathpoint-join.svg</file>
+ <file>dark_pathpoint-line.svg</file>
+ <file>dark_pathpoint-merge.svg</file>
+ <file>dark_pathpoint-remove.svg</file>
+ <file>dark_pathpoint-smooth.svg</file>
+ <file>dark_pathpoint-symmetric.svg</file>
+ <file>dark_pathsegment-curve.svg</file>
+ <file>dark_pathsegment-line.svg</file>
</qresource>
</RCC>
diff --git a/krita/pics/misc-light/light_distribute-horizontal-center.svg b/krita/pics/misc-light/light_distribute-horizontal-center.svg
new file mode 100644
index 0000000000..38b4f3f6ed
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-horizontal-center.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-horizontal-center.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 6,0 3,2 6,4 V 0 m 4,0 V 4 L 13,2 10,0 M 0,5 v 7 h 3 v 4 H 4 V 12 H 7 V 5 m 2,0 v 9 h 3 v 2 h 1 v -2 h 3 V 5 M 1,6 h 5 v 5 H 1 V 6 m 9,0 h 5 v 7 H 10 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-horizontal-left.svg b/krita/pics/misc-light/light_distribute-horizontal-left.svg
new file mode 100644
index 0000000000..50f7a43a99
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-horizontal-left.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-horizontal-left.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 4,0 1,2 4,4 V 0 M 6,0 V 4 L 9,2 6,0 M 0,3 v 3 6 4 H 1 V 12 H 7 V 5 H 1 V 3 H 0 m 9,0 v 3 8 2 h 1 v -2 h 6 V 5 H 10 V 3 H 9 M 1,6 h 5 v 5 H 1 V 6 m 9,0 h 5 v 7 H 10 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-horizontal-right.svg b/krita/pics/misc-light/light_distribute-horizontal-right.svg
new file mode 100644
index 0000000000..e45c0928b7
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-horizontal-right.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-horizontal-right.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-7.2245763"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 10,0 7,2 10,4 V 0 m 2,0 V 4 L 15,2 12,0 M 6,3 V 5 H 0 v 9 h 6 v 2 H 7 V 13 5 3 H 6 m 9,0 V 5 H 9 v 7 h 6 v 4 h 1 V 11 5 3 H 15 M 1,6 h 5 v 7 H 1 V 6 m 9,0 h 5 v 5 H 10 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-horizontal.svg b/krita/pics/misc-light/light_distribute-horizontal.svg
new file mode 100644
index 0000000000..70067e2a2e
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-horizontal.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-horizontal.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="m 0,4 v 8 H 5 V 4 H 0 m 11,0 v 8 h 5 V 4 H 11 M 1,5 h 3 v 6 H 1 V 5 m 11,0 h 3 v 6 H 12 V 5 M 6,6 v 4 H 7 V 9 h 2 v 1 h 1 V 6 H 9 V 7 H 7 V 6 H 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-vertical-bottom.svg b/krita/pics/misc-light/light_distribute-vertical-bottom.svg
new file mode 100644
index 0000000000..c773700891
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-vertical-bottom.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-vertical-bottom.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="m 16,10 -2,-3 -2,3 h 4 m 0,2 h -4 l 2,3 2,-3 M 13,6 H 11 V 0 H 2 V 6 H 0 v 1 h 3 8 2 V 6 m 0,9 H 11 V 9 H 4 v 6 H 0 v 1 h 5 6 2 V 15 M 10,1 V 6 H 3 V 1 h 7 m 0,9 v 5 H 5 v -5 h 5"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-vertical-center.svg b/krita/pics/misc-light/light_distribute-vertical-center.svg
new file mode 100644
index 0000000000..63479ae124
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-vertical-center.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.1 r15371"
+ sodipodi:docname="light_distribute-vertical-center.svg"
+ width="16"
+ height="16">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-23.817797"
+ inkscape:cy="10.254237"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 16,6 14,3 12,6 h 4 m 0,4 h -4 l 2,3 2,-3 M 11,0 H 4 V 3 H 0 v 1 h 4 v 3 h 7 m 0,2 H 2 v 3 H 0 v 1 h 2 v 3 h 9 M 10,1 V 6 H 5 V 1 h 5 m 0,9 v 5 H 3 v -5 h 7"
+ class="ColorScheme-Text"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-vertical-top.svg b/krita/pics/misc-light/light_distribute-vertical-top.svg
new file mode 100644
index 0000000000..8b8b1b6013
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-vertical-top.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-vertical-top.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-7.2245763"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 16,4 14,1 12,4 h 4 m 0,2 h -4 l 2,3 2,-3 M 13,0 H 10 4 0 v 1 h 4 v 6 h 7 V 1 h 2 V 0 m 0,9 H 10 2 0 v 1 h 2 v 6 h 9 v -6 h 2 V 9 M 10,1 V 6 H 5 V 1 h 5 m 0,9 v 5 H 3 v -5 h 7"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_distribute-vertical.svg b/krita/pics/misc-light/light_distribute-vertical.svg
new file mode 100644
index 0000000000..aa6d514131
--- /dev/null
+++ b/krita/pics/misc-light/light_distribute-vertical.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg6"
+ sodipodi:docname="light_distribute-vertical.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview8"
+ showgrid="false"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg6" />
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#4d4d4d;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#4d4d4d;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 12,0 H 4 v 5 h 8 V 0 m 0,11 H 4 v 5 h 8 V 11 M 11,1 V 4 H 5 V 1 h 6 m 0,11 v 3 H 5 v -3 h 6 M 10,6 H 6 V 7 H 7 V 9 H 6 v 1 h 4 V 9 H 9 V 7 h 1 V 6"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_geometry.svg b/krita/pics/misc-light/light_geometry.svg
new file mode 100644
index 0000000000..dacc889913
--- /dev/null
+++ b/krita/pics/misc-light/light_geometry.svg
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_krita_geometry.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="6.3389831"
+ inkscape:cy="6.5254237"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#cacaca;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="M 0 0 L 0 2 L 2 2 L 2 0 L 0 0 z M 0 4 L 0 6 L 2 6 L 2 4 L 0 4 z M 0 8 L 0 10 L 2 10 L 2 8 L 0 8 z M 0 12 L 0 14 L 0 16 L 2 16 L 4 16 L 4 14 L 2 14 L 2 12 L 0 12 z M 6 14 L 6 16 L 8 16 L 8 14 L 6 14 z M 10 14 L 10 16 L 12 16 L 12 14 L 10 14 z M 14 14 L 14 16 L 16 16 L 16 14 L 14 14 z "
+ id="rect4508" />
+</svg>
diff --git a/krita/pics/misc-light/light_path-break-point.svg b/krita/pics/misc-light/light_path-break-point.svg
new file mode 100644
index 0000000000..26fbf78091
--- /dev/null
+++ b/krita/pics/misc-light/light_path-break-point.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_path-break-point.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="-2.4936442"
+ inkscape:cy="7.690678"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 6,0 V 1 H 0 v 1 h 6 v 2 h 4 V 2 h 6 V 1 H 10 V 0 Z M 7,1 H 9 V 2 3 H 7 Z M 0,13 v 3 H 3 V 15 H 6 V 14 H 3 v -1 z m 13,0 v 1 h -3 v 1 h 3 v 1 h 3 V 13 Z M 1,14 h 1 v 1 H 1 Z m 13,0 h 1 v 1 h -1 z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_path-break-segment.svg b/krita/pics/misc-light/light_path-break-segment.svg
new file mode 100644
index 0000000000..4f86888086
--- /dev/null
+++ b/krita/pics/misc-light/light_path-break-segment.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_path-break-segment.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="15.170655"
+ inkscape:cx="-12.234179"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 0,0 V 3 H 3 V 2 h 3 4 3 v 1 h 3 V 0 H 13 V 1 H 10 6 3 V 0 Z M 1,1 H 2 V 2 H 1 Z m 13,0 h 1 V 2 H 14 Z M 0,13 v 3 H 3 V 15 H 6 V 14 H 3 v -1 z m 13,0 v 1 h -3 v 1 h 3 v 1 h 3 V 13 Z M 1,14 h 1 v 1 H 1 Z m 13,0 h 1 v 1 h -1 z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-corner.svg b/krita/pics/misc-light/light_pathpoint-corner.svg
new file mode 100644
index 0000000000..6cd6f6611e
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-corner.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-corner.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="-0.22015423"
+ inkscape:cy="-0.16058394"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4506">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ <style
+ id="current-color-scheme-6"
+ type="text/css">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <g
+ id="g4506"
+ transform="translate(-3,-3.5)">
+ <path
+ id="path4493"
+ class="ColorScheme-Text"
+ d="M 3.0019531,3.4960931 3,3.4980462 V 4.49414 L 3.00195,4.49609 C 6,4.5 8,12.5 8,15.5 l 1,-1 C 9,11.5 7,3.5 3.0019531,3.4960931 Z M 9,4.5 v 3 h 1 v 1 h 1 v -1 h 1 v -3 z m 9,-1 c 0,7 -4,11 -7,12 v 1 c 4,-1 8.000357,-6.000843 8,-13 z m -8,2 h 1 v 1 h -1 z m 0,4 v 1 h 1 v -1 z m 0,2 v 1 h 1 v -1 z m 6,4 v 1 h -1 v 1 h 1 v 1 h 3 v -3 z m -3,1 v 1 h 1 v -1 z m 4,0 h 1 v 1 h -1 z"
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <path
+ id="path4495"
+ class="ColorScheme-Highlight"
+ d="m 7,13.5 v 5 h 5 v -5 z m 2,2 h 1 v 1 H 9 Z"
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none;stroke-width:1"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+ </g>
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-curve.svg b/krita/pics/misc-light/light_pathpoint-curve.svg
new file mode 100644
index 0000000000..26556bded9
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-curve.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-curve.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="30.341309"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 13,0 V 1 A 12,12 0 0 0 4.0742188,5 H 5 V 5.4707031 A 11,11 0 0 1 13,2 v 1 h 3 V 0 Z m 1,1 h 1 V 2 H 14 Z M 4,5.0859375 A 12,12 0 0 0 1,13 H 0 v 3 H 3 V 13 H 2 A 11,11 0 0 1 4.5214844,6 H 4 Z M 1,14 h 1 v 1 H 1 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2,3 V 8 H 7 V 3 Z M 4,5 H 5 V 6 H 4 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-insert.svg b/krita/pics/misc-light/light_pathpoint-insert.svg
new file mode 100644
index 0000000000..fc5c072e00
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-insert.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-insert.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 6,5 V 6 H 0 v 1 h 6 v 2 h 4 V 7 h 6 V 6 H 10 V 5 Z M 7,6 H 9 V 8 H 7 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccc" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 12,10 v 2 h -2 v 2 h 2 v 2 h 2 v -2 h 2 v -2 h -2 v -2 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-join.svg b/krita/pics/misc-light/light_pathpoint-join.svg
new file mode 100644
index 0000000000..6c55f04728
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-join.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-join.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 0,0 V 3 H 3 V 2 H 6 V 1 H 3 V 0 Z m 13,0 v 1 h -3 v 1 h 3 v 1 h 3 V 0 Z M 1,1 H 2 V 2 H 1 Z m 13,0 h 1 V 2 H 14 Z M 0,13 v 3 h 3 v -1 h 3 4 3 v 1 h 3 v -3 h -3 v 1 H 10 6 3 v -1 z m 1,1 h 1 v 1 H 1 Z m 13,0 h 1 v 1 h -1 z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-line.svg b/krita/pics/misc-light/light_pathpoint-line.svg
new file mode 100644
index 0000000000..f142a4657e
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-line.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-line.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 13,0 V 1 2 2.3125 L 7.3125,8 H 8 V 8.6875 L 13.6875,3 H 16 V 0 Z m 1,1 h 1 V 2 H 14 Z M 7,8.3125 2.3125,13 H 2 1 0 v 3 H 3 V 13.6875 L 7.6875,9 H 7 Z M 1,14 h 1 v 1 H 1 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 v 5 h 5 V 6 Z M 7,8 H 8 V 9 H 7 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-merge.svg b/krita/pics/misc-light/light_pathpoint-merge.svg
new file mode 100644
index 0000000000..d7358820a0
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-merge.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-merge.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 0,0 V 3 H 3 V 2 h 3 4 3 v 1 h 3 V 0 H 13 V 1 H 10 6 3 V 0 Z M 1,1 H 2 V 2 H 1 Z m 13,0 h 1 V 2 H 14 Z M 6,12 v 2 H 0 v 1 h 6 v 1 h 4 v -1 h 6 v -1 h -6 v -2 z m 1,1 h 2 v 1 1 H 7 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 5,6 3,5 3,-5 z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-remove.svg b/krita/pics/misc-light/light_pathpoint-remove.svg
new file mode 100644
index 0000000000..d386db5d63
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-remove.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-remove.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4502">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ </style>
+ <style
+ id="current-color-scheme-5"
+ type="text/css">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <g
+ id="g4502"
+ transform="translate(-20)">
+ <path
+ sodipodi:nodetypes="cccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4-6"
+ class="ColorScheme-Text"
+ d="m 26,5 v 1 h -6 v 1 h 6 v 2 h 4 V 7 h 6 V 6 H 30 V 5 Z m 1,1 h 2 v 2 h -2 z"
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none" />
+ <path
+ sodipodi:nodetypes="ccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path6-2"
+ class="ColorScheme-Highlight"
+ d="m 30,11 2,2 -2,2 1,1 2,-2 2,2 1,-1 -2,-2 2,-2 -1,-1 -2,2 -2,-2 z"
+ style="color:#3daee9;fill:#da4453;fill-opacity:1;stroke:none" />
+ </g>
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-smooth.svg b/krita/pics/misc-light/light_pathpoint-smooth.svg
new file mode 100644
index 0000000000..156354de80
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-smooth.svg
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-smooth.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="0.70268007"
+ inkscape:cy="11.461417"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="M 0,0 C 0,8 3,13 5,13 V 12 C 4,12 1,8 1,0 Z m 15,0 c 0,6 -4,12 -8,12 v 1 c 4,0 9,-6 9,-13 z M 0,12 v 3 H 3 V 14 H 4 V 13 H 3 v -1 z m 13,0 v 1 h -1 v 1 h 1 v 1 h 3 V 12 Z M 1,13 h 1 v 1 H 1 Z m 7,0 v 1 h 1 v -1 z m 2,0 v 1 h 1 v -1 z m 4,0 h 1 v 1 h -1 z"
+ id="path4"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="m 3,11 v 5 h 5 v -5 z m 2,2 h 1 v 1 H 5 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccc" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathpoint-symmetric.svg b/krita/pics/misc-light/light_pathpoint-symmetric.svg
new file mode 100644
index 0000000000..39af7ab31f
--- /dev/null
+++ b/krita/pics/misc-light/light_pathpoint-symmetric.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathpoint-symmetric.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="21.454545"
+ inkscape:cx="-0.36935383"
+ inkscape:cy="3.3208832"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g4525">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ <style
+ id="current-color-scheme-6"
+ type="text/css">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <g
+ id="g4525"
+ transform="translate(-1,-2)">
+ <path
+ id="path4512"
+ class="ColorScheme-Text"
+ d="m 1,2 c 0,8 3,13 7,12.984375 V 13.980469 C 5,14 2,9 2,2 Z m 15,0 c 0,7 -3,12 -6,11.970703 V 15 c 4,0 7,-5 7,-13 z M 1,14 v 3 H 4 V 16 H 5 V 15 H 4 v -1 z m 13,0 v 1 h -1 v 1 h 1 v 1 h 3 V 14 Z M 2,15 h 1 v 1 H 2 Z m 4,0 v 1 h 1 v -1 z m 5,0 v 1 h 1 v -1 z m 4,0 h 1 v 1 h -1 z"
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccc" />
+ <path
+ id="path4514"
+ class="ColorScheme-Highlight"
+ d="m 9,11 -4,7 h 8 z m 0,3 1,2 H 8 Z"
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cccccccc" />
+ </g>
+</svg>
diff --git a/krita/pics/misc-light/light_pathsegment-curve.svg b/krita/pics/misc-light/light_pathsegment-curve.svg
new file mode 100644
index 0000000000..1307a6de60
--- /dev/null
+++ b/krita/pics/misc-light/light_pathsegment-curve.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathsegment-curve.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="0.65254237"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="m 13,0 v 3 h 3 V 0 Z m 1,1 h 1 V 2 H 14 Z M 0,13 v 3 h 3 v -3 z m 1,1 h 1 v 1 H 1 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 13,1 A 12,12 0 0 0 1,13 H 2 A 11,11 0 0 1 13,2 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/light_pathsegment-line.svg b/krita/pics/misc-light/light_pathsegment-line.svg
new file mode 100644
index 0000000000..7993c40291
--- /dev/null
+++ b/krita/pics/misc-light/light_pathsegment-line.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 16 16"
+ version="1.1"
+ id="svg8"
+ sodipodi:docname="light_pathsegment-line.svg"
+ width="16"
+ height="16"
+ inkscape:version="0.92.1 r15371">
+ <metadata
+ id="metadata12">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1884"
+ inkscape:window-height="1051"
+ id="namedview10"
+ showgrid="true"
+ inkscape:zoom="10.727273"
+ inkscape:cx="-17.572033"
+ inkscape:cy="11"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg8">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4489" />
+ </sodipodi:namedview>
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#373737;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="color:#373737;fill:#3daee9;fill-opacity:1;stroke:none"
+ d="M 13.646484,1.6464844 1.6464844,13.646484 2.3535156,14.353516 14.353516,2.3535156 Z"
+ class="ColorScheme-Text"
+ id="path4"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#3daee9;fill:#cacaca;fill-opacity:1;stroke:none"
+ d="m 13,0 v 3 h 3 V 0 Z m 1,1 h 1 V 2 H 14 Z M 0,13 v 3 h 3 v -3 z m 1,1 h 1 v 1 H 1 Z"
+ class="ColorScheme-Highlight"
+ id="path6"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/krita/pics/misc-light/misc-light-icons.qrc b/krita/pics/misc-light/misc-light-icons.qrc
index 58f156a5da..5905136ca1 100644
--- a/krita/pics/misc-light/misc-light-icons.qrc
+++ b/krita/pics/misc-light/misc-light-icons.qrc
@@ -1,32 +1,54 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>light_draw-eraser.svg</file>
+ <file>light_geometry.svg</file>
<file alias="light_object-align-horizontal-center-calligra.svg">light_ox16-action-object-align-horizontal-center-calligra.svg</file>
<file alias="light_object-align-horizontal-left-calligra.svg">light_ox16-action-object-align-horizontal-left-calligra.svg</file>
<file alias="light_object-align-horizontal-right-calligra.svg">light_ox16-action-object-align-horizontal-right-calligra.svg</file>
<file alias="light_object-align-vertical-bottom-calligra.svg">light_ox16-action-object-align-vertical-bottom-calligra.svg</file>
<file alias="light_object-align-vertical-center-calligra.svg">light_ox16-action-object-align-vertical-center-calligra.svg</file>
<file alias="light_object-align-vertical-top-calligra.svg">light_ox16-action-object-align-vertical-top-calligra.svg</file>
<file alias="light_object-order-back-calligra.svg">light_ox16-action-object-order-back-calligra.svg</file>
<file alias="light_object-order-front-calligra.svg">light_ox16-action-object-order-front-calligra.svg</file>
<file alias="light_object-order-lower-calligra.svg">light_ox16-action-object-order-lower-calligra.svg</file>
<file alias="light_object-order-raise-calligra.svg">light_ox16-action-object-order-raise-calligra.svg</file>
<file alias="light_object-group-calligra.svg">light_ox16-action-object-group-calligra.svg</file>
<file alias="light_object-ungroup-calligra.svg">light_ox16-action-object-ungroup-calligra.svg</file>
+ <file>light_distribute-horizontal-center.svg</file>
+ <file>light_distribute-horizontal-left.svg</file>
+ <file>light_distribute-horizontal-right.svg</file>
+ <file>light_distribute-horizontal.svg</file>
+ <file>light_distribute-vertical-bottom.svg</file>
+ <file>light_distribute-vertical-center.svg</file>
+ <file>light_distribute-vertical-top.svg</file>
+ <file>light_distribute-vertical.svg</file>
<file>light_paintop_settings_01.svg</file>
<file>light_paintop_settings_02.svg</file>
<file>light_pivot-point.svg</file>
<file>light_stroke-cap-butt.svg</file>
<file>light_stroke-cap-round.svg</file>
<file>light_stroke-cap-square.svg</file>
<file>light_stroke-join-bevel.svg</file>
<file>light_stroke-join-miter.svg</file>
<file>light_stroke-join-round.svg</file>
<file>light_symmetry-horizontal.svg</file>
<file>light_symmetry-vertical.svg</file>
<file>light_onionOff.svg</file>
<file>light_onionOn.svg</file>
<file>light_onion_skin_options.svg</file>
+ <file>light_path-break-point.svg</file>
+ <file>light_path-break-segment.svg</file>
+ <file>light_pathpoint-corner.svg</file>
+ <file>light_pathpoint-curve.svg</file>
+ <file>light_pathpoint-insert.svg</file>
+ <file>light_pathpoint-join.svg</file>
+ <file>light_pathpoint-line.svg</file>
+ <file>light_pathpoint-merge.svg</file>
+ <file>light_pathpoint-remove.svg</file>
+ <file>light_pathpoint-smooth.svg</file>
+ <file>light_pathpoint-symmetric.svg</file>
+ <file>light_pathsegment-curve.svg</file>
+ <file>light_pathsegment-line.svg</file>
</qresource>
</RCC>
diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt
index 3f8c7dd921..a92a04f737 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -1,18 +1,19 @@
add_subdirectory( version )
add_subdirectory( global )
add_subdirectory( koplugin )
add_subdirectory( widgetutils )
add_subdirectory( widgets )
add_subdirectory( store )
add_subdirectory( odf )
add_subdirectory( flake )
add_subdirectory( basicflakes )
add_subdirectory( pigment )
-add_subdirectory( kundo2 )
+add_subdirectory( command )
add_subdirectory( brush )
add_subdirectory( psd )
add_subdirectory( color )
add_subdirectory( image )
add_subdirectory( ui )
add_subdirectory( vectorimage )
add_subdirectory( impex )
+add_subdirectory( libkis )
diff --git a/libs/basicflakes/CMakeLists.txt b/libs/basicflakes/CMakeLists.txt
index 84ec048849..edf42265a1 100644
--- a/libs/basicflakes/CMakeLists.txt
+++ b/libs/basicflakes/CMakeLists.txt
@@ -1,37 +1,38 @@
add_subdirectory(plugin)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/tools
${CMAKE_CURRENT_SOURCE_DIR}/plugin
)
set(kritabasicflakes_LIB_SRCS
tools/KoCreatePathTool.cpp
tools/KoPencilTool.cpp
)
ki18n_wrap_ui( kritabasicflakes_LIB_SRCS
)
add_library(kritabasicflakes SHARED ${kritabasicflakes_LIB_SRCS})
generate_export_header(kritabasicflakes)
target_include_directories(kritabasicflakes
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tools>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/plugin>
)
target_link_libraries(kritabasicflakes
PUBLIC
- kritawidgets
+ kritaui
+ kritawidgets
kritaflake
kritapigment
)
set_target_properties(kritabasicflakes PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritabasicflakes ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/basicflakes/tools/KoCreatePathTool.cpp b/libs/basicflakes/tools/KoCreatePathTool.cpp
index c0355d2ecf..fa8027221f 100644
--- a/libs/basicflakes/tools/KoCreatePathTool.cpp
+++ b/libs/basicflakes/tools/KoCreatePathTool.cpp
@@ -1,533 +1,500 @@
/* 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 "KoStrokeConfigWidget.h"
#include "KoCanvasBase.h"
#include "kis_int_parse_spin_box.h"
#include <KoColor.h>
+#include "kis_canvas_resource_provider.h"
+#include <KisHandlePainterHelper.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()) {
- KoShapeStroke *stroke(createStroke());
-
- if (stroke) {
- d->shape->setStroke(stroke);
- }
-
painter.save();
paintPath(*(d->shape), painter, converter);
painter.restore();
- painter.save();
-
- painter.setTransform(d->shape->absoluteTransformation(&converter) * painter.transform());
-
- KoShape::applyConversion(painter, converter);
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&painter, d->shape, converter, d->handleRadius);
- QPen pen(QBrush(Qt::blue), 1);
- pen.setCosmetic(true);
- painter.setPen(pen);
- painter.setBrush(Qt::white);
+ const bool firstPointActive = d->firstPoint == d->activePoint;
- const bool firstPoint = (d->firstPoint == d->activePoint);
-
- if (d->pointIsDragged || firstPoint) {
+ if (d->pointIsDragged || firstPointActive) {
const bool onlyPaintActivePoints = false;
KoPathPoint::PointTypes paintFlags = KoPathPoint::ControlPoint2;
if (d->activePoint->activeControlPoint1()) {
paintFlags |= KoPathPoint::ControlPoint1;
}
- d->activePoint->paint(painter, d->handleRadius, paintFlags, onlyPaintActivePoints);
- }
-
- // check if we have to color the first point
- if (d->mouseOverFirstPoint) {
- painter.setBrush(Qt::red);
- } else {
- painter.setBrush(Qt::white);
+ helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
+ d->activePoint->paint(helper, paintFlags, onlyPaintActivePoints);
}
- d->firstPoint->paint(painter, d->handleRadius, KoPathPoint::Node);
-
- painter.restore();
+ if (!firstPointActive) {
+ helper.setHandleStyle(d->mouseOverFirstPoint ?
+ KisHandleStyle::highlightedPrimaryHandles() :
+ KisHandleStyle::primarySelection());
+ d->firstPoint->paint(helper, KoPathPoint::Node);
+ }
}
if (d->hoveredPoint) {
- painter.save();
- painter.setTransform(d->hoveredPoint->parent()->absoluteTransformation(&converter), true);
- KoShape::applyConversion(painter, converter);
- QPen pen(QBrush(Qt::blue), 1);
- pen.setCosmetic(true);
- painter.setPen(pen);
- painter.setBrush(Qt::white);
- d->hoveredPoint->paint(painter, d->handleRadius, KoPathPoint::Node);
- painter.restore();
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, d->hoveredPoint->parent(), converter, d->handleRadius);
+ helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
+ d->hoveredPoint->paint(helper, KoPathPoint::Node);
}
painter.save();
KoShape::applyConversion(painter, converter);
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.save();
KoShapePaintingContext paintContext; //FIXME
pathShape.paint(painter, converter, paintContext);
painter.restore();
if (pathShape.stroke()) {
painter.save();
pathShape.stroke()->paint(d->shape, painter, converter);
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);
bool haveCloseModifier = (listeningToModifiers() && (event->modifiers() & Qt::ShiftModifier));
if ((event->button() == Qt::LeftButton) && haveCloseModifier && !isOverFirstPoint) {
endPathWithoutLastPoint();
return;
}
d->finishAfterThisPoint = false;
if (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);
- KoShapeStroke *stroke = new KoShapeStroke(canvas()->resourceManager()->activeStroke());
+ 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()->setEditedShape(pathShape);
+ canvas()->snapGuide()->setAdditionalEditedShape(pathShape);
d->angleSnapStrategy = new AngleSnapStrategy(d->angleSnappingDelta, d->angleSnapStatus);
canvas()->snapGuide()->addCustomSnapStrategy(d->angleSnapStrategy);
}
if (d->angleSnapStrategy)
d->angleSnapStrategy->setStartPoint(d->activePoint->point());
}
bool KoCreatePathTool::listeningToModifiers()
{
Q_D(KoCreatePathTool);
return d->listeningToModifiers;
}
bool KoCreatePathTool::pathStarted()
{
Q_D(KoCreatePathTool);
return ((bool) d->shape);
}
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) {
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);
}
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->listeningToModifiers = true; // After the first press-and-release
d->repaintActivePoint();
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;
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, const QSet<KoShape*> &)
+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();
// 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;
}
}
void KoCreatePathTool::addPathShape(KoPathShape *pathShape)
{
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());
- pathShape->setStroke(createStroke());
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;
}
}
KUndo2Command *cmd = canvas()->shapeController()->addShape(pathShape);
if (cmd) {
KoSelection *selection = canvas()->shapeManager()->selection();
selection->deselectAll();
selection->select(pathShape);
if (startShape) {
canvas()->shapeController()->removeShape(startShape, cmd);
}
if (endShape && startShape != endShape) {
canvas()->shapeController()->removeShape(endShape, cmd);
}
canvas()->addCommand(cmd);
} else {
canvas()->updateCanvas(pathShape->boundingRect());
delete pathShape;
}
}
QList<QPointer<QWidget> > KoCreatePathTool::createOptionWidgets()
{
Q_D(KoCreatePathTool);
QList<QPointer<QWidget> > list;
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);
- d->strokeWidget = new KoStrokeConfigWidget(0);
- d->strokeWidget->setWindowTitle(i18n("Line"));
- d->strokeWidget->setCanvas(canvas());
- d->strokeWidget->setActive(false);
- list.append(d->strokeWidget);
-
connect(angleEdit, SIGNAL(valueChanged(int)), this, SLOT(angleDeltaChanged(int)));
connect(angleSnap, SIGNAL(stateChanged(int)), this, SLOT(angleSnapChanged(int)));
return list;
}
-KoShapeStroke *KoCreatePathTool::createStroke()
-{
- Q_D(KoCreatePathTool);
-
- KoShapeStroke *stroke = 0;
- if (d->strokeWidget) {
- stroke = d->strokeWidget->createShapeStroke();
- }
- return stroke;
-}
-
//have to include this because of Q_PRIVATE_SLOT
#include <moc_KoCreatePathTool.cpp>
diff --git a/libs/basicflakes/tools/KoCreatePathTool.h b/libs/basicflakes/tools/KoCreatePathTool.h
index 135c8e446d..51094aee08 100644
--- a/libs/basicflakes/tools/KoCreatePathTool.h
+++ b/libs/basicflakes/tools/KoCreatePathTool.h
@@ -1,110 +1,109 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
* 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.
*/
#ifndef KOCREATEPATHTOOL_H
#define KOCREATEPATHTOOL_H
#include "kritabasicflakes_export.h"
+#include <KoFlakeTypes.h>
#include <KoToolBase.h>
#include <QList>
class KoPathShape;
class KoShapeStroke;
class KoCreatePathToolPrivate;
#define KoCreatePathTool_ID "CreatePathTool"
/**
* Tool for creating path shapes.
*/
class KRITABASICFLAKES_EXPORT KoCreatePathTool : public KoToolBase
{
Q_OBJECT
public:
/**
* Constructor for the tool that allows you to create new paths by hand.
* @param canvas the canvas this tool will be working for.
*/
explicit KoCreatePathTool(KoCanvasBase * canvas);
virtual ~KoCreatePathTool();
/// reimplemented
virtual void paint(QPainter &painter, const KoViewConverter &converter);
/// reimplemented
virtual void mousePressEvent(KoPointerEvent *event);
/// reimplemented
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/// reimplemented
virtual void mouseMoveEvent(KoPointerEvent *event);
/// reimplemented
virtual void mouseReleaseEvent(KoPointerEvent *event);
/// reimplemented
virtual void keyPressEvent(QKeyEvent *event);
/// For behavior as selection tool and with initial shift-key
virtual bool listeningToModifiers();
/**
* Returns true if path has been started
*/
bool pathStarted();
public Q_SLOTS:
/// reimplemented
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape*> &shapes);
/// reimplemented
virtual void deactivate();
/// reimplemented
virtual void documentResourceChanged(int key, const QVariant & res);
protected:
/**
* Add path shape to document.
* This method can be overridden and change the behaviour of the tool. In that case the subclass takes ownership of pathShape.
* It gets only called if there are two or more points in the path.
*/
virtual void addPathShape(KoPathShape* pathShape);
protected:
/**
* This method is called to paint the path. Decorations are drawn by KoCreatePathTool afterwards.
*/
virtual void paintPath(KoPathShape& pathShape, QPainter &painter, const KoViewConverter &converter);
void endPath();
void endPathWithoutLastPoint();
void cancelPath();
void removeLastPoint();
/// reimplemented
virtual QList<QPointer<QWidget> > createOptionWidgets();
private:
- KoShapeStroke *createStroke();
-
Q_DECLARE_PRIVATE(KoCreatePathTool)
Q_PRIVATE_SLOT(d_func(), void angleDeltaChanged(int))
Q_PRIVATE_SLOT(d_func(), void angleSnapChanged(int))
};
#endif
diff --git a/libs/basicflakes/tools/KoCreatePathTool_p.h b/libs/basicflakes/tools/KoCreatePathTool_p.h
index adaeebeafe..a902bd59e1 100644
--- a/libs/basicflakes/tools/KoCreatePathTool_p.h
+++ b/libs/basicflakes/tools/KoCreatePathTool_p.h
@@ -1,428 +1,427 @@
/* 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.
*/
#ifndef KOCREATEPATHTOOL_P_H
#define KOCREATEPATHTOOL_P_H
#include "KoCreatePathTool.h"
#include "KoPathPoint.h"
#include "KoPathPointData.h"
#include "KoPathPointMergeCommand.h"
#include "KoParameterShape.h"
#include "KoShapeManager.h"
#include "KoSnapStrategy.h"
#include "KoToolBase_p.h"
#include <KoViewConverter.h>
#include "math.h"
class KoStrokeConfigWidget;
class KoConverter;
/// Small helper to keep track of a path point and its parent path shape
struct PathConnectionPoint {
PathConnectionPoint()
: path(0), point(0) {
}
// reset state to invalid
void reset() {
path = 0;
point = 0;
}
PathConnectionPoint& operator =(KoPathPoint * pathPoint) {
if (!pathPoint || ! pathPoint->parent()) {
reset();
} else {
path = pathPoint->parent();
point = pathPoint;
}
return *this;
}
bool operator != (const PathConnectionPoint &rhs) const {
return rhs.path != path || rhs.point != point;
}
bool operator == (const PathConnectionPoint &rhs) const {
return rhs.path == path && rhs.point == point;
}
bool isValid() const {
return path && point;
}
// checks if the path and point are still valid
void validate(KoCanvasBase *canvas) {
// no point in validating an already invalid state
if (!isValid()) {
return;
}
// we need canvas to validate
if (!canvas) {
reset();
return;
}
// check if path is still part of the docment
if (!canvas->shapeManager()->shapes().contains(path)) {
reset();
return;
}
// check if point is still part of the path
if (path->pathPointIndex(point) == KoPathPointIndex(-1, -1)) {
reset();
return;
}
}
KoPathShape * path;
KoPathPoint * point;
};
inline qreal squareDistance(const QPointF &p1, const QPointF &p2)
{
qreal dx = p1.x() - p2.x();
qreal dy = p1.y() - p2.y();
return dx * dx + dy * dy;
}
class AngleSnapStrategy : public KoSnapStrategy
{
public:
explicit AngleSnapStrategy(qreal angleStep, bool active)
: KoSnapStrategy(KoSnapGuide::CustomSnapping), m_angleStep(angleStep), m_active(active) {
}
void setStartPoint(const QPointF &startPoint) {
m_startPoint = startPoint;
}
void setAngleStep(qreal angleStep) {
m_angleStep = qAbs(angleStep);
}
virtual bool snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) {
Q_UNUSED(proxy);
if (!m_active)
return false;
QLineF line(m_startPoint, mousePosition);
qreal currentAngle = line.angle();
int prevStep = qAbs(currentAngle / m_angleStep);
int nextStep = prevStep + 1;
qreal prevAngle = prevStep * m_angleStep;
qreal nextAngle = nextStep * m_angleStep;
if (qAbs(currentAngle - prevAngle) <= qAbs(currentAngle - nextAngle)) {
line.setAngle(prevAngle);
} else {
line.setAngle(nextAngle);
}
qreal maxSquareSnapDistance = maxSnapDistance * maxSnapDistance;
qreal snapDistance = squareDistance(mousePosition, line.p2());
if (snapDistance > maxSquareSnapDistance)
return false;
setSnappedPosition(line.p2());
return true;
}
virtual QPainterPath decoration(const KoViewConverter &converter) const {
Q_UNUSED(converter);
QPainterPath decoration;
decoration.moveTo(m_startPoint);
decoration.lineTo(snappedPosition());
return decoration;
}
void deactivate() {
m_active = false;
}
void activate() {
m_active = true;
}
private:
QPointF m_startPoint;
qreal m_angleStep;
bool m_active;
};
class KoCreatePathToolPrivate : public KoToolBasePrivate
{
KoCreatePathTool * const q;
public:
KoCreatePathToolPrivate(KoCreatePathTool * const qq, KoCanvasBase* canvas)
: KoToolBasePrivate(qq, canvas),
q(qq),
shape(0),
activePoint(0),
firstPoint(0),
handleRadius(3),
mouseOverFirstPoint(false),
pointIsDragged(false),
finishAfterThisPoint(false),
hoveredPoint(0),
listeningToModifiers(false),
angleSnapStrategy(0),
angleSnappingDelta(15),
- angleSnapStatus(false),
- strokeWidget(0) {
+ angleSnapStatus(false)
+ {
}
KoPathShape *shape;
KoPathPoint *activePoint;
KoPathPoint *firstPoint;
int handleRadius;
bool mouseOverFirstPoint;
bool pointIsDragged;
bool finishAfterThisPoint;
PathConnectionPoint existingStartPoint; ///< an existing path point we started a new path at
PathConnectionPoint existingEndPoint; ///< an existing path point we finished a new path at
KoPathPoint *hoveredPoint; ///< an existing path end point the mouse is hovering on
bool listeningToModifiers; // Fine tune when to begin processing modifiers at the beginning of a stroke.
AngleSnapStrategy *angleSnapStrategy;
int angleSnappingDelta;
bool angleSnapStatus;
- KoStrokeConfigWidget *strokeWidget;
void repaintActivePoint() const {
const bool isFirstPoint = (activePoint == firstPoint);
if (!isFirstPoint && !pointIsDragged)
return;
QRectF rect = activePoint->boundingRect(false);
// make sure that we have the second control point inside our
// update rect, as KoPathPoint::boundingRect will not include
// the second control point of the last path point if the path
// is not closed
const QPointF &point = activePoint->point();
const QPointF &controlPoint = activePoint->controlPoint2();
rect = rect.united(QRectF(point, controlPoint).normalized());
// when painting the first point we want the
// first control point to be painted as well
if (isFirstPoint) {
const QPointF &controlPoint = activePoint->controlPoint1();
rect = rect.united(QRectF(point, controlPoint).normalized());
}
QPointF border = q->canvas()->viewConverter()
->viewToDocument(QPointF(handleRadius, handleRadius));
rect.adjust(-border.x(), -border.y(), border.x(), border.y());
q->canvas()->updateCanvas(rect);
}
/// returns the nearest existing path point
KoPathPoint* endPointAtPosition(const QPointF &position) const {
QRectF roi = q->handleGrabRect(position);
QList<KoShape *> shapes = q->canvas()->shapeManager()->shapesAt(roi);
KoPathPoint * nearestPoint = 0;
qreal minDistance = HUGE_VAL;
uint grabSensitivity = q->grabSensitivity();
qreal maxDistance = q->canvas()->viewConverter()->viewToDocumentX(grabSensitivity);
Q_FOREACH(KoShape * s, shapes) {
KoPathShape * path = dynamic_cast<KoPathShape*>(s);
if (!path)
continue;
KoParameterShape *paramShape = dynamic_cast<KoParameterShape*>(s);
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;
}
/// Connects given path with the ones we hit when starting/finishing
bool connectPaths(KoPathShape *pathShape, const PathConnectionPoint &pointAtStart, const PathConnectionPoint &pointAtEnd) const {
KoPathShape * startShape = 0;
KoPathShape * endShape = 0;
KoPathPoint * startPoint = 0;
KoPathPoint * endPoint = 0;
if (pointAtStart.isValid()) {
startShape = pointAtStart.path;
startPoint = pointAtStart.point;
}
if (pointAtEnd.isValid()) {
endShape = pointAtEnd.path;
endPoint = pointAtEnd.point;
}
// at least one point must be valid
if (!startPoint && !endPoint)
return false;
// do not allow connecting to the same point twice
if (startPoint == endPoint)
endPoint = 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);
// combine with the path we hit on start
KoPathPointIndex startIndex(-1, -1);
if (startShape && startPoint) {
startIndex = startShape->pathPointIndex(startPoint);
pathShape->combine(startShape);
pathShape->moveSubpath(0, pathShape->subpathCount() - 1);
}
// combine with the path we hit on finish
KoPathPointIndex endIndex(-1, -1);
if (endShape && endPoint) {
endIndex = endShape->pathPointIndex(endPoint);
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;
}
void addPathShape() {
if (!shape) return;
if (shape->pointCount() < 2) {
cleanUp();
return;
}
// this is done so that nothing happens when the mouseReleaseEvent for the this event is received
KoPathShape *pathShape = shape;
shape = 0;
q->addPathShape(pathShape);
cleanUp();
return;
}
void cleanUp() {
// reset snap guide
q->canvas()->updateCanvas(q->canvas()->snapGuide()->boundingRect());
q->canvas()->snapGuide()->reset();
angleSnapStrategy = 0;
delete shape;
shape = 0;
existingStartPoint = 0;
existingEndPoint = 0;
hoveredPoint = 0;
listeningToModifiers = false;
}
void angleDeltaChanged(int value) {
angleSnappingDelta = value;
if (angleSnapStrategy)
angleSnapStrategy->setAngleStep(angleSnappingDelta);
}
void angleSnapChanged(int angleSnap) {
angleSnapStatus = ! angleSnapStatus;
if (angleSnapStrategy) {
if (angleSnap == Qt::Checked)
angleSnapStrategy->activate();
else
angleSnapStrategy->deactivate();
}
}
};
#endif // KOCREATEPATHTOOL_P_H
diff --git a/libs/basicflakes/tools/KoPencilTool.cpp b/libs/basicflakes/tools/KoPencilTool.cpp
index e928c5da3c..e837a38457 100644
--- a/libs/basicflakes/tools/KoPencilTool.cpp
+++ b/libs/basicflakes/tools/KoPencilTool.cpp
@@ -1,559 +1,581 @@
/* 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 <KoCanvasResourceManager.h>
#include <KoColor.h>
#include <KoPathPoint.h>
#include <KoPathPointData.h>
#include <KoPathPointMergeCommand.h>
#include <KoShapePaintingContext.h>
-#include <KoStrokeConfigWidget.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.save();
KoShapePaintingContext paintContext; //FIXME
m_shape->paint(painter, converter, paintContext);
painter.restore();
if (m_shape->stroke()) {
painter.save();
m_shape->stroke()->paint(m_shape, painter, converter);
painter.restore();
}
painter.restore();
}
if (m_hoveredPoint) {
- painter.save();
- painter.setTransform(m_hoveredPoint->parent()->absoluteTransformation(&converter), true);
- KoShape::applyConversion(painter, converter);
-
- painter.setPen(QPen(Qt::blue, 0)); //TODO make configurable
- painter.setBrush(Qt::white); //TODO make configurable
- m_hoveredPoint->paint(painter, handleRadius(), KoPathPoint::Node);
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&painter, m_hoveredPoint->parent(), converter, handleRadius());
- painter.restore();
+ helper.setHandleStyle(KisHandleStyle::primarySelection());
+ m_hoveredPoint->paint(helper, KoPathPoint::Node);
}
}
void KoPencilTool::repaintDecorations()
{
}
void KoPencilTool::mousePressEvent(KoPointerEvent *event)
{
- if (! m_shape) {
+ 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
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, const QSet<KoShape*> &)
+void KoPencilTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
+ KoToolBase::activate(activation, shapes);
+
m_points.clear();
m_close = false;
- useCursor(Qt::ArrowCursor);
+ 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(0);
+ m_strokeWidget = new KoStrokeConfigWidget(canvas(), 0);
+ m_strokeWidget->setNoSelectionTrackingMode(true);
m_strokeWidget->setWindowTitle(i18n("Line"));
- m_strokeWidget->setCanvas(canvas());
+ 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);
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;
}
-KoShapeStroke* KoPencilTool::createStroke()
+KoShapeStrokeSP KoPencilTool::createStroke()
{
- KoShapeStroke *stroke = 0;
+ KoShapeStrokeSP stroke;
if (m_strokeWidget) {
stroke = m_strokeWidget->createShapeStroke();
}
return stroke;
}
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/basicflakes/tools/KoPencilTool.h b/libs/basicflakes/tools/KoPencilTool.h
index a035a4b874..60201a123f 100644
--- a/libs/basicflakes/tools/KoPencilTool.h
+++ b/libs/basicflakes/tools/KoPencilTool.h
@@ -1,98 +1,103 @@
/* 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.
*/
#ifndef _KOPENCILTOOL_H_
#define _KOPENCILTOOL_H_
+#include "KoFlakeTypes.h"
#include "KoToolBase.h"
class KoPathShape;
class KoShapeStroke;
class KoPathPoint;
class KoStrokeConfigWidget;
#include "kritabasicflakes_export.h"
class KRITABASICFLAKES_EXPORT KoPencilTool : public KoToolBase
{
Q_OBJECT
public:
explicit KoPencilTool(KoCanvasBase *canvas);
~KoPencilTool();
void paint(QPainter &painter, const KoViewConverter &converter);
void repaintDecorations();
void mousePressEvent(KoPointerEvent *event) ;
void mouseMoveEvent(KoPointerEvent *event);
void mouseReleaseEvent(KoPointerEvent *event);
void keyPressEvent(QKeyEvent *event);
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape*> &shapes);
void deactivate();
protected:
virtual QList<QPointer<QWidget> > createOptionWidgets();
/**
* Add path shape to document.
* This method can be overridden and change the behaviour of the tool. In that case the subclass takes ownership of pathShape.
* It gets only called, if there are two or more points in the path.
*/
virtual void addPathShape(KoPathShape* path, bool closePath);
- KoShapeStroke* createStroke();
+ KoShapeStrokeSP createStroke();
void setFittingError(qreal fittingError);
qreal getFittingError();
private Q_SLOTS:
void selectMode(int mode);
void setOptimize(int state);
void setDelta(double delta);
+
+protected Q_SLOTS:
+ virtual void slotUpdatePencilCursor();
+
private:
qreal lineAngle(const QPointF &p1, const QPointF &p2);
void addPoint(const QPointF & point);
void finish(bool closePath);
/// returns the nearest existing path point
KoPathPoint* endPointAtPosition(const QPointF &position);
/// Connects given path with the ones we hit when starting/finishing
bool connectPaths(KoPathShape *pathShape, KoPathPoint *pointAtStart, KoPathPoint *pointAtEnd);
enum PencilMode { ModeRaw, ModeCurve, ModeStraight };
PencilMode m_mode;
bool m_optimizeRaw;
bool m_optimizeCurve;
qreal m_combineAngle;
qreal m_fittingError;
bool m_close;
QList<QPointF> m_points; // the raw points
KoPathShape * m_shape;
KoPathPoint *m_existingStartPoint; ///< an existing path point we started a new path at
KoPathPoint *m_existingEndPoint; ///< an existing path point we finished a new path at
KoPathPoint *m_hoveredPoint; ///< an existing path end point the mouse is hovering on
KoStrokeConfigWidget *m_strokeWidget;
};
#endif // _KOPENCILTOOL_H_
diff --git a/libs/command/CMakeLists.txt b/libs/command/CMakeLists.txt
new file mode 100644
index 0000000000..e77271d871
--- /dev/null
+++ b/libs/command/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(kritacommand_LIB_SRCS
+ kundo2stack.cpp
+ kundo2group.cpp
+ kundo2view.cpp
+ kundo2model.cpp
+ kundo2magicstring.cpp
+ kundo2commandextradata.cpp
+ kis_undo_store.cpp
+ kis_undo_stores.cpp
+ kis_command_utils.cpp
+)
+
+add_library(kritacommand SHARED ${kritacommand_LIB_SRCS})
+generate_export_header(kritacommand BASE_NAME kritacommand)
+
+target_link_libraries(kritacommand
+ PUBLIC
+ kritawidgetutils
+ KF5::I18n
+ KF5::ConfigGui
+ Qt5::Core
+ Qt5::Widgets
+)
+
+set_target_properties(kritacommand PROPERTIES
+ VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
+)
+install(TARGETS kritacommand ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/kundo2/Mainpage.dox b/libs/command/Mainpage.dox
similarity index 100%
rename from libs/kundo2/Mainpage.dox
rename to libs/command/Mainpage.dox
diff --git a/libs/command/kis_command_ids.h b/libs/command/kis_command_ids.h
new file mode 100644
index 0000000000..4c60adcb58
--- /dev/null
+++ b/libs/command/kis_command_ids.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KIS_COMMAND_IDS_H
+#define KIS_COMMAND_IDS_H
+
+namespace KisCommandUtils {
+enum CommandId {
+ MoveShapeId = 9999,
+ ResizeShapeId,
+ TransformShapeId,
+ ChangeShapeTransparencyId,
+ ChangeShapeBackgroundId,
+ ChangeShapeStrokeId,
+ ChangeShapeMarkersId,
+ ChangeShapeParameterId,
+ ChangeEllipseShapeId,
+ ChangeRectangleShapeId,
+ ChangePathShapePointId,
+ ChangePathShapeControlPointId
+};
+
+}
+
+#endif // KIS_COMMAND_IDS_H
+
diff --git a/libs/command/kis_command_utils.cpp b/libs/command/kis_command_utils.cpp
new file mode 100644
index 0000000000..ad63adec90
--- /dev/null
+++ b/libs/command/kis_command_utils.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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_command_utils.h"
+
+namespace KisCommandUtils
+{
+ AggregateCommand::AggregateCommand()
+ : m_firstRedo(true) {}
+
+ void AggregateCommand::redo()
+ {
+ if (m_firstRedo) {
+ m_firstRedo = false;
+
+ populateChildCommands();
+ }
+
+ m_store.redoAll();
+ }
+
+ void AggregateCommand::undo()
+ {
+ m_store.undoAll();
+ }
+
+ void AggregateCommand::addCommand(KUndo2Command *cmd)
+ {
+ m_store.addCommand(cmd);
+ }
+
+ SkipFirstRedoWrapper::SkipFirstRedoWrapper(KUndo2Command *child, KUndo2Command *parent)
+ : KUndo2Command(child ? child->text() : kundo2_noi18n("<bug: unnamed command>"), parent), m_firstRedo(true), m_child(child) {}
+
+ void SkipFirstRedoWrapper::redo()
+ {
+ if (m_firstRedo) {
+ m_firstRedo = false;
+ } else {
+ if (m_child) {
+ m_child->redo();
+ }
+ KUndo2Command::redo();
+ }
+ }
+
+ void SkipFirstRedoWrapper::undo()
+ {
+ KUndo2Command::undo();
+ if (m_child) {
+ m_child->undo();
+ }
+ }
+
+ SkipFirstRedoBase::SkipFirstRedoBase(bool skipFirstRedo, KUndo2Command *parent)
+ : KUndo2Command(parent),
+ m_firstRedo(skipFirstRedo)
+ {
+ }
+
+ SkipFirstRedoBase::SkipFirstRedoBase(bool skipFirstRedo, const KUndo2MagicString &text, KUndo2Command *parent)
+ : KUndo2Command(text, parent),
+ m_firstRedo(skipFirstRedo)
+ {
+ }
+
+ void SkipFirstRedoBase::redo()
+ {
+ if (m_firstRedo) {
+ m_firstRedo = false;
+ } else {
+ redoImpl();
+ KUndo2Command::redo();
+ }
+ }
+
+ void SkipFirstRedoBase::undo()
+ {
+ KUndo2Command::undo();
+ undoImpl();
+ }
+
+ void SkipFirstRedoBase::setSkipOneRedo(bool value)
+ {
+ m_firstRedo = true;
+ }
+
+ FlipFlopCommand::FlipFlopCommand(bool finalize, KUndo2Command *parent)
+ : KUndo2Command(parent),
+ m_finalize(finalize),
+ m_firstRedo(true)
+ {
+ }
+
+ void FlipFlopCommand::redo()
+ {
+ if (!m_finalize) {
+ init();
+ } else {
+ end();
+ }
+
+ m_firstRedo = false;
+ }
+
+ void FlipFlopCommand::undo()
+ {
+ if (m_finalize) {
+ init();
+ } else {
+ end();
+ }
+ }
+
+ void FlipFlopCommand::init() {}
+ void FlipFlopCommand::end() {}
+
+ CompositeCommand::CompositeCommand(KUndo2Command *parent)
+ : KUndo2Command(parent) {}
+
+ CompositeCommand::~CompositeCommand() {
+ qDeleteAll(m_commands);
+ }
+
+ void CompositeCommand::addCommand(KUndo2Command *cmd) {
+ if (cmd) {
+ m_commands << cmd;
+ }
+ }
+
+ void CompositeCommand::redo() {
+ Q_FOREACH (KUndo2Command *cmd, m_commands) {
+ cmd->redo();
+ }
+ }
+
+ void CompositeCommand::undo() {
+ Q_FOREACH (KUndo2Command *cmd, m_commands) {
+ cmd->undo();
+ }
+ }
+}
diff --git a/libs/command/kis_command_utils.h b/libs/command/kis_command_utils.h
new file mode 100644
index 0000000000..87ab9a5773
--- /dev/null
+++ b/libs/command/kis_command_utils.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_COMMAND_UTILS_H
+#define __KIS_COMMAND_UTILS_H
+
+#include "kundo2command.h"
+#include "kis_undo_stores.h"
+#include "kritacommand_export.h"
+
+namespace KisCommandUtils
+{
+ struct KRITACOMMAND_EXPORT AggregateCommand : public KUndo2Command {
+ AggregateCommand();
+
+ void redo();
+ void undo();
+
+ protected:
+ virtual void populateChildCommands() = 0;
+ void addCommand(KUndo2Command *cmd);
+
+ private:
+ bool m_firstRedo;
+ KisSurrogateUndoStore m_store;
+ };
+
+ struct KRITACOMMAND_EXPORT SkipFirstRedoWrapper : public KUndo2Command {
+ SkipFirstRedoWrapper(KUndo2Command *child = 0, KUndo2Command *parent = 0);
+ void redo() override;
+ void undo() override;
+
+ private:
+ bool m_firstRedo;
+ QScopedPointer<KUndo2Command> m_child;
+ };
+
+ struct KRITACOMMAND_EXPORT SkipFirstRedoBase : public KUndo2Command {
+ SkipFirstRedoBase(bool skipFirstRedo, KUndo2Command *parent = 0);
+ SkipFirstRedoBase(bool skipFirstRedo, const KUndo2MagicString &text, KUndo2Command *parent = 0);
+
+ void redo() final;
+ void undo() final;
+
+ void setSkipOneRedo(bool value);
+
+ protected:
+ virtual void redoImpl() = 0;
+ virtual void undoImpl() = 0;
+
+ private:
+ bool m_firstRedo;
+ };
+
+
+ struct KRITACOMMAND_EXPORT FlipFlopCommand : public KUndo2Command {
+ FlipFlopCommand(bool finalize, KUndo2Command *parent = 0);
+
+ void redo();
+ void undo();
+
+ protected:
+ virtual void init();
+ virtual void end();
+ bool isFinalizing() const { return m_finalize; }
+ bool isFirstRedo() const { return m_firstRedo; }
+
+ private:
+ bool m_finalize;
+ bool m_firstRedo;
+ };
+
+ struct KRITACOMMAND_EXPORT CompositeCommand : public KUndo2Command {
+ CompositeCommand(KUndo2Command *parent = 0);
+ ~CompositeCommand();
+
+ void addCommand(KUndo2Command *cmd);
+
+ void redo();
+ void undo();
+
+ private:
+ QVector<KUndo2Command*> m_commands;
+ };
+}
+
+#endif /* __KIS_COMMAND_UTILS_H */
diff --git a/libs/image/kis_undo_store.cpp b/libs/command/kis_undo_store.cpp
similarity index 100%
rename from libs/image/kis_undo_store.cpp
rename to libs/command/kis_undo_store.cpp
diff --git a/libs/command/kis_undo_store.h b/libs/command/kis_undo_store.h
new file mode 100644
index 0000000000..dc57a06516
--- /dev/null
+++ b/libs/command/kis_undo_store.h
@@ -0,0 +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
+ *
+ * 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/image/kis_undo_stores.cpp b/libs/command/kis_undo_stores.cpp
similarity index 100%
rename from libs/image/kis_undo_stores.cpp
rename to libs/command/kis_undo_stores.cpp
diff --git a/libs/command/kis_undo_stores.h b/libs/command/kis_undo_stores.h
new file mode 100644
index 0000000000..806a5c40ab
--- /dev/null
+++ b/libs/command/kis_undo_stores.h
@@ -0,0 +1,74 @@
+/*
+ * 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_UNDO_STORES_H
+#define __KIS_UNDO_STORES_H
+
+#include "kis_undo_store.h"
+
+class KUndo2Stack;
+class KUndo2MagicString;
+
+
+/**
+ * KisSurrogateUndoAdapter -- saves commands directly to the
+ * internal stack. Used for wrapping around legacy code into
+ * a single command.
+ */
+class KRITACOMMAND_EXPORT KisSurrogateUndoStore : public KisUndoStore
+{
+public:
+ KisSurrogateUndoStore();
+ ~KisSurrogateUndoStore();
+
+ const KUndo2Command* presentCommand();
+ void undoLastCommand();
+ void addCommand(KUndo2Command *cmd);
+ void beginMacro(const KUndo2MagicString& macroName);
+ void endMacro();
+
+ void undo();
+ void redo();
+
+ void undoAll();
+ void redoAll();
+
+ void purgeRedoState();
+
+ void clear();
+
+private:
+ KUndo2Stack *m_undoStack;
+};
+
+/**
+ * @brief The KisDumbUndoStore class doesn't actually save commands,
+ * so you cannot undo or redo!
+ */
+class KRITACOMMAND_EXPORT KisDumbUndoStore : public KisUndoStore
+{
+public:
+ const KUndo2Command* presentCommand();
+ void undoLastCommand();
+ void addCommand(KUndo2Command *cmd);
+ void beginMacro(const KUndo2MagicString& macroName);
+ void endMacro();
+ void purgeRedoState();
+};
+
+#endif /* __KIS_UNDO_STORES_H */
diff --git a/libs/kundo2/kundo2command.h b/libs/command/kundo2command.h
similarity index 100%
rename from libs/kundo2/kundo2command.h
rename to libs/command/kundo2command.h
diff --git a/libs/kundo2/kundo2commandextradata.cpp b/libs/command/kundo2commandextradata.cpp
similarity index 100%
rename from libs/kundo2/kundo2commandextradata.cpp
rename to libs/command/kundo2commandextradata.cpp
diff --git a/libs/command/kundo2commandextradata.h b/libs/command/kundo2commandextradata.h
new file mode 100644
index 0000000000..3524d763ad
--- /dev/null
+++ b/libs/command/kundo2commandextradata.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KUNDO2COMMANDEXTRADATA_H
+#define __KUNDO2COMMANDEXTRADATA_H
+
+#include "kritacommand_export.h"
+
+
+class KRITACOMMAND_EXPORT KUndo2CommandExtraData
+{
+public:
+ virtual ~KUndo2CommandExtraData();
+};
+
+#endif /* __KUNDO2COMMANDEXTRADATA_H */
diff --git a/libs/kundo2/kundo2group.cpp b/libs/command/kundo2group.cpp
similarity index 100%
rename from libs/kundo2/kundo2group.cpp
rename to libs/command/kundo2group.cpp
diff --git a/libs/command/kundo2group.h b/libs/command/kundo2group.h
new file mode 100644
index 0000000000..dab85c9a70
--- /dev/null
+++ b/libs/command/kundo2group.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef KUNDO2GROUP_H
+#define KUNDO2GROUP_H
+
+#include <QObject>
+#include <QString>
+
+#include "kritacommand_export.h"
+
+class KUndo2GroupPrivate;
+class KUndo2QStack;
+class QAction;
+
+#ifndef QT_NO_UNDOGROUP
+
+class KRITACOMMAND_EXPORT KUndo2Group : public QObject
+{
+ Q_OBJECT
+ Q_DECLARE_PRIVATE(KUndo2Group)
+
+public:
+ explicit KUndo2Group(QObject *parent = 0);
+ ~KUndo2Group();
+
+ void addStack(KUndo2QStack *stack);
+ void removeStack(KUndo2QStack *stack);
+ QList<KUndo2QStack*> stacks() const;
+ KUndo2QStack *activeStack() const;
+
+#ifndef QT_NO_ACTION
+ QAction *createUndoAction(QObject *parent) const;
+ QAction *createRedoAction(QObject *parent) const;
+#endif // QT_NO_ACTION
+ bool canUndo() const;
+ bool canRedo() const;
+ QString undoText() const;
+ QString redoText() const;
+ bool isClean() const;
+
+public Q_SLOTS:
+ void undo();
+ void redo();
+ void setActiveStack(KUndo2QStack *stack);
+
+Q_SIGNALS:
+ void activeStackChanged(KUndo2QStack *stack);
+ void indexChanged(int idx);
+ void cleanChanged(bool clean);
+ void canUndoChanged(bool canUndo);
+ void canRedoChanged(bool canRedo);
+ void undoTextChanged(const QString &undoActionText);
+ void redoTextChanged(const QString &redoActionText);
+
+private:
+ // from QUndoGroupPrivate
+ KUndo2QStack *m_active;
+ QList<KUndo2QStack*> m_stack_list;
+
+ Q_DISABLE_COPY(KUndo2Group)
+};
+
+#endif // QT_NO_UNDOGROUP
+
+#endif // KUNDO2GROUP_H
diff --git a/libs/kundo2/kundo2magicstring.cpp b/libs/command/kundo2magicstring.cpp
similarity index 100%
rename from libs/kundo2/kundo2magicstring.cpp
rename to libs/command/kundo2magicstring.cpp
diff --git a/libs/command/kundo2magicstring.h b/libs/command/kundo2magicstring.h
new file mode 100644
index 0000000000..d6a3ffe8f8
--- /dev/null
+++ b/libs/command/kundo2magicstring.h
@@ -0,0 +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
+ * nominative</a>) and the message in undo/redo actions (which is
+ * usually a <a href="http://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 speacial 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/kundo2/kundo2model.cpp b/libs/command/kundo2model.cpp
similarity index 100%
rename from libs/kundo2/kundo2model.cpp
rename to libs/command/kundo2model.cpp
diff --git a/libs/kundo2/kundo2model.h b/libs/command/kundo2model.h
similarity index 100%
rename from libs/kundo2/kundo2model.h
rename to libs/command/kundo2model.h
diff --git a/libs/kundo2/kundo2qstack.h b/libs/command/kundo2qstack.h
similarity index 100%
rename from libs/kundo2/kundo2qstack.h
rename to libs/command/kundo2qstack.h
diff --git a/libs/kundo2/kundo2stack.cpp b/libs/command/kundo2stack.cpp
similarity index 100%
rename from libs/kundo2/kundo2stack.cpp
rename to libs/command/kundo2stack.cpp
diff --git a/libs/command/kundo2stack.h b/libs/command/kundo2stack.h
new file mode 100644
index 0000000000..4aef82244c
--- /dev/null
+++ b/libs/command/kundo2stack.h
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
+ * Copyright (c) 2014 Mohit Goyal <mohit.bits2011@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 program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef KUNDO2STACK_H
+#define KUNDO2STACK_H
+
+#include <QObject>
+#include <QString>
+#include <QList>
+#include <QAction>
+#include <QTime>
+#include <QVector>
+
+
+#include "kritacommand_export.h"
+
+class QAction;
+class KUndo2CommandPrivate;
+class KUndo2Group;
+class KActionCollection;
+
+#ifndef QT_NO_UNDOCOMMAND
+
+#include "kundo2magicstring.h"
+#include "kundo2commandextradata.h"
+
+
+/**
+ * WARNING: In general, don't derive undo commands from QObject. And
+ * if you really need it, don't use QObject lifetime tracking
+ * for the commands: KUndo2Command has its own, *nonvirtual*
+ * hierarchy, and don't make it a parent or a child of any
+ * QObject. Otherwise two different parents will try to track
+ * the lifetime of your command and, most probably, you'll
+ * get a crash.
+ *
+ * As a general rule: an undo command should be derived
+ * from QObject only for the sake of signal/slots capabilities.
+ * Nothing else.
+ */
+class KRITACOMMAND_EXPORT KUndo2Command
+{
+ KUndo2CommandPrivate *d;
+ int timedID;
+
+public:
+ explicit KUndo2Command(KUndo2Command *parent = 0);
+ explicit KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent = 0);
+ virtual ~KUndo2Command();
+
+ virtual void undo();
+ virtual void redo();
+
+ QString actionText() const;
+ KUndo2MagicString text() const;
+ void setText(const KUndo2MagicString &text);
+
+ virtual int id() const;
+ virtual int timedId();
+ virtual void setTimedID(int timedID);
+ virtual bool mergeWith(const KUndo2Command *other);
+ virtual bool timedMergeWith(KUndo2Command *other);
+
+ int childCount() const;
+ const KUndo2Command *child(int index) const;
+
+ bool hasParent();
+ virtual void setTime();
+ virtual QTime time();
+ virtual void setEndTime();
+ virtual QTime endTime();
+
+ virtual QVector<KUndo2Command*> mergeCommandsVector();
+ virtual bool isMerged();
+ virtual void undoMergedCommands();
+ virtual void redoMergedCommands();
+
+ /**
+ * \return user-defined object associated with the command
+ *
+ * \see setExtraData()
+ */
+ KUndo2CommandExtraData* extraData() const;
+
+ /**
+ * The user can assign an arbitrary object associated with the
+ * command. The \p data object is owned by the command. If you assign
+ * the object twice, the first one will be destroyed.
+ */
+ void setExtraData(KUndo2CommandExtraData *data);
+
+private:
+ Q_DISABLE_COPY(KUndo2Command)
+ friend class KUndo2QStack;
+
+
+ bool m_hasParent;
+ int m_timedID;
+
+ QTime m_timeOfCreation;
+ QTime m_endOfCommand;
+ QVector<KUndo2Command*> m_mergeCommandsVector;
+};
+
+#endif // QT_NO_UNDOCOMMAND
+
+#ifndef QT_NO_UNDOSTACK
+
+class KRITACOMMAND_EXPORT KUndo2QStack : public QObject
+{
+ Q_OBJECT
+// Q_DECLARE_PRIVATE(KUndo2QStack)
+ Q_PROPERTY(bool active READ isActive WRITE setActive)
+ Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit)
+
+public:
+ explicit KUndo2QStack(QObject *parent = 0);
+ virtual ~KUndo2QStack();
+ void clear();
+
+ void push(KUndo2Command *cmd);
+
+ bool canUndo() const;
+ bool canRedo() const;
+ QString undoText() const;
+ QString redoText() const;
+
+ int count() const;
+ int index() const;
+ QString actionText(int idx) const;
+ QString text(int idx) const;
+
+#ifndef QT_NO_ACTION
+ QAction *createUndoAction(QObject *parent) const;
+ QAction *createRedoAction(QObject *parent) const;
+#endif // QT_NO_ACTION
+
+ bool isActive() const;
+ bool isClean() const;
+ int cleanIndex() const;
+
+ void beginMacro(const KUndo2MagicString &text);
+ void endMacro();
+
+ void setUndoLimit(int limit);
+ int undoLimit() const;
+
+ const KUndo2Command *command(int index) const;
+
+ void setUseCumulativeUndoRedo(bool value);
+ bool useCumulativeUndoRedo();
+ void setTimeT1(double value);
+ double timeT1();
+ void setTimeT2(double value);
+ double timeT2();
+ int strokesN();
+ void setStrokesN(int value);
+
+
+public Q_SLOTS:
+ void setClean();
+ virtual void setIndex(int idx);
+ virtual void undo();
+ virtual void redo();
+ void setActive(bool active = true);
+
+ void purgeRedoState();
+
+Q_SIGNALS:
+ void indexChanged(int idx);
+ void cleanChanged(bool clean);
+ void canUndoChanged(bool canUndo);
+ void canRedoChanged(bool canRedo);
+ void undoTextChanged(const QString &undoActionText);
+ void redoTextChanged(const QString &redoActionText);
+
+protected:
+ virtual void notifySetIndexChangedOneCommand();
+
+private:
+ // from QUndoStackPrivate
+ QList<KUndo2Command*> m_command_list;
+ QList<KUndo2Command*> m_macro_stack;
+ int m_index;
+ int m_clean_index;
+ KUndo2Group *m_group;
+ int m_undo_limit;
+ bool m_useCumulativeUndoRedo;
+ double m_timeT1;
+ double m_timeT2;
+ int m_strokesN;
+ int m_lastMergedSetCount;
+ int m_lastMergedIndex;
+
+ // also from QUndoStackPrivate
+ void setIndex(int idx, bool clean);
+ bool checkUndoLimit();
+
+ Q_DISABLE_COPY(KUndo2QStack)
+ friend class KUndo2Group;
+};
+
+class KRITACOMMAND_EXPORT KUndo2Stack : public KUndo2QStack
+{
+public:
+ explicit KUndo2Stack(QObject *parent = 0);
+
+ // functions from KUndoStack
+ QAction* createRedoAction(KActionCollection* actionCollection, const QString& actionName = QString());
+ QAction* createUndoAction(KActionCollection* actionCollection, const QString& actionName = QString());
+};
+
+#endif // QT_NO_UNDOSTACK
+
+#endif // KUNDO2STACK_H
diff --git a/libs/kundo2/kundo2stack_p.h b/libs/command/kundo2stack_p.h
similarity index 100%
rename from libs/kundo2/kundo2stack_p.h
rename to libs/command/kundo2stack_p.h
diff --git a/libs/kundo2/kundo2view.cpp b/libs/command/kundo2view.cpp
similarity index 100%
rename from libs/kundo2/kundo2view.cpp
rename to libs/command/kundo2view.cpp
diff --git a/libs/command/kundo2view.h b/libs/command/kundo2view.h
new file mode 100644
index 0000000000..1162b4a81e
--- /dev/null
+++ b/libs/command/kundo2view.h
@@ -0,0 +1,111 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef KUNDO2VIEW_H
+#define KUNDO2VIEW_H
+
+#include <QListView>
+#include <QString>
+
+#include "kritacommand_export.h"
+
+#ifndef QT_NO_UNDOVIEW
+
+class KUndo2ViewPrivate;
+class KUndo2QStack;
+class KUndo2Group;
+class QIcon;
+
+class KRITACOMMAND_EXPORT KUndo2View : public QListView
+{
+ Q_OBJECT
+ Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel)
+ Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon)
+
+public:
+ explicit KUndo2View(QWidget *parent = 0);
+ explicit KUndo2View(KUndo2QStack *stack, QWidget *parent = 0);
+#ifndef QT_NO_UNDOGROUP
+ explicit KUndo2View(KUndo2Group *group, QWidget *parent = 0);
+#endif
+ ~KUndo2View();
+
+ KUndo2QStack *stack() const;
+#ifndef QT_NO_UNDOGROUP
+ KUndo2Group *group() const;
+#endif
+
+ void setEmptyLabel(const QString &label);
+ QString emptyLabel() const;
+
+ void setCleanIcon(const QIcon &icon);
+ QIcon cleanIcon() const;
+
+public Q_SLOTS:
+ void setStack(KUndo2QStack *stack);
+#ifndef QT_NO_UNDOGROUP
+ void setGroup(KUndo2Group *group);
+#endif
+
+private:
+ KUndo2ViewPrivate* const d;
+ Q_DISABLE_COPY(KUndo2View)
+};
+
+#endif // QT_NO_UNDOVIEW
+#endif // KUNDO2VIEW_H
diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt
index 437b4c0c62..d810eae7c0 100644
--- a/libs/flake/CMakeLists.txt
+++ b/libs/flake/CMakeLists.txt
@@ -1,226 +1,231 @@
project(kritaflake)
include_directories(
${CMAKE_SOURCE_DIR}/libs/flake/commands
${CMAKE_SOURCE_DIR}/libs/flake/tools
${CMAKE_SOURCE_DIR}/libs/flake/svg
${CMAKE_BINARY_DIR}/libs/flake
)
add_subdirectory(styles)
add_subdirectory(tests)
set(kritaflake_SRCS
KoGradientHelper.cpp
KoFlake.cpp
KoCanvasBase.cpp
KoResourceManager_p.cpp
KoDerivedResourceConverter.cpp
KoResourceUpdateMediator.cpp
KoCanvasResourceManager.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
KoShapeBasedDocumentBase.cpp
KoShapeApplicationData.cpp
KoShapeContainer.cpp
KoShapeContainerModel.cpp
- KoShapeContainerDefaultModel.cpp
KoShapeGroup.cpp
- KoShapeManagerPaintingStrategy.cpp
KoShapeManager.cpp
KoShapePaintingContext.cpp
KoFrameShape.cpp
KoUnavailShape.cpp
- KoMarkerData.cpp
KoMarker.cpp
KoMarkerCollection.cpp
- KoMarkerSharedLoadingData.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
KoShapeConfigWidgetBase.cpp
KoDrag.cpp
+ KoSvgPaste.cpp
KoDragOdfSaveHelper.cpp
KoShapeOdfSaveHelper.cpp
- KoShapePaste.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
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
tools/KoCreateShapeStrategy.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/KoPanTool.cpp
tools/KoPanToolFactory.cpp
tools/KoInteractionTool.cpp
tools/KoInteractionStrategy.cpp
+ tools/KoInteractionStrategyFactory.cpp
tools/KoCreateShapesTool.cpp
tools/KoCreateShapesToolFactory.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/SvgPatternHelper.cpp
svg/SvgFilterHelper.cpp
svg/SvgCssHelper.cpp
svg/SvgClipPathHelper.cpp
svg/SvgLoadingContext.cpp
svg/SvgShapeFactory.cpp
+ svg/parsers/SvgTransformParser.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>
)
-target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritaundo2 KF5::WidgetsAddons Qt5::Svg)
+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/KoBakedShapeRenderer.h b/libs/flake/KoBakedShapeRenderer.h
new file mode 100644
index 0000000000..d3d335f934
--- /dev/null
+++ b/libs/flake/KoBakedShapeRenderer.h
@@ -0,0 +1,133 @@
+/*
+ * 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 KOBAKEDSHAPERENDERER_H
+#define KOBAKEDSHAPERENDERER_H
+
+#include <QImage>
+#include <QPainter>
+#include <QPainterPath>
+#include <QTransform>
+
+#include <kis_debug.h>
+#include <kis_algebra_2d.h>
+
+
+struct KoBakedShapeRenderer {
+ KoBakedShapeRenderer(const QPainterPath &dstShapeOutline, const QTransform &dstShapeTransform,
+ const QTransform &bakedTransform,
+ const QRectF &referenceRect,
+ bool contentIsObb, const QRectF &bakedShapeBoundingRect,
+ bool referenceIsObb,
+ const QTransform &patternTransform)
+ : m_dstShapeOutline(dstShapeOutline),
+ m_dstShapeTransform(dstShapeTransform),
+ m_contentIsObb(contentIsObb),
+ m_referenceIsObb(referenceIsObb),
+ m_patternTransform(patternTransform)
+ {
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!contentIsObb || !bakedShapeBoundingRect.isEmpty());
+
+ const QRectF dstShapeBoundingRect = dstShapeOutline.boundingRect();
+
+ QTransform relativeToBakedShape;
+
+ if (referenceIsObb || contentIsObb) {
+ m_relativeToShape = KisAlgebra2D::mapToRect(dstShapeBoundingRect);
+ relativeToBakedShape = KisAlgebra2D::mapToRect(bakedShapeBoundingRect);
+ }
+
+
+ m_referenceRectUser =
+ referenceIsObb ?
+ m_relativeToShape.mapRect(referenceRect).toAlignedRect() :
+ referenceRect.toAlignedRect();
+
+ m_patch = QImage(m_referenceRectUser.size(), QImage::Format_ARGB32);
+ m_patch.fill(0);
+ m_patchPainter.begin(&m_patch);
+
+ m_patchPainter.translate(-m_referenceRectUser.topLeft());
+ m_patchPainter.setClipRect(m_referenceRectUser);
+
+ if (contentIsObb) {
+ m_patchPainter.setTransform(m_relativeToShape, true);
+ m_patchPainter.setTransform(relativeToBakedShape.inverted(), true);
+ }
+
+ m_patchPainter.setTransform(bakedTransform.inverted(), true);
+ }
+
+ QPainter* bakeShapePainter() {
+ return &m_patchPainter;
+ }
+
+ void renderShape(QPainter &painter) {
+ painter.save();
+
+ painter.setTransform(m_dstShapeTransform, true);
+ painter.setClipPath(m_dstShapeOutline);
+
+ QTransform brushTransform;
+
+ QPointF patternOffset = m_referenceRectUser.topLeft();
+
+ brushTransform =
+ brushTransform *
+ QTransform::fromTranslate(patternOffset.x(), patternOffset.y());
+
+ if (m_contentIsObb) {
+ brushTransform = brushTransform * m_relativeToShape.inverted();
+ }
+
+ brushTransform = brushTransform * m_patternTransform;
+
+ if (m_contentIsObb) {
+ brushTransform = brushTransform * m_relativeToShape;
+ }
+
+ QBrush brush(m_patch);
+ brush.setTransform(brushTransform);
+
+ painter.setBrush(brush);
+ painter.drawPath(m_dstShapeOutline);
+
+ painter.restore();
+ }
+
+ QImage patchImage() const {
+ return m_patch;
+ }
+
+
+private:
+ QPainterPath m_dstShapeOutline;
+ QTransform m_dstShapeTransform;
+
+ bool m_contentIsObb;
+ bool m_referenceIsObb;
+ const QTransform &m_patternTransform;
+
+ QImage m_patch;
+ QPainter m_patchPainter;
+
+ QTransform m_relativeToShape;
+ QRect m_referenceRectUser;
+};
+
+#endif // KOBAKEDSHAPERENDERER_H
diff --git a/libs/flake/KoCanvasBase.cpp b/libs/flake/KoCanvasBase.cpp
index 01d3f85940..8117ebe24c 100644
--- a/libs/flake/KoCanvasBase.cpp
+++ b/libs/flake/KoCanvasBase.cpp
@@ -1,129 +1,131 @@
/* 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.
*/
#include <QRectF>
#include <QPointer>
#include <QDebug>
#include "KoCanvasBase.h"
#include "KoCanvasResourceManager.h"
#include "KoShapeController.h"
#include "KoCanvasController.h"
#include "KoViewConverter.h"
#include "KoSnapGuide.h"
#include "KoShapeManager.h"
#include "KoToolProxy.h"
#include "KoSelection.h"
+#include "KoSelectedShapesProxy.h"
class Q_DECL_HIDDEN KoCanvasBase::Private
{
public:
Private()
: shapeController(0),
resourceManager(0),
isResourceManagerShared(false),
controller(0),
snapGuide(0)
{
}
~Private() {
delete shapeController;
if (!isResourceManagerShared) {
delete resourceManager;
}
delete snapGuide;
}
QPointer<KoShapeController> shapeController;
QPointer<KoCanvasResourceManager> resourceManager;
bool isResourceManagerShared;
KoCanvasController *controller;
KoSnapGuide *snapGuide;
};
KoCanvasBase::KoCanvasBase(KoShapeBasedDocumentBase *shapeBasedDocument, KoCanvasResourceManager *sharedResourceManager)
: d(new Private())
{
d->resourceManager = sharedResourceManager ?
sharedResourceManager : new KoCanvasResourceManager();
d->isResourceManagerShared = sharedResourceManager;
d->shapeController = new KoShapeController(this, shapeBasedDocument);
d->snapGuide = new KoSnapGuide(this);
}
KoCanvasBase::~KoCanvasBase()
{
d->shapeController->reset();
delete d;
}
QPointF KoCanvasBase::viewToDocument(const QPointF &viewPoint) const
{
return viewConverter()->viewToDocument(viewPoint - documentOrigin());
}
KoShapeController *KoCanvasBase::shapeController() const
{
if (d->shapeController)
return d->shapeController;
else
return 0;
}
void KoCanvasBase::disconnectCanvasObserver(QObject *object)
{
if (shapeManager()) shapeManager()->selection()->disconnect(object);
if (resourceManager()) resourceManager()->disconnect(object);
if (shapeManager()) shapeManager()->disconnect(object);
if (toolProxy()) toolProxy()->disconnect(object);
+ if (selectedShapesProxy()) selectedShapesProxy()->disconnect(object);
}
KoCanvasResourceManager *KoCanvasBase::resourceManager() const
{
return d->resourceManager;
}
void KoCanvasBase::ensureVisible(const QRectF &rect)
{
if (d->controller && d->controller->canvas())
d->controller->ensureVisible(
d->controller->canvas()->viewConverter()->documentToView(rect));
}
void KoCanvasBase::setCanvasController(KoCanvasController *controller)
{
d->controller = controller;
}
KoCanvasController *KoCanvasBase::canvasController() const
{
return d->controller;
}
void KoCanvasBase::clipToDocument(const KoShape *, QPointF &) const
{
}
KoSnapGuide * KoCanvasBase::snapGuide() const
{
return d->snapGuide;
}
diff --git a/libs/flake/KoCanvasBase.h b/libs/flake/KoCanvasBase.h
index d8fad1ed95..3205dab0ea 100644
--- a/libs/flake/KoCanvasBase.h
+++ b/libs/flake/KoCanvasBase.h
@@ -1,241 +1,254 @@
/* This file is part of the KDE project
Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2006, 2010 Thomas Zander <zander@kde.org>
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.
*/
#ifndef KOCANVASBASE_H
#define KOCANVASBASE_H
#include <QPoint>
#include "kritaflake_export.h"
class KUndo2Command;
class KoUnit;
class KoCanvasResourceManager;
class KoShapeManager;
class KoToolProxy;
class KoViewConverter;
class KoShapeController;
class KoShapeBasedDocumentBase;
class KoCanvasController;
class KoShape;
class KoSnapGuide;
+class KoSelectedShapesProxy;
class QWidget;
class QCursor;
class QPointF;
class QRectF;
class QSizeF;
#include <QObject>
/**
* KoCanvasBase is the interface actual application canvas classes
* should implement. Flake tools know about the canvas, so they can
* do things like scroll, redraw, set a cursor etc.
*/
class KRITAFLAKE_EXPORT KoCanvasBase : public QObject
{
Q_OBJECT
public:
/**
* The constructor.
* @param shapeBasedDocument the implementation of the shapeController that the
* application provides to allow shapes to be added in multiple views.
*/
explicit KoCanvasBase(KoShapeBasedDocumentBase *shapeBasedDocument, KoCanvasResourceManager *sharedResourceManager = 0);
virtual ~KoCanvasBase();
public:
/**
* @return true if opengl can be used directly on the canvas
*/
virtual bool canvasIsOpenGL() const { return false; }
/**
* retrieve the grid size setting.
* The grid spacing will be provided in pt.
* @param horizontal a pointer to a qreal that will be filled with the horizontal grid-spacing
* @param vertical a pointer to a qreal that will be filled with the vertical grid-spacing
*/
virtual void gridSize(QPointF *offset, QSizeF *spacing) const = 0;
/**
* return if snap to grid is enabled.
* @return if snap to grid is enabled.
*/
virtual bool snapToGrid() const = 0;
/**
* set the specified cursor on this canvas
*
* @param cursor the new cursor
* @return the old cursor
*/
virtual void setCursor(const QCursor &cursor) = 0;
/**
* Adds a command to the history. Call this for each @p command you create.
* This will also execute the command.
* This means, most of the application's code will look like
* MyCommand * cmd = new MyCommand( parameters );
* canvas.addCommand( cmd );
*
* Note that the command history takes ownership of the command, it will delete
* it when the undo limit is reached, or when deleting the command history itself.
* @param command the command to add
*/
virtual void addCommand(KUndo2Command *command) = 0;
/**
- * return the current shapeManager
+ * Return the current shapeManager. WARNING: the shape manager can switch
+ * in time, e.g. when a layer is changed. Please don't keep any persistent
+ * connections to it. Instead please use selectedShapesProxy(),
+ * which is guaranteed to be the same throughout the life of the canvas.
+ *
* @return the current shapeManager
*/
virtual KoShapeManager *shapeManager() const = 0;
+ /**
+ * @brief selectedShapesProxy() is a special interface for keeping a persistent connections
+ * to selectionChanged() and selectionContentChanged() signals. While shapeManager() can change
+ * throughout the life time of the cavas, selectedShapesProxy() is guaranteed to stay the same.
+ * @return persistent KoSelectedShapesProxy object
+ */
+ virtual KoSelectedShapesProxy *selectedShapesProxy() const = 0;
+
/**
* Tell the canvas to repaint the specified rectangle. The coordinates
* are document coordinates, not view coordinates.
*/
virtual void updateCanvas(const QRectF &rc) = 0;
/**
* Return the proxy to the active tool (determining which tool
* is really, really active is hard when tablets are involved,
* so leave that to others.
*/
virtual KoToolProxy *toolProxy() const = 0;
/**
* Return the viewConverter for this view.
* @return the viewConverter for this view.
*/
virtual KoViewConverter *viewConverter() const = 0;
/**
* 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;
/**
* Return the widget that will be added to the scrollArea.
*/
virtual QWidget *canvasWidget() = 0;
/**
* Return the widget that will be added to the scrollArea.
*/
virtual const QWidget *canvasWidget() const = 0;
/**
* Return the unit of the current document for initialization of the widgets created
* by the flake framework.
* @see KoDocument::unit()
*/
virtual KoUnit unit() const = 0;
/**
* Called when the user tries to move the argument shape to allow the application to limit the
* users movement to stay within the document bounds.
* An implementation can alter the parameter move to make sure that if the distance moved
* is applied to the shape it will not become unreachable for the user.
* The default implementation does not restrict movement.
* @param shape the shape that will be moved soon.
* @param move the distance the caller intends to move the shape.
*/
virtual void clipToDocument(const KoShape *shape, QPointF &move) const;
/**
* Return the position of the document origin inside the canvas widget, in pixels.
* By default the origin of the canvas widget and the position of the
* document origin are coincident, thus an empty point is returned.
*/
virtual QPoint documentOrigin() const {
return QPoint(0, 0);
}
/**
* This method should somehow call QWidget::updateMicroFocus() on the canvas widget.
*/
virtual void updateInputMethodInfo() = 0;
/**
* disconnect the given QObject completely and utterly from any and all
* connections it has to any QObject owned by the canvas. Do this in
* the setCanvas of every KoCanvasObserver.
*/
virtual void disconnectCanvasObserver(QObject *object);
/**
* Return a pointer to the resource manager associated with this
* canvas. The resource manager contains per-canvas settings such
* as current foreground and background color.
* If instead of per-canvas resources you need per-document resources
* you can by going via the shapeController instead;
* @code
* canvasBase->shapeController()->resourceManager();
* @endcode
* @see KoShapeController::resourceManager()
*/
KoCanvasResourceManager *resourceManager() const;
/**
* Return the shape controller for this canvas.
* A shape controller is used to create or delete shapes and show the relevant dialogs to the user.
*/
KoShapeController *shapeController() const;
/**
* Return the canvas controller for this canvas.
*/
KoCanvasController *canvasController() const;
/**
* @brief Scrolls the content of the canvas so that the given rect is visible.
*
* The rect is to be specified in document coordinates.
*
* @param rect the rectangle to make visible
*/
virtual void ensureVisible(const QRectF &rect);
/**
* Returns the snap guide of the canvas
*/
KoSnapGuide *snapGuide() const;
/// called by KoCanvasController to set the controller that handles this canvas.
void setCanvasController(KoCanvasController *controller);
private:
// we need a KoShapeBasedDocumentBase so that it can work
KoCanvasBase();
class Private;
Private * const d;
};
#endif // KOCANVASBASE_H
diff --git a/libs/flake/KoCanvasController.h b/libs/flake/KoCanvasController.h
index e1ab05fb1a..297650c300 100644
--- a/libs/flake/KoCanvasController.h
+++ b/libs/flake/KoCanvasController.h
@@ -1,471 +1,478 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2008 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007-2008 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006-2007 Jan Hambrecht <jaham@gmx.net>
* 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 KOCANVASCONTROLLER_H
#define KOCANVASCONTROLLER_H
#include "kritaflake_export.h"
#include <QObject>
#include <QSize>
#include <QPoint>
#include <QPointF>
class KActionCollection;
class QRect;
class QRectF;
class KoShape;
class KoCanvasBase;
class KoCanvasControllerProxyObject;
/**
* KoCanvasController is the base class for wrappers around your canvas
* that provides scrolling and zooming for your canvas.
*
* Flake does not provide a canvas, the application will have to
* implement a canvas themselves. You canvas can be QWidget-based
* or something we haven't invented yet -- as long the class that holds the canvas
* imlements KoCanvasController, tools, scrolling and zooming will work.
*
* A KoCanvasController implementation acts as a decorator around the canvas widget
* and provides a way to scroll the canvas, allows the canvas to be centered
* in the viewArea and manages tool activation.
*
* <p>The using application can instantiate this class and add its
* canvas using the setCanvas() call. Which is designed so it can be
* called multiple times if you need to exchange one canvas
* widget for another, for instance, switching between a plain QWidget or a QOpenGLWidget.
*
* <p>There is _one_ KoCanvasController per canvas in your
* application.
*
* <p>The canvas widget is at most as big as the viewport of the scroll
* area, and when the view on the document is near its edges, smaller.
* In your canvas widget code, you can find the right place in your
* document in view coordinates (pixels) by adding the documentOffset
*/
class KRITAFLAKE_EXPORT KoCanvasController
{
public:
/// An enum to alter the positioning and size of the canvas inside the canvas controller
enum CanvasMode {
AlignTop, ///< canvas is top aligned if smaller than the viewport
Centered, ///< canvas is centered if smaller than the viewport
Infinite, ///< canvas is never smaller than the viewport
Spreadsheet ///< same as Infinite, but supports right-to-left layouts
};
// proxy QObject: use this to connect to slots and signals.
KoCanvasControllerProxyObject *proxyObject;
/**
* Constructor.
* @param actionCollection the action collection for this canvas
*/
explicit KoCanvasController(KActionCollection* actionCollection);
virtual ~KoCanvasController();
public:
/**
* Returns the current margin that is used to pad the canvas with.
* This value is read from the KConfig property "canvasmargin"
*/
virtual int margin() const;
/**
* Set the new margin to pad the canvas with.
*/
virtual void setMargin(int margin);
/**
* Sets the how the canvas behaves if the zoomed document becomes smaller than the viewport.
* @param mode the new canvas mode, CanvasMode::Centered is the default value
*/
virtual void setCanvasMode(KoCanvasController::CanvasMode mode);
/// Returns the current canvas mode
virtual KoCanvasController::CanvasMode canvasMode() const;
/**
* compatibility with QAbstractScrollArea
*/
virtual void scrollContentsBy(int dx, int dy) = 0;
/**
* @return the size of the viewport
*/
virtual QSize viewportSize() const = 0;
/**
* Set the shadow option -- by default the canvas controller draws
* a black shadow around the canvas widget, which you may or may
* not want.
*
* @param drawShadow if true, the shadow is drawn, if false, not
*/
virtual void setDrawShadow(bool drawShadow) = 0;
/**
* Set the new canvas to be shown as a child
* Calling this will emit canvasRemoved() if there was a canvas before, and will emit
* canvasSet() with the new canvas.
* @param canvas the new canvas. The KoCanvasBase::canvas() will be called to retrieve the
* actual widget which will then be added as child of this one.
*/
virtual void setCanvas(KoCanvasBase *canvas) = 0;
/**
* Return the currently set canvas. The default implementation will return Null
* @return the currently set canvas
*/
virtual KoCanvasBase *canvas() const;
/**
* return the amount of pixels vertically visible of the child canvas.
* @return the amount of pixels vertically visible of the child canvas.
*/
virtual int visibleHeight() const = 0;
/**
* return the amount of pixels horizontally visible of the child canvas.
* @return the amount of pixels horizontally visible of the child canvas.
*/
virtual int visibleWidth() const = 0;
/**
* return the amount of pixels that are not visible on the left side of the canvas.
* The leftmost pixel that is shown is returned.
*/
virtual int canvasOffsetX() const = 0;
/**
* return the amount of pixels that are not visible on the top side of the canvas.
* The topmost pixel that is shown is returned.
*/
virtual int canvasOffsetY() const = 0;
/**
* @brief Scrolls the content of the canvas so that the given rect is visible.
*
* The rect is to be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the centerpoint of the rectangle is centered if possible.
*
* @param rect the rectangle to make visible
* @param smooth if true the viewport translation will make be just enough to ensure visibility, no more.
* @see KoViewConverter::documentToView()
*/
virtual void ensureVisible(const QRectF &rect, bool smooth = false) = 0;
/**
* @brief Scrolls the content of the canvas so that the given shape is visible.
*
* This is just a wrapper function of the above function.
*
* @param shape the shape to make visible
*/
virtual void ensureVisible(KoShape *shape) = 0;
/**
* @brief zooms in around the center.
*
* The center must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center becomes center if possible.
*
* @param center the position to zoom in on
*/
virtual void zoomIn(const QPoint &center) = 0;
/**
* @brief zooms out around the center.
*
* The center must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center becomes center if possible.
*
* @param center the position to zoom out around
*/
virtual void zoomOut(const QPoint &center) = 0;
/**
* @brief zooms around the center.
*
* The center must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center becomes center if possible.
*
* @param center the position to zoom around
* @param zoom the zoom to apply
*/
virtual void zoomBy(const QPoint &center, qreal zoom) = 0;
/**
* @brief zoom so that rect is exactly visible (as close as possible)
*
* The rect must be specified in view coordinates (pixels). The scrollbar positions
* are changed so that the center of the rect becomes center if possible.
*
* @param rect the rect in view coordinates (pixels) that should fit the view afterwards
*/
virtual void zoomTo(const QRect &rect) = 0;
/**
* @brief repositions the scrollbars so previous center is once again center
*
* The previous center is cached from when the user uses the scrollbars or zoomTo
* are called. zoomTo is mostly used when a zoom tool of sorts have marked an area
* to zoom in on
*
* The success of this method is limited by the size of thing. But we try our best.
*/
virtual void recenterPreferred() = 0;
/**
* Sets the preferred center point in view coordinates (pixels).
* @param viewPoint the new preferred center
*/
virtual void setPreferredCenter(const QPointF &viewPoint) = 0;
/// Returns the currently set preferred center point in view coordinates (pixels)
virtual QPointF preferredCenter() const = 0;
/**
* Move the canvas over the x and y distance of the parameter distance
* @param distance the distance in view coordinates (pixels). A positive distance means moving the canvas up/left.
*/
virtual void pan(const QPoint &distance) = 0;
/**
* Get the position of the scrollbar
*/
virtual QPoint scrollBarValue() const = 0;
/**
* Set the position of the scrollbar
* @param value the new values of the scroll bars
*/
virtual void setScrollBarValue(const QPoint &value) = 0;
/**
* Called when the size of your document in view coordinates (pixels) changes, for instance when zooming.
*
* @param newSize the new size, in view coordinates (pixels), of the document.
* @param recalculateCenter if true the offset in the document we center on after calling
* recenterPreferred() will be recalculated for the new document size so the visual offset stays the same.
*/
virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter) = 0;
/**
* Set mouse wheel to zoom behaviour
* @param zoom if true wheel will zoom instead of scroll, control modifier will scroll
*/
virtual void setZoomWithWheel(bool zoom) = 0;
/**
* Set scroll area to be bigger than actual document.
* It allows the user to move the corner of the document
* to e.g. the center of the screen
*
* @param factor the coefficient, defining how much we can scroll out,
* measured in parts of the widget size. Null value means vast
* scrolling is disabled.
*/
virtual void setVastScrolling(qreal factor) = 0;
/**
* Returns the action collection for the canvas
* @returns action collection for this canvas, can be 0
*/
virtual KActionCollection* actionCollection() const;
QPoint documentOffset() const;
+ /**
+ * @return the current position of the cursor fetched from QCursor::pos() and
+ * converted into document coordinates
+ */
+ virtual QPointF currentCursorPosition() const = 0;
+
protected:
void setDocumentSize(const QSize &sz);
QSize documentSize() const;
void setPreferredCenterFractionX(qreal);
qreal preferredCenterFractionX() const;
void setPreferredCenterFractionY(qreal);
qreal preferredCenterFractionY() const;
void setDocumentOffset( QPoint &offset);
private:
class Private;
Private * const d;
};
/**
* Workaround class for the problem that Qt does not allow two QObject base classes.
* KoCanvasController can be implemented by for instance QWidgets, so it cannot be
* a QObject directly. The interface of this class should be considered public interface
* for KoCanvasController.
*/
class KRITAFLAKE_EXPORT KoCanvasControllerProxyObject : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(KoCanvasControllerProxyObject)
public:
explicit KoCanvasControllerProxyObject(KoCanvasController *canvasController, QObject *parent = 0);
public:
// Convenience methods to invoke the signals from subclasses
void emitCanvasRemoved(KoCanvasController *canvasController) { emit canvasRemoved(canvasController); }
void emitCanvasSet(KoCanvasController *canvasController) { emit canvasSet(canvasController); }
void emitCanvasOffsetXChanged(int offset) { emit canvasOffsetXChanged(offset); }
void emitCanvasOffsetYChanged(int offset) { emit canvasOffsetYChanged(offset); }
void emitCanvasMousePositionChanged(const QPoint &position) { emit canvasMousePositionChanged(position); }
void emitDocumentMousePositionChanged(const QPointF &position) { emit documentMousePositionChanged(position); }
void emitSizeChanged(const QSize &size) { emit sizeChanged(size); }
void emitMoveDocumentOffset(const QPoint &point) { emit moveDocumentOffset(point); }
void emitZoomRelative(const qreal factor, const QPointF &stillPoint) { emit zoomRelative(factor, stillPoint); }
// Convenience method to retrieve the canvas controller for who needs to use QPointer
KoCanvasController *canvasController() const { return m_canvasController; }
Q_SIGNALS:
/**
* Emitted when a previously added canvas is about to be removed.
* @param canvasController this object
*/
void canvasRemoved(KoCanvasController *canvasController);
/**
* Emitted when a canvas is set on this widget
* @param canvasController this object
*/
void canvasSet(KoCanvasController *canvasController);
/**
* Emitted when canvasOffsetX() changes
* @param offset the new canvas offset
*/
void canvasOffsetXChanged(int offset);
/**
* Emitted when canvasOffsetY() changes
* @param offset the new canvas offset
*/
void canvasOffsetYChanged(int offset);
/**
* Emitted when the cursor is moved over the canvas widget.
* @param position the position in view coordinates (pixels).
*/
void canvasMousePositionChanged(const QPoint &position);
/**
* Emitted when the cursor is moved over the canvas widget.
* @param position the position in document coordinates.
*
* Use \ref canvasMousePositionChanged to get the position
* in view coordinates.
*/
void documentMousePositionChanged(const QPointF &position);
/**
* Emitted when the entire controller size changes
* @param size the size in widget pixels.
*/
void sizeChanged(const QSize &size);
/**
* Emitted whenever the document is scrolled.
*
* @param point the new top-left point from which the document should
* be drawn.
*/
void moveDocumentOffset(const QPoint &point);
/**
* Emitted when zoomRelativeToPoint have calculated a factor by which
* the zoom should change and the point which should stand still
* on screen.
* Someone needs to connect to this and take action
*
* @param factor by how much the zoom needs to change.
* @param stillPoint the point which will not change its position
* in widget during the zooming. It is measured in
* view coordinate system *before* zoom.
*/
void zoomRelative(const qreal factor, const QPointF &stillPoint);
public Q_SLOTS:
/**
* Call this slot whenever the size of your document in view coordinates (pixels)
* changes, for instance when zooming.
* @param newSize the new size, in view coordinates (pixels), of the document.
* @param recalculateCenter if true the offset in the document we center on after calling
* recenterPreferred() will be recalculated for the new document size so the visual offset stays the same.
*/
void updateDocumentSize(const QSize &newSize, bool recalculateCenter = true);
private:
KoCanvasController *m_canvasController;
};
class KRITAFLAKE_EXPORT KoDummyCanvasController : public KoCanvasController {
public:
explicit KoDummyCanvasController(KActionCollection* actionCollection)
: KoCanvasController(actionCollection)
{}
virtual ~KoDummyCanvasController()
{}
virtual void scrollContentsBy(int /*dx*/, int /*dy*/) {}
virtual QSize viewportSize() const { return QSize(); }
virtual void setDrawShadow(bool /*drawShadow*/) {}
virtual void setCanvas(KoCanvasBase *canvas) {Q_UNUSED(canvas)}
virtual KoCanvasBase *canvas() const {return 0;}
virtual int visibleHeight() const {return 0;}
virtual int visibleWidth() const {return 0;}
virtual int canvasOffsetX() const {return 0;}
virtual int canvasOffsetY() const {return 0;}
virtual void ensureVisible(const QRectF &/*rect*/, bool /*smooth */ = false) {}
virtual void ensureVisible(KoShape *shape) {Q_UNUSED(shape)}
virtual void zoomIn(const QPoint &/*center*/) {}
virtual void zoomOut(const QPoint &/*center*/) {}
virtual void zoomBy(const QPoint &/*center*/, qreal /*zoom*/) {}
virtual void zoomTo(const QRect &/*rect*/) {}
virtual void recenterPreferred() {}
virtual void setPreferredCenter(const QPointF &/*viewPoint*/) {}
virtual QPointF preferredCenter() const {return QPointF();}
virtual void pan(const QPoint &/*distance*/) {}
virtual QPoint scrollBarValue() const {return QPoint();}
virtual void setScrollBarValue(const QPoint &/*value*/) {}
virtual void updateDocumentSize(const QSize &/*sz*/, bool /*recalculateCenter*/) {}
virtual void setZoomWithWheel(bool /*zoom*/) {}
virtual void setVastScrolling(qreal /*factor*/) {}
+ QPointF currentCursorPosition() const override { return QPointF(); }
};
#endif
diff --git a/libs/flake/KoCanvasControllerWidget.cpp b/libs/flake/KoCanvasControllerWidget.cpp
index 5ab03cf7e1..33184563af 100644
--- a/libs/flake/KoCanvasControllerWidget.cpp
+++ b/libs/flake/KoCanvasControllerWidget.cpp
@@ -1,597 +1,599 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2008-2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Peter Simonsson <peter.simonsson@gmail.com>
* Copyright (C) 2006, 2009 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006-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 "KoCanvasControllerWidget.h"
#include "KoCanvasControllerWidget_p.h"
#include "KoCanvasControllerWidgetViewport_p.h"
#include "KoShape.h"
#include "KoViewConverter.h"
#include "KoCanvasBase.h"
#include "KoCanvasObserverBase.h"
#include "KoCanvasSupervisor.h"
#include "KoToolManager_p.h"
#include <FlakeDebug.h>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QEvent>
#include <QDockWidget>
#include <QTimer>
#include <QPointer>
#include <QOpenGLWidget>
#include <math.h>
void KoCanvasControllerWidget::Private::setDocumentOffset()
{
// The margins scroll the canvas widget inside the viewport, not
// the document. The documentOffset is meant to be the value that
// the canvas must add to the update rect in its paint event, to
// compensate.
QPoint pt(q->horizontalScrollBar()->value(), q->verticalScrollBar()->value());
q->proxyObject->emitMoveDocumentOffset(pt);
QWidget *canvasWidget = canvas->canvasWidget();
if (canvasWidget) {
// If it isn't an OpenGL canvas
if (qobject_cast<QOpenGLWidget*>(canvasWidget) == 0) {
QPoint diff = q->documentOffset() - pt;
if (q->canvasMode() == Spreadsheet && canvasWidget->layoutDirection() == Qt::RightToLeft) {
canvasWidget->scroll(-diff.x(), diff.y());
} else {
canvasWidget->scroll(diff.x(), diff.y());
}
}
}
q->setDocumentOffset(pt);
}
void KoCanvasControllerWidget::Private::resetScrollBars()
{
// The scrollbar value always points at the top-left corner of the
// bit of image we paint.
int docH = q->documentSize().height() + q->margin();
int docW = q->documentSize().width() + q->margin();
int drawH = viewportWidget->height();
int drawW = viewportWidget->width();
QScrollBar *hScroll = q->horizontalScrollBar();
QScrollBar *vScroll = q->verticalScrollBar();
int horizontalReserve = vastScrollingFactor * drawW;
int verticalReserve = vastScrollingFactor * drawH;
int xMin = -horizontalReserve;
int yMin = -verticalReserve;
int xMax = docW - drawW + horizontalReserve;
int yMax = docH - drawH + verticalReserve;
hScroll->setRange(xMin, xMax);
vScroll->setRange(yMin, yMax);
int fontheight = QFontMetrics(q->font()).height();
vScroll->setPageStep(drawH);
vScroll->setSingleStep(fontheight);
hScroll->setPageStep(drawW);
hScroll->setSingleStep(fontheight);
}
void KoCanvasControllerWidget::Private::emitPointerPositionChangedSignals(QEvent *event)
{
if (!canvas) return;
if (!canvas->viewConverter()) return;
QPoint pointerPos;
QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
if (mouseEvent) {
pointerPos = mouseEvent->pos();
} else {
QTabletEvent *tabletEvent = dynamic_cast<QTabletEvent*>(event);
if (tabletEvent) {
pointerPos = tabletEvent->pos();
}
}
QPoint pixelPos = (pointerPos - canvas->documentOrigin()) + q->documentOffset();
QPointF documentPos = canvas->viewConverter()->viewToDocument(pixelPos);
q->proxyObject->emitDocumentMousePositionChanged(documentPos);
q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
}
#include <QTime>
void KoCanvasControllerWidget::Private::activate()
{
QWidget *parent = q;
while (parent->parentWidget()) {
parent = parent->parentWidget();
}
KoCanvasSupervisor *observerProvider = dynamic_cast<KoCanvasSupervisor*>(parent);
if (!observerProvider) {
return;
}
KoCanvasBase *canvas = q->canvas();
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
if (observer) {
observer->setObservedCanvas(canvas);
}
}
}
void KoCanvasControllerWidget::Private::unsetCanvas(KoCanvasBase *canvas)
{
QWidget *parent = q;
while (parent->parentWidget()) {
parent = parent->parentWidget();
}
KoCanvasSupervisor *observerProvider = dynamic_cast<KoCanvasSupervisor*>(parent);
if (!observerProvider) {
return;
}
Q_FOREACH (KoCanvasObserverBase *docker, observerProvider->canvasObservers()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
if (observer) {
if (!canvas || observer->observedCanvas() == q->canvas()) {
observer->unsetObservedCanvas();
}
}
}
}
////////////
KoCanvasControllerWidget::KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent)
: QAbstractScrollArea(parent)
, KoCanvasController(actionCollection)
, d(new Private(this))
{
// We need to set this as QDeclarativeView sets them a bit differnt from QAbstractScrollArea
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// And then our own Viewport
d->viewportWidget = new Viewport(this);
setViewport(d->viewportWidget);
d->viewportWidget->setFocusPolicy(Qt::NoFocus);
setFocusPolicy(Qt::NoFocus);
setFrameStyle(0);
//setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setAutoFillBackground(false);
/*
Fixes: apps starting at zero zoom.
Details: Since the document is set on the mainwindow before loading commences the inial show/layout can choose
to set the document to be very small, even to be zero pixels tall. Setting a sane minimum size on the
widget means we no loger get rounding errors in zooming and we no longer end up with zero-zoom.
Note: KoPage apps should probably startup with a sane document size; for Krita that's impossible
*/
setMinimumSize(QSize(50, 50));
setMouseTracking(true);
connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetX()));
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateCanvasOffsetY()));
connect(d->viewportWidget, SIGNAL(sizeChanged()), this, SLOT(updateCanvasOffsetX()));
connect(proxyObject, SIGNAL(moveDocumentOffset(const QPoint&)), d->viewportWidget, SLOT(documentOffsetMoved(const QPoint&)));
}
KoCanvasControllerWidget::~KoCanvasControllerWidget()
{
delete d;
}
void KoCanvasControllerWidget::activate()
{
d->activate();
}
void KoCanvasControllerWidget::scrollContentsBy(int dx, int dy)
{
Q_UNUSED(dx);
Q_UNUSED(dy);
d->setDocumentOffset();
}
QSize KoCanvasControllerWidget::viewportSize() const
{
return viewport()->size();
}
void KoCanvasControllerWidget::setDrawShadow(bool drawShadow)
{
d->viewportWidget->setDrawShadow(drawShadow);
}
void KoCanvasControllerWidget::resizeEvent(QResizeEvent *resizeEvent)
{
proxyObject->emitSizeChanged(resizeEvent->size());
// XXX: When resizing, keep the area we're looking at now in the
// center of the resized view.
d->resetScrollBars();
d->setDocumentOffset();
}
void KoCanvasControllerWidget::setCanvas(KoCanvasBase *canvas)
{
Q_ASSERT(canvas); // param is not null
if (d->canvas) {
d->unsetCanvas(canvas);
proxyObject->emitCanvasRemoved(this);
canvas->setCanvasController(0);
d->canvas->canvasWidget()->removeEventFilter(this);
}
canvas->setCanvasController(this);
d->canvas = canvas;
changeCanvasWidget(canvas->canvasWidget());
proxyObject->emitCanvasSet(this);
QTimer::singleShot(0, this, SLOT(activate()));
setPreferredCenterFractionX(0);
setPreferredCenterFractionY(0);
}
KoCanvasBase* KoCanvasControllerWidget::canvas() const
{
if (d->canvas.isNull()) return 0;
return d->canvas;
}
void KoCanvasControllerWidget::changeCanvasWidget(QWidget *widget)
{
if (d->viewportWidget->canvas()) {
widget->setCursor(d->viewportWidget->canvas()->cursor());
d->viewportWidget->canvas()->removeEventFilter(this);
}
d->viewportWidget->setCanvas(widget);
setFocusProxy(d->canvas->canvasWidget());
}
int KoCanvasControllerWidget::visibleHeight() const
{
if (d->canvas == 0)
return 0;
QWidget *canvasWidget = canvas()->canvasWidget();
int height1;
if (canvasWidget == 0)
height1 = viewport()->height();
else
height1 = qMin(viewport()->height(), canvasWidget->height());
int height2 = height();
return qMin(height1, height2);
}
int KoCanvasControllerWidget::visibleWidth() const
{
if (d->canvas == 0)
return 0;
QWidget *canvasWidget = canvas()->canvasWidget();
int width1;
if (canvasWidget == 0)
width1 = viewport()->width();
else
width1 = qMin(viewport()->width(), canvasWidget->width());
int width2 = width();
return qMin(width1, width2);
}
int KoCanvasControllerWidget::canvasOffsetX() const
{
int offset = -horizontalScrollBar()->value();
if (d->canvas) {
offset += d->canvas->canvasWidget()->x() + frameWidth();
}
return offset;
}
int KoCanvasControllerWidget::canvasOffsetY() const
{
int offset = -verticalScrollBar()->value();
if (d->canvas) {
offset += d->canvas->canvasWidget()->y() + frameWidth();
}
return offset;
}
void KoCanvasControllerWidget::updateCanvasOffsetX()
{
proxyObject->emitCanvasOffsetXChanged(canvasOffsetX());
if (d->ignoreScrollSignals)
return;
setPreferredCenterFractionX((horizontalScrollBar()->value()
+ viewport()->width() / 2.0) / documentSize().width());
}
void KoCanvasControllerWidget::updateCanvasOffsetY()
{
proxyObject->emitCanvasOffsetYChanged(canvasOffsetY());
if (d->ignoreScrollSignals)
return;
setPreferredCenterFractionY((verticalScrollBar()->value()
+ verticalScrollBar()->pageStep() / 2.0) / documentSize().height());
}
void KoCanvasControllerWidget::ensureVisible(KoShape *shape)
{
Q_ASSERT(shape);
ensureVisible(d->canvas->viewConverter()->documentToView(shape->boundingRect()));
}
void KoCanvasControllerWidget::ensureVisible(const QRectF &rect, bool smooth)
{
QRect currentVisible(-canvasOffsetX(), -canvasOffsetY(), visibleWidth(), visibleHeight());
QRect viewRect = rect.toRect();
viewRect.translate(d->canvas->documentOrigin());
if (!viewRect.isValid() || currentVisible.contains(viewRect))
return; // its visible. Nothing to do.
// if we move, we move a little more so the amount of times we have to move is less.
int jumpWidth = smooth ? 0 : currentVisible.width() / 5;
int jumpHeight = smooth ? 0 : currentVisible.height() / 5;
if (!smooth && viewRect.width() + jumpWidth > currentVisible.width())
jumpWidth = 0;
if (!smooth && viewRect.height() + jumpHeight > currentVisible.height())
jumpHeight = 0;
int horizontalMove = 0;
if (currentVisible.width() <= viewRect.width()) // center view
horizontalMove = viewRect.center().x() - currentVisible.center().x();
else if (currentVisible.x() > viewRect.x()) // move left
horizontalMove = viewRect.x() - currentVisible.x() - jumpWidth;
else if (currentVisible.right() < viewRect.right()) // move right
horizontalMove = viewRect.right() - qMax(0, currentVisible.right() - jumpWidth);
int verticalMove = 0;
if (currentVisible.height() <= viewRect.height()) // center view
verticalMove = viewRect.center().y() - currentVisible.center().y();
if (currentVisible.y() > viewRect.y()) // move up
verticalMove = viewRect.y() - currentVisible.y() - jumpHeight;
else if (currentVisible.bottom() < viewRect.bottom()) // move down
verticalMove = viewRect.bottom() - qMax(0, currentVisible.bottom() - jumpHeight);
pan(QPoint(horizontalMove, verticalMove));
}
void KoCanvasControllerWidget::recenterPreferred()
{
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
QPointF center = preferredCenter();
// convert into a viewport based point
center.rx() += d->canvas->canvasWidget()->x() + frameWidth();
center.ry() += d->canvas->canvasWidget()->y() + frameWidth();
// scroll to a new center point
QPointF topLeft = center - 0.5 * QPointF(viewport()->width(), viewport()->height());
setScrollBarValue(topLeft.toPoint());
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::zoomIn(const QPoint &center)
{
zoomBy(center, sqrt(2.0));
}
void KoCanvasControllerWidget::zoomOut(const QPoint &center)
{
zoomBy(center, sqrt(0.5));
}
void KoCanvasControllerWidget::zoomBy(const QPoint &center, qreal zoom)
{
setPreferredCenterFractionX(1.0 * center.x() / documentSize().width());
setPreferredCenterFractionY(1.0 * center.y() / documentSize().height());
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
proxyObject->emitZoomRelative(zoom, preferredCenter());
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::zoomTo(const QRect &viewRect)
{
qreal scale;
if (1.0 * viewport()->width() / viewRect.width() > 1.0 * viewport()->height() / viewRect.height())
scale = 1.0 * viewport()->height() / viewRect.height();
else
scale = 1.0 * viewport()->width() / viewRect.width();
zoomBy(viewRect.center(), scale);
}
void KoCanvasControllerWidget::updateDocumentSize(const QSize &sz, bool recalculateCenter)
{
// Don't update if the document-size didn't changed to prevent infinite loops and unneeded updates.
if (KoCanvasController::documentSize() == sz)
return;
if (!recalculateCenter) {
// assume the distance from the top stays equal and recalculate the center.
setPreferredCenterFractionX(documentSize().width() * preferredCenterFractionX() / sz.width());
setPreferredCenterFractionY(documentSize().height() * preferredCenterFractionY() / sz.height());
}
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
KoCanvasController::setDocumentSize(sz);
d->viewportWidget->setDocumentSize(sz);
d->resetScrollBars();
// Always emit the new offset.
updateCanvasOffsetX();
updateCanvasOffsetY();
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
void KoCanvasControllerWidget::setZoomWithWheel(bool zoom)
{
d->zoomWithWheel = zoom;
}
void KoCanvasControllerWidget::setVastScrolling(qreal factor)
{
d->vastScrollingFactor = factor;
}
+QPointF KoCanvasControllerWidget::currentCursorPosition() const
+{
+ QWidget *canvasWidget = d->canvas->canvasWidget();
+ const KoViewConverter *converter = d->canvas->viewConverter();
+ return converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + d->canvas->canvasController()->documentOffset() - canvasWidget->pos());
+}
+
void KoCanvasControllerWidget::pan(const QPoint &distance)
{
QPoint sourcePoint = scrollBarValue();
setScrollBarValue(sourcePoint + distance);
}
void KoCanvasControllerWidget::setPreferredCenter(const QPointF &viewPoint)
{
setPreferredCenterFractionX(viewPoint.x() / documentSize().width());
setPreferredCenterFractionY(viewPoint.y() / documentSize().height());
recenterPreferred();
}
QPointF KoCanvasControllerWidget::preferredCenter() const
{
QPointF center;
center.setX(preferredCenterFractionX() * documentSize().width());
center.setY(preferredCenterFractionY() * documentSize().height());
return center;
}
void KoCanvasControllerWidget::paintEvent(QPaintEvent *event)
{
QPainter gc(viewport());
d->viewportWidget->handlePaintEvent(gc, event);
}
void KoCanvasControllerWidget::dragEnterEvent(QDragEnterEvent *event)
{
d->viewportWidget->handleDragEnterEvent(event);
}
void KoCanvasControllerWidget::dropEvent(QDropEvent *event)
{
d->viewportWidget->handleDropEvent(event);
}
void KoCanvasControllerWidget::dragMoveEvent(QDragMoveEvent *event)
{
d->viewportWidget->handleDragMoveEvent(event);
}
void KoCanvasControllerWidget::dragLeaveEvent(QDragLeaveEvent *event)
{
d->viewportWidget->handleDragLeaveEvent(event);
}
-void KoCanvasControllerWidget::keyPressEvent(QKeyEvent *event)
-{
- KoToolManager::instance()->priv()->switchToolByShortcut(event);
-}
-
void KoCanvasControllerWidget::wheelEvent(QWheelEvent *event)
{
if (d->zoomWithWheel != ((event->modifiers() & Qt::ControlModifier) == Qt::ControlModifier)) {
const qreal zoomCoeff = event->delta() > 0 ? sqrt(2.0) : sqrt(0.5);
zoomRelativeToPoint(event->pos(), zoomCoeff);
event->accept();
} else
QAbstractScrollArea::wheelEvent(event);
}
void KoCanvasControllerWidget::zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff)
{
const QPoint offset = scrollBarValue();
const QPoint mousePos(widgetPoint + offset);
const bool oldIgnoreScrollSignals = d->ignoreScrollSignals;
d->ignoreScrollSignals = true;
proxyObject->emitZoomRelative(zoomCoeff, mousePos);
d->ignoreScrollSignals = oldIgnoreScrollSignals;
}
bool KoCanvasControllerWidget::focusNextPrevChild(bool)
{
// we always return false meaning the canvas takes keyboard focus, but never gives it away.
return false;
}
void KoCanvasControllerWidget::setMargin(int margin)
{
KoCanvasController::setMargin(margin);
Q_ASSERT(d->viewportWidget);
d->viewportWidget->setMargin(margin);
}
QPoint KoCanvasControllerWidget::scrollBarValue() const
{
QScrollBar * hBar = horizontalScrollBar();
QScrollBar * vBar = verticalScrollBar();
return QPoint(hBar->value(), vBar->value());
}
void KoCanvasControllerWidget::setScrollBarValue(const QPoint &value)
{
QScrollBar * hBar = horizontalScrollBar();
QScrollBar * vBar = verticalScrollBar();
hBar->setValue(value.x());
vBar->setValue(value.y());
}
KoCanvasControllerWidget::Private *KoCanvasControllerWidget::priv()
{
return d;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoCanvasControllerWidget.cpp"
diff --git a/libs/flake/KoCanvasControllerWidget.h b/libs/flake/KoCanvasControllerWidget.h
index ac716a3b1b..68d9d80f2f 100644
--- a/libs/flake/KoCanvasControllerWidget.h
+++ b/libs/flake/KoCanvasControllerWidget.h
@@ -1,184 +1,184 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2008 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007-2008 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2006-2007 Jan Hambrecht <jaham@gmx.net>
* 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 KOCANVASCONTROLLERWIDGET_H
#define KOCANVASCONTROLLERWIDGET_H
#include "kritaflake_export.h"
#include <QAbstractScrollArea>
#include <QPointer>
#include "KoCanvasController.h"
class KoShape;
class KoCanvasBase;
/**
* KoCanvasController implementation for QWidget based canvases
*/
class KRITAFLAKE_EXPORT KoCanvasControllerWidget : public QAbstractScrollArea, public KoCanvasController
{
Q_OBJECT
public:
/**
* Constructor.
* @param parent the parent this widget will belong to
*/
explicit KoCanvasControllerWidget(KActionCollection * actionCollection, QWidget *parent = 0);
virtual ~KoCanvasControllerWidget();
/**
* Reimplemented from QAbstractScrollArea.
*/
void scrollContentsBy(int dx, int dy);
virtual QSize viewportSize() const;
/// Reimplemented from KoCanvasController
/**
* Activate this canvascontroller
*/
virtual void activate();
virtual void setDrawShadow(bool drawShadow);
virtual void setCanvas(KoCanvasBase *canvas);
virtual KoCanvasBase *canvas() const;
/**
* Change the actual canvas widget used by the current canvas. This allows the canvas widget
* to be changed while keeping the current KoCanvasBase canvas and its associated resources as
* they are. This might be used, for example, to switch from a QWidget to a QOpenGLWidget canvas.
* @param widget the new canvas widget.
*/
virtual void changeCanvasWidget(QWidget *widget);
virtual int visibleHeight() const;
virtual int visibleWidth() const;
virtual int canvasOffsetX() const;
virtual int canvasOffsetY() const;
virtual void ensureVisible(const QRectF &rect, bool smooth = false);
virtual void ensureVisible(KoShape *shape);
/**
* will cause the toolOptionWidgetsChanged to be emitted and all
* listeners to be updated to the new widget.
*
* FIXME: This doesn't belong her and it does an
* inherits("KoView") so it too much tied to komain
*
* @param widgets the map of widgets
*/
void setToolOptionWidgets(const QList<QPointer<QWidget> > &widgets);
virtual void zoomIn(const QPoint &center);
virtual void zoomOut(const QPoint &center);
virtual void zoomBy(const QPoint &center, qreal zoom);
virtual void zoomTo(const QRect &rect);
/**
* Zoom document keeping point \p widgetPoint unchanged
* \param widgetPoint sticky point in widget pixels
*/
virtual void zoomRelativeToPoint(const QPoint &widgetPoint, qreal zoomCoeff);
virtual void recenterPreferred();
virtual void setPreferredCenter(const QPointF &viewPoint);
/// Returns the currently set preferred center point in view coordinates (pixels)
virtual QPointF preferredCenter() const;
virtual void pan(const QPoint &distance);
virtual void setMargin(int margin);
virtual QPoint scrollBarValue() const;
/**
* Used by KisCanvasController to correct the scrollbars position
* after the rotation.
*/
virtual void setScrollBarValue(const QPoint &value);
virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter = true);
/**
* Set mouse wheel to zoom behaviour
* @param zoom if true wheel will zoom instead of scroll, control modifier will scroll
*/
void setZoomWithWheel(bool zoom);
virtual void setVastScrolling(qreal factor);
+ QPointF currentCursorPosition() const override;
+
/**
* \internal
*/
class Private;
KoCanvasControllerWidget::Private *priv();
private Q_SLOTS:
/// Called by the horizontal scrollbar when its value changes
void updateCanvasOffsetX();
/// Called by the vertical scrollbar when its value changes
void updateCanvasOffsetY();
protected:
friend class KisZoomAndPanTest;
/// reimplemented from QWidget
virtual void paintEvent(QPaintEvent *event);
/// reimplemented from QWidget
virtual void resizeEvent(QResizeEvent *resizeEvent);
/// reimplemented from QWidget
virtual void dragEnterEvent(QDragEnterEvent *event);
/// reimplemented from QWidget
virtual void dropEvent(QDropEvent *event);
/// reimplemented from QWidget
virtual void dragMoveEvent(QDragMoveEvent *event);
/// reimplemented from QWidget
virtual void dragLeaveEvent(QDragLeaveEvent *event);
/// reimplemented from QWidget
virtual void wheelEvent(QWheelEvent *event);
/// reimplemented from QWidget
- virtual void keyPressEvent(QKeyEvent *event);
- /// reimplemented from QWidget
virtual bool focusNextPrevChild(bool next);
private:
Q_PRIVATE_SLOT(d, void activate())
Private * const d;
};
#endif
diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
index edb24195b7..7cc545349e 100644
--- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
+++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
@@ -1,384 +1,373 @@
/* 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 "KoShapePaste.h"
#include "KoShapePaintingContext.h"
#include "KoToolProxy.h"
#include "KoCanvasControllerWidget.h"
#include "KoViewConverter.h"
// ********** Viewport **********
Viewport::Viewport(KoCanvasControllerWidget *parent)
: QWidget(parent)
, m_draggedShape(0)
, m_drawShadow(false)
, 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 QSize &size)
{
m_documentSize = size;
resetLayout();
}
void Viewport::documentOffsetMoved(const QPoint &pt)
{
m_documentOffset = pt;
resetLayout();
}
void Viewport::setDrawShadow(bool drawShadow)
{
m_drawShadow = drawShadow;
}
-
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()))
+ if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) {
+ event->ignore();
return;
+ }
// only allow dropping when active layer is editable
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
KoShapeLayer *activeLayer = selection->activeLayer();
- if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected()))
+ if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected())) {
+ event->ignore();
return;
+ }
const QMimeData *data = event->mimeData();
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) ||
data->hasFormat(SHAPEID_MIMETYPE)) {
QByteArray itemData;
bool isTemplate = true;
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE))
itemData = data->data(SHAPETEMPLATE_MIMETYPE);
else {
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;
}
event->setDropAction(Qt::CopyAction);
event->accept();
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());
Q_ASSERT(m_draggedShape);
if (!m_draggedShape) return;
if (m_draggedShape->shapeId().isEmpty())
m_draggedShape->setShapeId(factory->id());
m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_parent->canvas()->shapeManager()->addShape(m_draggedShape);
- }
- else if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) {
- KoShapeManager *sm = m_parent->canvas()->shapeManager();
- KoShapePaste paste(m_parent->canvas(), sm->selection()->activeLayer());
- if (paste.paste(KoOdf::Text, data)) {
- QList<KoShape *> shapes = paste.pastedShapes();
- if (shapes.count() == 1) {
- m_draggedShape = shapes.first();
- m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex);
- event->setDropAction(Qt::CopyAction);
- }
- event->accept();
- }
} 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);
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);
// Draw the shadow around the canvas.
if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) {
QWidget *canvas = m_parent->canvas()->canvasWidget();
painter.setPen(QPen(Qt::black, 0));
QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height());
rect.adjust(-1, -1, 0, 0);
painter.drawRect(rect);
painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2);
painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2);
}
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.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();
const int docH = m_documentSize.height();
const int docW = m_documentSize.width();
int moveX = 0;
int moveY = 0;
int resizeW = viewW;
int resizeH = viewH;
// debugFlake <<"viewH:" << viewH << endl
// << "docH: " << docH << endl
// << "viewW: " << viewW << endl
// << "docW: " << docW << endl;
if (viewH == docH && viewW == docW) {
// Do nothing
resizeW = docW;
resizeH = docH;
} else if (viewH > docH && viewW > docW) {
// Show entire canvas centered
moveX = (viewW - docW) / 2;
moveY = (viewH - docH) / 2;
resizeW = docW;
resizeH = docH;
} else if (viewW > docW) {
// Center canvas horizontally
moveX = (viewW - docW) / 2;
resizeW = docW;
int marginTop = m_margin - m_documentOffset.y();
int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y());
if (marginTop > 0) moveY = marginTop;
if (marginTop > 0) resizeH = viewH - marginTop;
if (marginBottom > 0) resizeH = viewH - marginBottom;
} else if (viewH > docH) {
// Center canvas vertically
moveY = (viewH - docH) / 2;
resizeH = docH;
int marginLeft = m_margin - m_documentOffset.x();
int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x());
if (marginLeft > 0) moveX = marginLeft;
if (marginLeft > 0) resizeW = viewW - marginLeft;
if (marginRight > 0) resizeW = viewW - marginRight;
} else {
// Take care of the margin around the canvas
int marginTop = m_margin - m_documentOffset.y();
int marginLeft = m_margin - m_documentOffset.x();
int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x());
int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y());
if (marginTop > 0) moveY = marginTop;
if (marginLeft > 0) moveX = marginLeft;
if (marginTop > 0) resizeH = viewH - marginTop;
if (marginLeft > 0) resizeW = viewW - marginLeft;
if (marginRight > 0) resizeW = viewW - marginRight;
if (marginBottom > 0) resizeH = viewH - marginBottom;
}
if (m_parent->canvasMode() == KoCanvasController::AlignTop) {
// have up to m_margin pixels at top.
moveY = qMin(m_margin, moveY);
}
if (m_canvas) {
QRect geom;
if (m_parent->canvasMode() == KoCanvasController::Infinite)
geom = QRect(0, 0, viewW, viewH);
else
geom = QRect(moveX, moveY, resizeW, resizeH);
if (m_canvas->geometry() != geom) {
m_canvas->setGeometry(geom);
m_canvas->update();
}
}
if (m_drawShadow) {
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/KoCanvasResourceManager.cpp b/libs/flake/KoCanvasResourceManager.cpp
index 79b3a93279..3ae7cfcb44 100644
--- a/libs/flake/KoCanvasResourceManager.cpp
+++ b/libs/flake/KoCanvasResourceManager.cpp
@@ -1,197 +1,180 @@
/*
Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
Copyright (C) 2007, 2010 Thomas Zander <zander@kde.org>
Copyright (c) 2008 Carlos Licea <carlos.licea@kdemail.net>
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 "KoCanvasResourceManager.h"
#include <QVariant>
#include <FlakeDebug.h>
#include "KoShape.h"
#include "KoShapeStroke.h"
#include "KoResourceManager_p.h"
#include <KoColorSpaceRegistry.h>
class Q_DECL_HIDDEN KoCanvasResourceManager::Private
{
public:
KoResourceManager manager;
};
KoCanvasResourceManager::KoCanvasResourceManager(QObject *parent)
: QObject(parent)
, d(new Private())
{
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
setForegroundColor(KoColor(Qt::black, cs));
setBackgroundColor(KoColor(Qt::white, cs));
setResource(ApplicationSpeciality, NoSpecial);
connect(&d->manager, &KoResourceManager::resourceChanged,
this, &KoCanvasResourceManager::canvasResourceChanged);
}
KoCanvasResourceManager::~KoCanvasResourceManager()
{
delete d;
}
void KoCanvasResourceManager::setResource(int key, const QVariant &value)
{
d->manager.setResource(key, value);
}
QVariant KoCanvasResourceManager::resource(int key) const
{
return d->manager.resource(key);
}
void KoCanvasResourceManager::setResource(int key, const KoColor &color)
{
QVariant v;
v.setValue(color);
setResource(key, v);
}
void KoCanvasResourceManager::setResource(int key, KoShape *shape)
{
QVariant v;
v.setValue(shape);
setResource(key, v);
}
void KoCanvasResourceManager::setResource(int key, const KoUnit &unit)
{
QVariant v;
v.setValue(unit);
setResource(key, v);
}
KoColor KoCanvasResourceManager::koColorResource(int key) const
{
return d->manager.koColorResource(key);
}
void KoCanvasResourceManager::setForegroundColor(const KoColor &color)
{
setResource(ForegroundColor, color);
}
KoColor KoCanvasResourceManager::foregroundColor() const
{
return koColorResource(ForegroundColor);
}
void KoCanvasResourceManager::setBackgroundColor(const KoColor &color)
{
setResource(BackgroundColor, color);
}
KoColor KoCanvasResourceManager::backgroundColor() const
{
return koColorResource(BackgroundColor);
}
KoShape *KoCanvasResourceManager::koShapeResource(int key) const
{
return d->manager.koShapeResource(key);
}
KoUnit KoCanvasResourceManager::unitResource(int key) const
{
return resource(key).value<KoUnit>();
}
-
-void KoCanvasResourceManager::setActiveStroke(const KoShapeStroke &stroke)
-{
- QVariant v;
- v.setValue(stroke);
- setResource(ActiveStroke, v);
-}
-
-KoShapeStroke KoCanvasResourceManager::activeStroke() const
-{
- if (!d->manager.hasResource(ActiveStroke)) {
- KoShapeStroke empty;
- return empty;
- }
- return resource(ActiveStroke).value<KoShapeStroke>();
-}
-
bool KoCanvasResourceManager::boolResource(int key) const
{
return d->manager.boolResource(key);
}
int KoCanvasResourceManager::intResource(int key) const
{
return d->manager.intResource(key);
}
QString KoCanvasResourceManager::stringResource(int key) const
{
return d->manager.stringResource(key);
}
QSizeF KoCanvasResourceManager::sizeResource(int key) const
{
return d->manager.sizeResource(key);
}
bool KoCanvasResourceManager::hasResource(int key) const
{
return d->manager.hasResource(key);
}
void KoCanvasResourceManager::clearResource(int key)
{
d->manager.clearResource(key);
}
void KoCanvasResourceManager::addDerivedResourceConverter(KoDerivedResourceConverterSP converter)
{
d->manager.addDerivedResourceConverter(converter);
}
bool KoCanvasResourceManager::hasDerivedResourceConverter(int key)
{
return d->manager.hasDerivedResourceConverter(key);
}
void KoCanvasResourceManager::removeDerivedResourceConverter(int key)
{
d->manager.removeDerivedResourceConverter(key);
}
void KoCanvasResourceManager::addResourceUpdateMediator(KoResourceUpdateMediatorSP mediator)
{
d->manager.addResourceUpdateMediator(mediator);
}
bool KoCanvasResourceManager::hasResourceUpdateMediator(int key)
{
return d->manager.hasResourceUpdateMediator(key);
}
void KoCanvasResourceManager::removeResourceUpdateMediator(int key)
{
d->manager.removeResourceUpdateMediator(key);
}
diff --git a/libs/flake/KoCanvasResourceManager.h b/libs/flake/KoCanvasResourceManager.h
index 9060cdb3d1..c66a250d8d 100644
--- a/libs/flake/KoCanvasResourceManager.h
+++ b/libs/flake/KoCanvasResourceManager.h
@@ -1,288 +1,281 @@
/*
Copyright (c) 2006, 2011 Boudewijn Rempt (boud@valdyas.org)
Copyright (C) 2007, 2009, 2010 Thomas Zander <zander@kde.org>
Copyright (c) 2008 Carlos Licea <carlos.licea@kdemail.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 KO_CANVASRESOURCEMANAGER_H
#define KO_CANVASRESOURCEMANAGER_H
#include <QObject>
#include "kritaflake_export.h"
#include "KoDerivedResourceConverter.h"
#include "KoResourceUpdateMediator.h"
class KoShape;
class KoShapeStroke;
class KoColor;
class KoUnit;
class QVariant;
class QSizeF;
/**
* The KoCanvasResourceManager contains a set of per-canvas
* properties, like current foreground color, current background
* color and more. All tools belonging to the current canvas are
* notified when a Resource changes (is set).
*
* The manager can contain all sorts of variable types and there are accessors
* for the most common ones. All variables are always stored inside a QVariant
* instance internally and you can always just use the resource() method to get
* that directly.
* The way to store arbitairy data objects that are stored as pointers you can use
* the following code snippets;
* @code
* QVariant variant;
* variant.setValue<void*>(textShapeData->document());
* resourceManager->setResource(KoText::CurrentTextDocument, variant);
* // and get it out again.
* QVariant var = resourceManager->resource(KoText::CurrentTextDocument);
* document = static_cast<QTextDocument*>(var.value<void*>());
* @endcode
*/
class KRITAFLAKE_EXPORT KoCanvasResourceManager : public QObject
{
Q_OBJECT
public:
/**
* This enum holds identifiers to the resources that can be stored in here.
*/
enum CanvasResource {
ForegroundColor, ///< The active forground color selected for this canvas.
BackgroundColor, ///< The active background color selected for this canvas.
- ActiveStroke, ///< The active stroke selected for this canvas
PageSize, ///< The size of the (current) page in postscript points.
Unit, ///< The unit of this canvas
CurrentPage, ///< The current page number
ActiveStyleType, ///< the actual active style type see KoFlake::StyleType for valid values
ActiveRange, ///< The area where the rulers should show white
ShowTextShapeOutlines, ///< Paint of text shape outlines ?
ShowFormattingCharacters, ///< Paint of formatting characters ?
ShowTableBorders, ///< Paint of table borders (when not really there) ?
ShowSectionBounds, ///< Paint of sections bounds ?
ShowInlineObjectVisualization, ///< paint a different background for inline objects
ApplicationSpeciality, ///< Special features and limitations of the application
KarbonStart = 1000, ///< Base number for Karbon specific values.
KexiStart = 2000, ///< Base number for Kexi specific values.
FlowStart = 3000, ///< Base number for Flow specific values.
PlanStart = 4000, ///< Base number for Plan specific values.
StageStart = 5000, ///< Base number for Stage specific values.
KritaStart = 6000, ///< Base number for Krita specific values.
SheetsStart = 7000, ///< Base number for Sheets specific values.
WordsStart = 8000, ///< Base number for Words specific values.
KoPageAppStart = 9000 ///< Base number for KoPageApp specific values.
};
enum ApplicationSpecial {
NoSpecial = 0,
NoAdvancedText = 1
};
/**
* Constructor.
* @param parent the parent QObject, used for memory management.
*/
explicit KoCanvasResourceManager(QObject *parent = 0);
virtual ~KoCanvasResourceManager();
public Q_SLOTS:
/**
* Set a resource of any type.
* @param key the integer key
* @param value the new value for the key.
* @see KoCanvasResourceManager::CanvasResource
*/
void setResource(int key, const QVariant &value);
/**
* Set a resource of type KoColor.
* @param key the integer key
* @param color the new value for the key.
* @see KoCanvasResourceManager::CanvasResource
*/
void setResource(int key, const KoColor &color);
/**
* Set a resource of type KoShape*.
* @param key the integer key
* @param id the new value for the key.
* @see KoCanvasResourceManager::CanvasResource
*/
void setResource(int key, KoShape *shape);
/**
* Set a resource of type KoUnit
* @param key the integer key
* @param id the new value for the key.
* @see KoCanvasResourceManager::CanvasResource
*/
void setResource(int key, const KoUnit &unit);
public:
/**
* Returns a qvariant containing the specified resource or a standard one if the
* specified resource does not exist.
* @param key the key
* @see KoCanvasResourceManager::CanvasResource
*/
QVariant resource(int key) const;
/**
* Set the foregroundColor resource.
* @param color the new foreground color
*/
void setForegroundColor(const KoColor &color);
/**
* Return the foregroundColor
*/
KoColor foregroundColor() const;
/**
* Set the backgroundColor resource.
* @param color the new background color
*/
void setBackgroundColor(const KoColor &color);
/**
* Return the backgroundColor
*/
KoColor backgroundColor() const;
- /// Sets the stroke resource
- void setActiveStroke(const KoShapeStroke &stroke);
-
- /// Returns the stroke resource
- KoShapeStroke activeStroke() const;
-
/**
* Return the resource determined by param key as a boolean.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
bool boolResource(int key) const;
/**
* Return the resource determined by param key as an integer.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
int intResource(int key) const;
/**
* Return the resource determined by param key as a KoColor.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
KoColor koColorResource(int key) const;
/**
* Return the resource determined by param key as a pointer to a KoShape.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
KoShape *koShapeResource(int key) const;
/**
* Return the resource determined by param key as a QString .
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
QString stringResource(int key) const;
/**
* Return the resource determined by param key as a QSizeF.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
QSizeF sizeResource(int key) const;
/**
* Return the resource determined by param key as a KoUnit.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
KoUnit unitResource(int key) const;
/**
* Returns true if there is a resource set with the requested key.
* @param key the indentifying key for the resource
* @see KoCanvasResourceManager::CanvasResource
*/
bool hasResource(int key) const;
/**
* Remove the resource with @p key from the provider.
* @param key the key that will be used to remove the resource
* There will be a signal emitted with a variable that will return true on QVariable::isNull();
* @see KoCanvasResourceManager::CanvasResource
*/
void clearResource(int key);
/**
* @see KoReosurceManager::addDerivedResourceConverter()
*/
void addDerivedResourceConverter(KoDerivedResourceConverterSP converter);
/**
* @see KoReosurceManager::hasDerivedResourceConverter()
*/
bool hasDerivedResourceConverter(int key);
/**
* @see KoReosurceManager::removeDerivedResourceConverter()
*/
void removeDerivedResourceConverter(int key);
/**
* @see KoReosurceManager::addResourceUpdateMediator
*/
void addResourceUpdateMediator(KoResourceUpdateMediatorSP mediator);
/**
* @see KoReosurceManager::hasResourceUpdateMediator
*/
bool hasResourceUpdateMediator(int key);
/**
* @see KoReosurceManager::removeResourceUpdateMediator
*/
void removeResourceUpdateMediator(int key);
Q_SIGNALS:
/**
* This signal is emitted every time a resource is set that is either
* new or different from the previous set value.
* @param key the indentifying key for the resource
* @param value the variants new value.
* @see KoCanvasResourceManager::CanvasResource
*/
void canvasResourceChanged(int key, const QVariant &value);
private:
KoCanvasResourceManager(const KoCanvasResourceManager&);
KoCanvasResourceManager& operator=(const KoCanvasResourceManager&);
class Private;
Private *const d;
};
#endif
diff --git a/libs/flake/KoClipMask.cpp b/libs/flake/KoClipMask.cpp
new file mode 100644
index 0000000000..8a21648f32
--- /dev/null
+++ b/libs/flake/KoClipMask.cpp
@@ -0,0 +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.
+ */
+
+#include "KoClipMask.h"
+
+#include <QRectF>
+#include <QTransform>
+#include <QPainter>
+#include <KoShape.h>
+#include "kis_algebra_2d.h"
+
+#include <KoViewConverter.h>
+#include <KoShapePainter.h>
+
+struct Q_DECL_HIDDEN KoClipMask::Private {
+ Private() {}
+ Private(const Private &rhs)
+ : 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(const KoClipMask &rhs)
+ : m_d(new Private(*rhs.m_d))
+{
+}
+
+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);
+
+ painter->restore();
+}
diff --git a/libs/flake/KoClipMask.h b/libs/flake/KoClipMask.h
new file mode 100644
index 0000000000..e66af53d35
--- /dev/null
+++ b/libs/flake/KoClipMask.h
@@ -0,0 +1,68 @@
+/*
+ * 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 KOCLIPMASK_H
+#define KOCLIPMASK_H
+
+#include "kritaflake_export.h"
+
+#include <KoFlakeCoordinateSystem.h>
+#include <QScopedPointer>
+#include <QList>
+
+class KoShape;
+class QRectF;
+class QTransform;
+class QPointF;
+class QPainter;
+
+
+class KRITAFLAKE_EXPORT KoClipMask
+{
+public:
+ KoClipMask();
+ ~KoClipMask();
+
+ KoClipMask *clone() const;
+
+ KoFlake::CoordinateSystem coordinates() const;
+ void setCoordinates(KoFlake::CoordinateSystem value);
+
+ KoFlake::CoordinateSystem contentCoordinates() const;
+ void setContentCoordinates(KoFlake::CoordinateSystem value);
+
+ QRectF maskRect() const;
+ void setMaskRect(const QRectF &value);
+
+ QList<KoShape *> shapes() const;
+ void setShapes(const QList<KoShape *> &value);
+
+ bool isEmpty() const;
+
+ void setExtraShapeOffset(const QPointF &value);
+
+ void drawMask(QPainter *painter, KoShape *shape);
+
+private:
+ KoClipMask(const KoClipMask &rhs);
+
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KOCLIPMASK_H
diff --git a/libs/flake/KoClipMaskPainter.cpp b/libs/flake/KoClipMaskPainter.cpp
new file mode 100644
index 0000000000..4fa456c825
--- /dev/null
+++ b/libs/flake/KoClipMaskPainter.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 "KoClipMaskPainter.h"
+
+#include <QPainter>
+#include <QRectF>
+
+#include "kis_assert.h"
+
+struct Q_DECL_HIDDEN KoClipMaskPainter::Private
+{
+ QPainter *globalPainter;
+
+ QImage shapeImage;
+ QImage maskImage;
+
+ QPainter shapePainter;
+ QPainter maskPainter;
+
+ QRect alignedGlobalClipRect;
+};
+
+KoClipMaskPainter::KoClipMaskPainter(QPainter *painter, const QRectF &globalClipRect)
+ : m_d(new Private)
+{
+ m_d->globalPainter = painter;
+ m_d->alignedGlobalClipRect = globalClipRect.toAlignedRect();
+
+ m_d->shapeImage = QImage(m_d->alignedGlobalClipRect.size(), QImage::Format_ARGB32);
+ m_d->maskImage = QImage(m_d->alignedGlobalClipRect.size(), QImage::Format_ARGB32);
+
+ m_d->shapeImage.fill(0);
+ m_d->maskImage.fill(0);
+
+ QTransform moveToBufferTransform =
+ QTransform::fromTranslate(-m_d->alignedGlobalClipRect.x(),
+ -m_d->alignedGlobalClipRect.y());
+
+ m_d->shapePainter.begin(&m_d->shapeImage);
+ m_d->shapePainter.setTransform(moveToBufferTransform);
+ m_d->shapePainter.setTransform(painter->transform(), true);
+ if (painter->hasClipping()) {
+ m_d->shapePainter.setClipPath(painter->clipPath());
+ }
+ m_d->shapePainter.setOpacity(painter->opacity());
+ m_d->shapePainter.setBrush(painter->brush());
+ m_d->shapePainter.setPen(painter->pen());
+
+ m_d->maskPainter.begin(&m_d->maskImage);
+ m_d->maskPainter.setTransform(moveToBufferTransform);
+ m_d->maskPainter.setTransform(painter->transform(), true);
+ if (painter->hasClipping()) {
+ m_d->maskPainter.setClipPath(painter->clipPath());
+ }
+ m_d->maskPainter.setOpacity(painter->opacity());
+ m_d->maskPainter.setBrush(painter->brush());
+ m_d->maskPainter.setPen(painter->pen());
+}
+
+KoClipMaskPainter::~KoClipMaskPainter()
+{
+}
+
+QPainter *KoClipMaskPainter::shapePainter()
+{
+ return &m_d->shapePainter;
+}
+
+QPainter *KoClipMaskPainter::maskPainter()
+{
+ return &m_d->maskPainter;
+}
+
+void KoClipMaskPainter::renderOnGlobalPainter()
+{
+ KIS_ASSERT_RECOVER_RETURN(m_d->maskImage.size() == m_d->shapeImage.size());
+
+ for (int y = 0; y < m_d->maskImage.height(); y++) {
+ QRgb *shapeData = reinterpret_cast<QRgb*>(m_d->shapeImage.scanLine(y));
+ QRgb *maskData = reinterpret_cast<QRgb*>(m_d->maskImage.scanLine(y));
+
+ for (int x = 0; x < m_d->maskImage.width(); x++) {
+
+ const qreal normCoeff = 1.0 / 255.0 * 255.0;
+
+ qreal maskValue = qreal(qAlpha(*maskData)) *
+ (0.2125 * qRed(*maskData) +
+ 0.7154 * qGreen(*maskData) +
+ 0.0721 * qBlue(*maskData));
+
+ int alpha = qRound(maskValue * qAlpha(*shapeData) * normCoeff);
+
+ *shapeData = (alpha << 24) | (*shapeData & 0x00ffffff);
+
+ shapeData++;
+ maskData++;
+ }
+ }
+
+ KIS_ASSERT_RECOVER_RETURN(m_d->shapeImage.size() == m_d->alignedGlobalClipRect.size());
+ QPainterPath globalClipPath;
+
+ if (m_d->globalPainter->hasClipping()) {
+ globalClipPath = m_d->globalPainter->transform().map(m_d->globalPainter->clipPath());
+ }
+
+ m_d->globalPainter->save();
+
+ m_d->globalPainter->setTransform(QTransform());
+
+ if (!globalClipPath.isEmpty()) {
+ m_d->globalPainter->setClipPath(globalClipPath);
+ }
+
+ m_d->globalPainter->drawImage(m_d->alignedGlobalClipRect.topLeft(), m_d->shapeImage);
+ m_d->globalPainter->restore();
+}
+
diff --git a/libs/flake/KoClipMaskPainter.h b/libs/flake/KoClipMaskPainter.h
new file mode 100644
index 0000000000..253eca9c1a
--- /dev/null
+++ b/libs/flake/KoClipMaskPainter.h
@@ -0,0 +1,46 @@
+/*
+ * 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 KOCLIPMASKPAINTER_H
+#define KOCLIPMASKPAINTER_H
+
+#include "kritaflake_export.h"
+
+#include <QScopedPointer>
+
+class QPainter;
+class QRectF;
+
+
+class KRITAFLAKE_EXPORT KoClipMaskPainter
+{
+public:
+ KoClipMaskPainter(QPainter *painter, const QRectF &globalClipRect);
+ ~KoClipMaskPainter();
+
+ QPainter* shapePainter();
+ QPainter* maskPainter();
+
+ void renderOnGlobalPainter();
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KOCLIPMASKPAINTER_H
diff --git a/libs/flake/KoClipPath.cpp b/libs/flake/KoClipPath.cpp
index 3dbda5c4a2..97e19d4211 100644
--- a/libs/flake/KoClipPath.cpp
+++ b/libs/flake/KoClipPath.cpp
@@ -1,207 +1,240 @@
/* 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 <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(100/w, 100/h);
+ 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/100, h/100);
+ return QTransform().scale(w/1.0, h/1.0);
}
-class Q_DECL_HIDDEN KoClipData::Private
+class Q_DECL_HIDDEN KoClipPath::Private
{
public:
- Private() : deleteClipShapes(true)
+ Private()
+ {}
+
+ Private(const Private &rhs)
+ : 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()
{
- if (deleteClipShapes)
- qDeleteAll(clipPathShapes);
+ qDeleteAll(shapes);
+ shapes.clear();
}
- QList<KoPathShape*> clipPathShapes;
- bool deleteClipShapes;
-};
-
-KoClipData::KoClipData(KoPathShape *clipPathShape)
- : d(new Private())
-{
- Q_ASSERT(clipPathShape);
- d->clipPathShapes.append(clipPathShape);
-}
-
-KoClipData::KoClipData(const QList<KoPathShape*> &clipPathShapes)
- : d(new Private())
-{
- Q_ASSERT(clipPathShapes.count());
- d->clipPathShapes = clipPathShapes;
-}
-
-KoClipData::~KoClipData()
-{
- delete d;
-}
-
-QList<KoPathShape*> KoClipData::clipPathShapes() const
-{
- return d->clipPathShapes;
-}
-
-void KoClipData::removeClipShapesOwnership()
-{
- d->deleteClipShapes = false;
-}
-
-class Q_DECL_HIDDEN KoClipPath::Private
-{
-public:
- Private(KoClipData *data)
- : clipData(data)
- {}
-
- ~Private()
- {
+ 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);
+ result->addPath(t.map(pathShape->outline()));
+ } else if (const KoShapeGroup *groupShape = dynamic_cast<const KoShapeGroup*>(shape)) {
+ QList<KoShape*> shapes = groupShape->shapes();
+ qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
+
+ Q_FOREACH (const KoShape *child, shapes) {
+ collectShapePath(result, child);
+ }
+ }
}
- void compileClipPath(KoShape *clippedShape)
+
+ void compileClipPath()
{
- QList<KoPathShape*> clipShapes = clipData->clipPathShapes();
- if (!clipShapes.count())
+ QList<KoShape*> clipShapes = this->shapes;
+ if (clipShapes.isEmpty())
return;
- initialShapeSize = clippedShape->outline().boundingRect().size();
- initialTransformToShape = clippedShape->absoluteTransformation(0).inverted();
+ clipPath = QPainterPath();
+ clipPath.setFillRule(Qt::WindingFill);
+
+ qSort(clipShapes.begin(), clipShapes.end(), KoShape::compareShapeZIndex);
- QTransform transformToShape = initialTransformToShape * scaleToPercent(initialShapeSize);
+ Q_FOREACH (KoShape *path, clipShapes) {
+ if (!path) continue;
- Q_FOREACH (KoPathShape *path, clipShapes) {
- if (!path)
- continue;
- // map clip path to shape coordinates of clipped shape
- QTransform m = path->absoluteTransformation(0) * transformToShape;
- if (clipPath.isEmpty())
- clipPath = m.map(path->outline());
- else
- clipPath |= m.map(path->outline());
+ collectShapePath(&clipPath, path);
}
}
- QExplicitlySharedDataPointer<KoClipData> clipData; ///< the clip path data
+ 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(KoShape *clippedShape, KoClipData *clipData)
- : d( new Private(clipData) )
+KoClipPath::KoClipPath(QList<KoShape*> clipShapes, KoFlake::CoordinateSystem coordinates)
+ : d(new Private())
+{
+ d->shapes = clipShapes;
+ d->coordinates = coordinates;
+ d->compileClipPath();
+}
+
+KoClipPath::KoClipPath(const KoClipPath &rhs)
+ : d(new Private(*rhs.d))
{
- d->compileClipPath(clippedShape);
}
KoClipPath::~KoClipPath()
{
- delete d;
+}
+
+KoClipPath *KoClipPath::clone() const
+{
+ return new KoClipPath(*this);
}
void KoClipPath::setClipRule(Qt::FillRule clipRule)
{
- d->clipPath.setFillRule(clipRule);
+ d->clipRule = clipRule;
}
Qt::FillRule KoClipPath::clipRule() const
{
- return d->clipPath.fillRule();
+ return d->clipRule;
+}
+
+KoFlake::CoordinateSystem KoClipPath::coordinates() const
+{
+ return d->coordinates;
}
void KoClipPath::applyClipping(KoShape *clippedShape, QPainter &painter, const KoViewConverter &converter)
{
QPainterPath clipPath;
KoShape *shape = clippedShape;
while (shape) {
if (shape->clipPath()) {
- QTransform m = scaleFromPercent(shape->outline().boundingRect().size()) * shape->absoluteTransformation(0);
- if (clipPath.isEmpty())
- clipPath = m.map(shape->clipPath()->path());
- else
- clipPath |= m.map(shape->clipPath()->path());
+ 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;
+ }
}
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);
}
}
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
{
- return d->clipData->clipPathShapes();
+ // 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);
// 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 transfromation
// 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 9b54924c97..ffa7011dbd 100644
--- a/libs/flake/KoClipPath.h
+++ b/libs/flake/KoClipPath.h
@@ -1,105 +1,93 @@
/* 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 <QSharedData>
#include <qnamespace.h>
+#include <KoFlakeCoordinateSystem.h>
class KoShape;
class KoPathShape;
class KoViewConverter;
class QPainter;
class QTransform;
class QPainterPath;
class QSizeF;
-/// Shared clip path data
-class KRITAFLAKE_EXPORT KoClipData : public QSharedData
-{
-public:
- /// Creates clip path data from a single path shape, takes ownership of the path shape
- explicit KoClipData(KoPathShape *clipPathShape);
-
- /// Creates clip path data from multiple path shapes, takes ownership of the path shapes
- explicit KoClipData(const QList<KoPathShape*> &clipPathShapes);
-
- /// Destroys the clip path data
- ~KoClipData();
-
- /// Returns the clip path shapes
- QList<KoPathShape*> clipPathShapes() const;
-
- /// Gives up ownership of clip path shapes
- void removeClipShapesOwnership();
-
-private:
- class Private;
- Private * const d;
-};
-
/// Clip path used to clip shapes
class KRITAFLAKE_EXPORT KoClipPath
{
public:
+
/**
* Create a new shape clipping using the given clip data
- * @param clippedShape the shape to clip
- * @param clipData shared clipping data containing the clip paths
+ * @param clipShapes define the clipping shapes, owned by KoClipPath!
+ * @param coordinates shows if ObjectBoundingBox or UserSpaceOnUse coordinate
+ * system is used.
*/
- KoClipPath(KoShape *clippedShape, KoClipData *clipData);
-
+ 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);
+private:
+ KoClipPath(const KoClipPath &rhs);
+
private:
class Private;
- Private * const d;
+ const QScopedPointer<Private> d;
};
#endif // KOCLIPPATH_H
diff --git a/libs/flake/KoColorBackground.cpp b/libs/flake/KoColorBackground.cpp
index ae906b758e..80d449ada6 100644
--- a/libs/flake/KoColorBackground.cpp
+++ b/libs/flake/KoColorBackground.cpp
@@ -1,102 +1,110 @@
/* 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 "KoColorBackground_p.h"
#include "KoShapeSavingContext.h"
#include <KoOdfGraphicStyles.h>
#include <KoOdfLoadingContext.h>
#include <KoXmlNS.h>
#include <KoStyleStack.h>
#include <QColor>
#include <QPainter>
KoColorBackground::KoColorBackground()
: KoShapeBackground(*(new KoColorBackgroundPrivate()))
{
}
KoColorBackground::KoColorBackground(KoShapeBackgroundPrivate &dd)
: KoShapeBackground(dd)
{
}
KoColorBackground::KoColorBackground(const QColor &color, Qt::BrushStyle style)
: KoShapeBackground(*(new KoColorBackgroundPrivate()))
{
Q_D(KoColorBackground);
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
+{
+ Q_D(const KoColorBackground);
+
+ const KoColorBackground *bg = dynamic_cast<const KoColorBackground*>(other);
+ return bg && bg->color() == d->color;
+}
+
QColor KoColorBackground::color() const
{
Q_D(const KoColorBackground);
return d->color;
}
void KoColorBackground::setColor(const QColor &color)
{
Q_D(KoColorBackground);
d->color = color;
}
Qt::BrushStyle KoColorBackground::style() const
{
Q_D(const KoColorBackground);
return d->style;
}
void KoColorBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
{
Q_D(const KoColorBackground);
painter.setBrush(QBrush(d->color, d->style));
painter.drawPath(fillPath);
}
void KoColorBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
Q_D(KoColorBackground);
KoOdfGraphicStyles::saveOdfFillStyle(style, context.mainStyles(), QBrush(d->color, d->style));
}
bool KoColorBackground::loadStyle(KoOdfLoadingContext & context, const QSizeF &)
{
Q_D(KoColorBackground);
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 2530d04aa0..b406c567b7 100644
--- a/libs/flake/KoColorBackground.h
+++ b/libs/flake/KoColorBackground.h
@@ -1,63 +1,65 @@
/* 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>
class KoColorBackgroundPrivate;
class QColor;
/// 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);
virtual ~KoColorBackground();
+ 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;
// reimplemented from KoShapeBackground
virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const;
// reimplemented from KoShapeBackground
virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context);
// reimplemented from KoShapeBackground
virtual bool loadStyle(KoOdfLoadingContext & context, const QSizeF &shapeSize);
protected:
KoColorBackground(KoShapeBackgroundPrivate &dd);
private:
Q_DECLARE_PRIVATE(KoColorBackground)
Q_DISABLE_COPY(KoColorBackground)
};
#endif // KOCOLORBACKGROUND_H
diff --git a/libs/flake/KoConnectionShape.cpp b/libs/flake/KoConnectionShape.cpp
index 856b346bc6..daaf7f1959 100644
--- a/libs/flake/KoConnectionShape.cpp
+++ b/libs/flake/KoConnectionShape.cpp
@@ -1,754 +1,779 @@
/* 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>
KoConnectionShapePrivate::KoConnectionShapePrivate(KoConnectionShape *q)
: KoParameterShapePrivate(q),
shape1(0),
shape2(0),
connectionPointId1(-1),
connectionPointId2(-1),
connectionType(KoConnectionShape::Standard),
forceUpdate(false),
hasCustomPath(false)
{
}
+KoConnectionShapePrivate::KoConnectionShapePrivate(const KoConnectionShapePrivate &rhs, KoConnectionShape *q)
+ : KoParameterShapePrivate(rhs, q),
+ 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 KoConnectionShapePrivate::escapeDirection(int handleId) const
{
Q_Q(const KoConnectionShape);
QPointF direction;
if (handleConnected(handleId)) {
KoShape *attachedShape = handleId == KoConnectionShape::StartHandle ? shape1 : shape2;
int connectionPointId = handleId == KoConnectionShape::StartHandle ? connectionPointId1 : connectionPointId2;
KoConnectionPoint::EscapeDirection ed = attachedShape->connectionPoint(connectionPointId).escapeDirection;
if (ed == KoConnectionPoint::AllDirections) {
QPointF handlePoint = q->shapeToDocument(handles[handleId]);
- QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition);
+ 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::Position corners[4] = {
- KoFlake::BottomRightCorner,
- KoFlake::BottomLeftCorner,
- KoFlake::TopLeftCorner,
- KoFlake::TopRightCorner
+ 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 = crossProd(p1-centerPoint, p2-centerPoint);
// check on which side of the first sector edge our handle point is
const qreal c1 = crossProd(p1-centerPoint, vHandle);
// second egde 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 = 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 = q->shapeToDocument(handles[handleId]);
- QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition);
+ 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 = q->shapeToDocument(handles[handleId]);
- QPointF centerPoint = attachedShape->absolutePosition(KoFlake::CenteredPosition);
+ 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 = q->absoluteTransformation(0).inverted();
direction = invMatrix.map(direction) - invMatrix.map(QPointF());
direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
}
return direction;
}
bool KoConnectionShapePrivate::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 coincidient
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 KoConnectionShapePrivate::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 KoConnectionShapePrivate::normalPath(const qreal MinimumEscapeLength)
{
// Clear the path to build it again.
path.clear();
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 (handleConnected(KoConnectionShape::StartHandle) && handleConnected(KoConnectionShape::EndHandle)) {
QPointF intersection;
bool connected = false;
do {
// first check if directions from current edge points intersect
if (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 = 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 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
} else {
// we are not going into the same direction, so switch direction
direction1 = perpendicularDirection(edgePoint1, direction1, edgePoint2);
}
} while (! connected);
}
path.append(edges1);
path.append(edges2);
path.append(handles[KoConnectionShape::EndHandle]);
}
qreal KoConnectionShapePrivate::scalarProd(const QPointF &v1, const QPointF &v2) const
{
return v1.x() * v2.x() + v1.y() * v2.y();
}
qreal KoConnectionShapePrivate::crossProd(const QPointF &v1, const QPointF &v2) const
{
return v1.x() * v2.y() - v1.y() * v2.x();
}
bool KoConnectionShapePrivate::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()
{
Q_D(KoConnectionShape);
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));
if (d->handles[StartHandle] != p) {
d->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));
if (d->handles[EndHandle] != p) {
d->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(*(new KoConnectionShapePrivate(this)))
+ : KoParameterShape(new KoConnectionShapePrivate(this))
{
Q_D(KoConnectionShape);
d->handles.push_back(QPointF(0, 0));
d->handles.push_back(QPointF(140, 140));
moveTo(d->handles[StartHandle]);
lineTo(d->handles[EndHandle]);
updatePath(QSizeF(140, 140));
clearConnectionPoints();
}
+KoConnectionShape::KoConnectionShape(const KoConnectionShape &rhs)
+ : KoParameterShape(new KoConnectionShapePrivate(*rhs.d_func(), this))
+{
+}
+
+
KoConnectionShape::~KoConnectionShape()
{
Q_D(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
{
Q_D(const KoConnectionShape);
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(d->handles[StartHandle]) * context.shapeOffset(this));
context.xmlWriter().addAttributePt("svg:x1", p.x());
context.xmlWriter().addAttributePt("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(d->handles[EndHandle]) * context.shapeOffset(this));
context.xmlWriter().addAttributePt("svg:x2", p.x());
context.xmlWriter().addAttributePt("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)
{
Q_D(KoConnectionShape);
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";
d->handles[StartHandle] = d->shape1->absoluteTransformation(0).map(d->shape1->connectionPoint(d->connectionPointId1).position);
debugFlake << "start handle position =" << d->handles[StartHandle];
}
} else {
debugFlake << "start-shape not loaded yet, deferring connection";
context.updateShape(shapeId1, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::First));
}
} else {
d->handles[StartHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", QString())));
d->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";
d->handles[EndHandle] = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position);
debugFlake << "end handle position =" << d->handles[EndHandle];
}
} else {
debugFlake << "end-shape not loaded yet, deferring connection";
context.updateShape(shapeId2, new KoConnectionShapeLoadingUpdater(this, KoConnectionShapeLoadingUpdater::Second));
}
} else {
d->handles[EndHandle].setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", QString())));
d->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 (m_subpaths.size() > 0) {
+ if (d->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());
d->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()
{
Q_D(KoConnectionShape);
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);
}
} else {
p1 = d->handles[StartHandle];
}
if (d->handleConnected(EndHandle)) {
if (d->shape2->hasConnectionPoint(d->connectionPointId2)) {
p2 = d->shape2->absoluteTransformation(0).map(d->shape2->connectionPoint(d->connectionPointId2).position);
}
} else {
p2 = d->handles[EndHandle];
}
- QPointF relativeBegin = m_subpaths.first()->first()->point();
- QPointF relativeEnd = m_subpaths.last()->last()->point();
+ QPointF relativeBegin = d->subpaths.first()->first()->point();
+ QPointF relativeEnd = d->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());
d->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);
Q_D(KoConnectionShape);
if (handleId >= d->handles.size())
return;
d->handles[handleId] = point;
}
void KoConnectionShape::updatePath(const QSizeF &size)
{
Q_UNUSED(size);
Q_D(KoConnectionShape);
const qreal MinimumEscapeLength = (qreal)20.;
clear();
switch (d->connectionType) {
case Standard: {
d->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 = d->escapeDirection(0);
QPointF direction2 = d->escapeDirection(d->handles.count() - 1);
moveTo(d->handles[StartHandle]);
if (! direction1.isNull())
lineTo(d->handles[StartHandle] + MinimumEscapeLength * direction1);
if (! direction2.isNull())
lineTo(d->handles[EndHandle] + MinimumEscapeLength * direction2);
lineTo(d->handles[EndHandle]);
break;
}
case Straight:
moveTo(d->handles[StartHandle]);
lineTo(d->handles[EndHandle]);
break;
case Curve:
// TODO
QPointF direction1 = d->escapeDirection(0);
QPointF direction2 = d->escapeDirection(d->handles.count() - 1);
moveTo(d->handles[StartHandle]);
if (! direction1.isNull() && ! direction2.isNull()) {
QPointF curvePoint1 = d->handles[StartHandle] + 5.0 * MinimumEscapeLength * direction1;
QPointF curvePoint2 = d->handles[EndHandle] + 5.0 * MinimumEscapeLength * direction2;
curveTo(curvePoint1, curvePoint2, d->handles[EndHandle]);
} else {
lineTo(d->handles[EndHandle]);
}
break;
}
normalize();
}
bool KoConnectionShape::connectFirst(KoShape * shape1, int connectionPointId)
{
Q_D(KoConnectionShape);
// 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)
{
Q_D(KoConnectionShape);
// 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
{
Q_D(const KoConnectionShape);
return d->shape1;
}
int KoConnectionShape::firstConnectionId() const
{
Q_D(const KoConnectionShape);
return d->connectionPointId1;
}
KoShape *KoConnectionShape::secondShape() const
{
Q_D(const KoConnectionShape);
return d->shape2;
}
int KoConnectionShape::secondConnectionId() const
{
Q_D(const KoConnectionShape);
return d->connectionPointId2;
}
KoConnectionShape::Type KoConnectionShape::type() const
{
Q_D(const KoConnectionShape);
return d->connectionType;
}
void KoConnectionShape::setType(Type connectionType)
{
Q_D(KoConnectionShape);
d->connectionType = connectionType;
updatePath(size());
}
void KoConnectionShape::shapeChanged(ChangeType type, KoShape *shape)
{
Q_D(KoConnectionShape);
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/KoConnectionShape.h b/libs/flake/KoConnectionShape.h
index 3de8bba82a..f1c1b4f1d0 100644
--- a/libs/flake/KoConnectionShape.h
+++ b/libs/flake/KoConnectionShape.h
@@ -1,140 +1,145 @@
/* 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 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.
*/
#ifndef KO_CONNECTION_SHAPE_H
#define KO_CONNECTION_SHAPE_H
#include "KoParameterShape.h"
#include "kritaflake_export.h"
#define KOCONNECTIONSHAPEID "KoConnectionShape"
class KoConnectionShapePrivate;
/// API docs go here
class KRITAFLAKE_EXPORT KoConnectionShape : public KoParameterShape
{
public:
enum Type {
Standard, ///< escapes connected shapes with straight lines, connects with perpendicular lines
Lines, ///< escapes connected shapes with straight lines, connects with straight line
Straight, ///< one straight line between connected shapes
Curve ///< a single curved line between connected shapes
};
// IDs of the connecting handles
enum HandleId {
StartHandle,
EndHandle,
ControlHandle_1,
ControlHandle_2,
ControlHandle_3
};
KoConnectionShape();
virtual ~KoConnectionShape();
+ KoShape* cloneShape() const override;
+
// reimplemented
virtual void saveOdf(KoShapeSavingContext &context) const;
// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
// reimplemented
virtual QString pathShapeId() const;
/**
* Sets the first shape this connector is connected to
*
* Passing a null pointer as the first parameter will sever the connection.
*
* @param shape the shape to connect to or null to reset the connection
* @param connectionPointId the id of the connection point to connect to
* @return true if connection could be established, otherwise false
*/
bool connectFirst(KoShape *shape, int connectionPointId);
/**
* Sets the second shape the connector is connected to
*
* Passing a null pointer as the first parameter will sever the connection.
*
* @param shape the shape to connect to or null to reset the connection
* @param connectionPointId the id of the connection point to connect to
* @return true if connection could be established, otherwise false
*/
bool connectSecond(KoShape *shape, int connectionPointId);
/**
* Return the first shape this connection is attached to, or null if none.
*/
KoShape *firstShape() const;
/**
* Return the connection point id of the first shape we are connected to.
* In case we are not connected to a first shape the return value is undefined.
* @see firstShape(), KoShape::connectionPoints()
*/
int firstConnectionId() const;
/**
* Return the second shape this connection is attached to, or null if none.
*/
KoShape *secondShape() const;
/**
* Return the connection point id of the second shape we are connected to.
* In case we are not connected to a second shape the return value is undefined.
* @see firstShape(), KoShape::connectionPoints()
*/
int secondConnectionId() const;
/**
* Finishes the loading of a connection.
*/
void finishLoadingConnection();
/// Returns connection type
Type type() const;
/// Sets the connection type
void setType(Type connectionType);
/// Updates connections to shapes
void updateConnections();
protected:
+ KoConnectionShape(const KoConnectionShape &rhs);
+
+
/// reimplemented
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
/// reimplemented
void updatePath(const QSizeF &size);
/// reimplemented
virtual void shapeChanged(ChangeType type, KoShape *shape);
private:
Q_DECLARE_PRIVATE(KoConnectionShape)
};
#endif
diff --git a/libs/flake/KoConnectionShapeFactory.cpp b/libs/flake/KoConnectionShapeFactory.cpp
index 74e4893c4f..bbb03e193d 100644
--- a/libs/flake/KoConnectionShapeFactory.cpp
+++ b/libs/flake/KoConnectionShapeFactory.cpp
@@ -1,63 +1,65 @@
/* 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 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 "KoConnectionShapeFactory.h"
#include "KoConnectionShape.h"
#include "KoConnectionShapeConfigWidget.h"
#include <KoXmlNS.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <KoShapeStroke.h>
#include <KoShapeLoadingContext.h>
+#include "kis_pointer_utils.h"
+
KoConnectionShapeFactory::KoConnectionShapeFactory()
: KoShapeFactoryBase(KOCONNECTIONSHAPEID, i18n("Tie"))
{
setToolTip(i18n("A connection between two other shapes"));
setIconName(koIconNameCStr("x-shape-connection"));
setXmlElementNames(KoXmlNS::draw, QStringList("connector"));
setLoadingPriority(1);
setHidden(true); // Don't show this shape in collections. Only ConnectionTool should create
}
KoShape* KoConnectionShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
KoConnectionShape * shape = new KoConnectionShape();
- shape->setStroke(new KoShapeStroke());
+ shape->setStroke(toQShared(new KoShapeStroke()));
shape->setShapeId(KoPathShapeId);
return shape;
}
bool KoConnectionShapeFactory::supports(const KoXmlElement & e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
return (e.localName() == "connector" && e.namespaceURI() == KoXmlNS::draw);
}
QList<KoShapeConfigWidgetBase*> KoConnectionShapeFactory::createShapeOptionPanels()
{
QList<KoShapeConfigWidgetBase*> panels;
panels.append(new KoConnectionShapeConfigWidget());
return panels;
}
diff --git a/libs/flake/KoConnectionShape_p.h b/libs/flake/KoConnectionShape_p.h
index 23d3efb17f..bb3cd7c4ed 100644
--- a/libs/flake/KoConnectionShape_p.h
+++ b/libs/flake/KoConnectionShape_p.h
@@ -1,59 +1,60 @@
/* 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 KOCONNECTIONSHAPEPRIVATE_P
#define KOCONNECTIONSHAPEPRIVATE_P
#include "KoParameterShape_p.h"
class KoConnectionShapePrivate : public KoParameterShapePrivate
{
public:
explicit KoConnectionShapePrivate(KoConnectionShape *q);
+ explicit KoConnectionShapePrivate(const KoConnectionShapePrivate &rhs, KoConnectionShape *q);
/// Returns escape direction of given handle
QPointF escapeDirection(int handleId) const;
/// Checks if rays from given points into given directions intersect
bool intersects(const QPointF &p1, const QPointF &d1, const QPointF &p2, const QPointF &d2, QPointF &isect);
/// Returns perpendicular direction from given point p1 and direction d1 toward point p2
QPointF perpendicularDirection(const QPointF &p1, const QPointF &d1, const QPointF &p2);
/// Populate the path list by a normal way
void normalPath(const qreal MinimumEscapeLength);
qreal scalarProd(const QPointF &v1, const QPointF &v2) const;
qreal crossProd(const QPointF &v1, const QPointF &v2) const;
/// Returns if given handle is connected to a shape
bool handleConnected(int handleId) const;
QList<QPointF> path;
KoShape *shape1;
KoShape *shape2;
int connectionPointId1;
int connectionPointId2;
KoConnectionShape::Type connectionType;
bool forceUpdate;
bool hasCustomPath;
Q_DECLARE_PUBLIC(KoConnectionShape)
};
#endif
diff --git a/libs/flake/KoDocumentResourceManager.cpp b/libs/flake/KoDocumentResourceManager.cpp
index 3a0e3ad67b..15c791ed51 100644
--- a/libs/flake/KoDocumentResourceManager.cpp
+++ b/libs/flake/KoDocumentResourceManager.cpp
@@ -1,222 +1,201 @@
/*
Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
Copyright (C) 2007, 2010 Thomas Zander <zander@kde.org>
Copyright (c) 2008 Carlos Licea <carlos.licea@kdemail.net>
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 "KoDocumentResourceManager.h"
#include <QVariant>
#include <kundo2stack.h>
#include <FlakeDebug.h>
#include "KoShape.h"
#include "KoShapeController.h"
#include "KoResourceManager_p.h"
class Q_DECL_HIDDEN KoDocumentResourceManager::Private
{
public:
KoResourceManager manager;
};
KoDocumentResourceManager::KoDocumentResourceManager(QObject *parent)
: QObject(parent),
d(new Private())
{
connect(&d->manager, &KoResourceManager::resourceChanged,
this, &KoDocumentResourceManager::resourceChanged);
}
KoDocumentResourceManager::~KoDocumentResourceManager()
{
delete d;
}
void KoDocumentResourceManager::setResource(int key, const QVariant &value)
{
d->manager.setResource(key, value);
}
QVariant KoDocumentResourceManager::resource(int key) const
{
return d->manager.resource(key);
}
void KoDocumentResourceManager::setResource(int key, const KoColor &color)
{
QVariant v;
v.setValue(color);
setResource(key, v);
}
void KoDocumentResourceManager::setResource(int key, KoShape *shape)
{
QVariant v;
v.setValue(shape);
setResource(key, v);
}
void KoDocumentResourceManager::setResource(int key, const KoUnit &unit)
{
QVariant v;
v.setValue(unit);
setResource(key, v);
}
KoColor KoDocumentResourceManager::koColorResource(int key) const
{
return d->manager.koColorResource(key);
}
bool KoDocumentResourceManager::boolResource(int key) const
{
return d->manager.boolResource(key);
}
int KoDocumentResourceManager::intResource(int key) const
{
return d->manager.intResource(key);
}
QString KoDocumentResourceManager::stringResource(int key) const
{
return d->manager.stringResource(key);
}
QSizeF KoDocumentResourceManager::sizeResource(int key) const
{
return d->manager.sizeResource(key);
}
bool KoDocumentResourceManager::hasResource(int key) const
{
return d->manager.hasResource(key);
}
void KoDocumentResourceManager::clearResource(int key)
{
d->manager.clearResource(key);
}
KUndo2Stack *KoDocumentResourceManager::undoStack() const
{
if (!hasResource(UndoStack))
return 0;
return static_cast<KUndo2Stack*>(resource(UndoStack).value<void*>());
}
void KoDocumentResourceManager::setHandleRadius(int handleRadius)
{
// do not allow arbitrary small handles
- if (handleRadius < 3)
- handleRadius = 3;
+ if (handleRadius < 5)
+ handleRadius = 5;
setResource(HandleRadius, QVariant(handleRadius));
}
int KoDocumentResourceManager::handleRadius() const
{
if (hasResource(HandleRadius))
return intResource(HandleRadius);
- return 3; // default value.
+ return 5; // default value (and is used just about everywhere)
}
void KoDocumentResourceManager::setGrabSensitivity(int grabSensitivity)
{
// do not allow arbitrary small grab sensitivity
- if (grabSensitivity < 3)
- grabSensitivity = 3;
+ if (grabSensitivity < 5)
+ grabSensitivity = 5;
setResource(GrabSensitivity, QVariant(grabSensitivity));
}
int KoDocumentResourceManager::grabSensitivity() const
{
if (hasResource(GrabSensitivity))
return intResource(GrabSensitivity);
- return 3; // default value
+ return 5; // default value (and is used just about everywhere)
}
-void KoDocumentResourceManager::setPasteOffset(qreal pasteOffset)
-{
- setResource(PasteOffset, QVariant(pasteOffset));
-}
-
-qreal KoDocumentResourceManager::pasteOffset() const
-{
- return resource(PasteOffset).toDouble();
-}
-
-void KoDocumentResourceManager::enablePasteAtCursor(bool enable)
-{
- setResource(PasteAtCursor, QVariant(enable));
-}
-
-bool KoDocumentResourceManager::pasteAtCursor() const
-{
- return resource(PasteAtCursor).toBool();
-}
-
-
void KoDocumentResourceManager::setUndoStack(KUndo2Stack *undoStack)
{
QVariant variant;
variant.setValue<void*>(undoStack);
setResource(UndoStack, variant);
}
KoImageCollection *KoDocumentResourceManager::imageCollection() const
{
if (!hasResource(ImageCollection))
return 0;
return static_cast<KoImageCollection*>(resource(ImageCollection).value<void*>());
}
void KoDocumentResourceManager::setImageCollection(KoImageCollection *ic)
{
QVariant variant;
variant.setValue<void*>(ic);
setResource(ImageCollection, variant);
}
KoDocumentBase *KoDocumentResourceManager::odfDocument() const
{
if (!hasResource(OdfDocument))
return 0;
return static_cast<KoDocumentBase*>(resource(OdfDocument).value<void*>());
}
void KoDocumentResourceManager::setOdfDocument(KoDocumentBase *currentDocument)
{
QVariant variant;
variant.setValue<void*>(currentDocument);
setResource(OdfDocument, variant);
}
KoShapeController *KoDocumentResourceManager::shapeController() const
{
if (!hasResource(ShapeController))
return 0;
return resource(ShapeController).value<KoShapeController *>();
}
void KoDocumentResourceManager::setShapeController(KoShapeController *shapeController)
{
QVariant variant;
variant.setValue<KoShapeController *>(shapeController);
setResource(ShapeController, variant);
}
diff --git a/libs/flake/KoDocumentResourceManager.h b/libs/flake/KoDocumentResourceManager.h
index d3a468f5ab..b24c06f23e 100644
--- a/libs/flake/KoDocumentResourceManager.h
+++ b/libs/flake/KoDocumentResourceManager.h
@@ -1,269 +1,253 @@
/*
Copyright (c) 2006 Boudewijn Rempt (boud@valdyas.org)
Copyright (C) 2007, 2009, 2010 Thomas Zander <zander@kde.org>
Copyright (c) 2008 Carlos Licea <carlos.licea@kdemail.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 KO_DOCUMENTRESOURCEMANAGER_H
#define KO_DOCUMENTRESOURCEMANAGER_H
#include <QObject>
#include "kritaflake_export.h"
class KoShape;
class KUndo2Stack;
class KoImageCollection;
class KoDocumentBase;
class KoShapeController;
class KoColor;
class KoUnit;
class QVariant;
class QSizeF;
/**
* The KoResourceManager contains a set of per-canvas <i>or</i> per-document
* properties, like current foreground color, current background
* color and more. All tools belonging to the current canvas are
* notified when a Resource changes (is set).
*
* The properties come from the KoDocumentResourceManager::DocumentResource
* See KoShapeController::resourceManager
*
* The manager can contain all sorts of variable types and there are accessors
* for the most common ones. All variables are always stored inside a QVariant
* instance internally and you can always just use the resource() method to get
* that directly.
* The way to store arbitairy data objects that are stored as pointers you can use
* the following code snippets;
* @code
* QVariant variant;
* variant.setValue<void*>(textShapeData->document());
* resourceManager->setResource(KoText::CurrentTextDocument, variant);
* // and get it out again.
* QVariant var = resourceManager->resource(KoText::CurrentTextDocument);
* document = static_cast<QTextDocument*>(var.value<void*>());
* @endcode
*/
class KRITAFLAKE_EXPORT KoDocumentResourceManager : public QObject
{
Q_OBJECT
public:
/**
* This enum holds identifiers to the resources that can be stored in here.
*/
enum DocumentResource {
UndoStack, ///< The document-wide undo stack (KUndo2Stack)
ImageCollection, ///< The KoImageCollection for the document
OdfDocument, ///< The document this canvas shows (KoDocumentBase)
- PasteOffset, ///< Application wide paste offset
- PasteAtCursor, ///< Application wide paste at cursor setting
HandleRadius, ///< The handle radius used for drawing handles of any kind
GrabSensitivity, ///< The grab sensitivity used for grabbing handles of any kind
MarkerCollection, ///< The collection holding all markers
ShapeController, ///< The KoShapeController for the document
KarbonStart = 1000, ///< Base number for Karbon specific values.
KexiStart = 2000, ///< Base number for Kexi specific values.
FlowStart = 3000, ///< Base number for Flow specific values.
PlanStart = 4000, ///< Base number for Plan specific values.
StageStart = 5000, ///< Base number for Stage specific values.
KritaStart = 6000, ///< Base number for Krita specific values.
SheetsStart = 7000, ///< Base number for Sheets specific values.
WordsStart = 8000, ///< Base number for Words specific values.
KoPageAppStart = 9000, ///< Base number for KoPageApp specific values.
KoTextStart = 10000 ///< Base number for KoText specific values.
};
/**
* Constructor.
* @param parent the parent QObject, used for memory management.
*/
explicit KoDocumentResourceManager(QObject *parent = 0);
~KoDocumentResourceManager();
/**
* Set a resource of any type.
* @param key the integer key
* @param value the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, const QVariant &value);
/**
* Set a resource of type KoColor.
* @param key the integer key
* @param color the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, const KoColor &color);
/**
* Set a resource of type KoShape*.
* @param key the integer key
* @param id the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, KoShape *shape);
/**
* Set a resource of type KoUnit
* @param key the integer key
* @param id the new value for the key.
* @see KoDocumentResourceManager::DocumentResource
*/
void setResource(int key, const KoUnit &unit);
/**
* Returns a qvariant containing the specified resource or a standard one if the
* specified resource does not exist.
* @param key the key
* @see KoDocumentResourceManager::DocumentResource
*/
QVariant resource(int key) const;
/**
* Return the resource determined by param key as a boolean.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
bool boolResource(int key) const;
/**
* Return the resource determined by param key as an integer.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
int intResource(int key) const;
/**
* Return the resource determined by param key as a KoColor.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
KoColor koColorResource(int key) const;
/**
* Return the resource determined by param key as a pointer to a KoShape.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
KoShape *koShapeResource(int key) const;
/**
* Return the resource determined by param key as a QString .
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
QString stringResource(int key) const;
/**
* Return the resource determined by param key as a QSizeF.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
QSizeF sizeResource(int key) const;
/**
* Return the resource determined by param key as a KoUnit.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
KoUnit unitResource(int key) const;
/**
* Returns true if there is a resource set with the requested key.
* @param key the indentifying key for the resource
* @see KoDocumentResourceManager::DocumentResource
*/
bool hasResource(int key) const;
/**
* Remove the resource with @p key from the provider.
* @param key the key that will be used to remove the resource
* There will be a signal emitted with a variable that will return true on QVariable::isNull();
* @see KoDocumentResourceManager::DocumentResource
*/
void clearResource(int key);
/**
* Tools that provide a handle for controlling the content that the tool can edit can
* use this property to alter the radius that a circular handle should have on screen.
* @param handleSize the radius in pixels.
*/
void setHandleRadius(int handleSize);
/// Returns the actual handle radius
int handleRadius() const;
/**
* Tools that are used to grab handles or similar with the mouse
* should use this value to determine if the mouse is near enough
* @param grabSensitivity the grab sensitivity in pixels
*/
void setGrabSensitivity(int grabSensitivity);
/// Returns the actual grab sensitivity
int grabSensitivity() const;
- /**
- * Offset used for pasting shapes to a document.
- */
- void setPasteOffset(qreal pasteOffset);
- /// Returns the current paste offset
- qreal pasteOffset() const;
-
- /**
- * Enables/disables pasting shape at cursor position
- */
- void enablePasteAtCursor(bool enable);
- /// Returns current state of paste at cursor setting
- bool pasteAtCursor() const;
-
KUndo2Stack *undoStack() const;
void setUndoStack(KUndo2Stack *undoStack);
KoImageCollection *imageCollection() const;
void setImageCollection(KoImageCollection *ic);
KoDocumentBase *odfDocument() const;
void setOdfDocument(KoDocumentBase *currentDocument);
KoShapeController *shapeController() const;
void setShapeController(KoShapeController *shapeController);
Q_SIGNALS:
/**
* This signal is emitted every time a resource is set that is either
* new or different from the previous set value.
* @param key the indentifying key for the resource
* @param value the variants new value.
* @see KoDocumentResourceManager::DocumentResource
*/
void resourceChanged(int key, const QVariant &value);
private:
KoDocumentResourceManager(const KoDocumentResourceManager&);
KoDocumentResourceManager& operator=(const KoDocumentResourceManager&);
class Private;
Private *const d;
};
#endif
diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp
index 2f82798c27..08d5ccd328 100644
--- a/libs/flake/KoDrag.cpp
+++ b/libs/flake/KoDrag.cpp
@@ -1,149 +1,112 @@
/* 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 <FlakeDebug.h>
#include <KoStore.h>
#include <KoGenStyles.h>
#include <KoOdfWriteStore.h>
#include <KoXmlWriter.h>
#include <KoDocumentBase.h>
#include <KoEmbeddedDocumentSaver.h>
#include "KoShapeSavingContext.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::setOdf(const char *mimeType, KoDragOdfSaveHelper &helper)
+bool KoDrag::setSvg(const QList<KoShape *> originalShapes)
{
- struct Finally {
- Finally(KoStore *s) : store(s) { }
- ~Finally() {
- delete store;
- }
- KoStore *store;
- };
-
- QBuffer buffer;
- KoStore *store = KoStore::createStore(&buffer, KoStore::Write, mimeType);
- Finally finally(store); // delete store when we exit this scope
- Q_ASSERT(store);
- Q_ASSERT(!store->bad());
-
- KoOdfWriteStore odfStore(store);
- KoEmbeddedDocumentSaver embeddedSaver;
-
- KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType);
- KoXmlWriter *contentWriter = odfStore.contentWriter();
-
- if (!contentWriter) {
- return false;
- }
-
- KoGenStyles mainStyles;
- KoXmlWriter *bodyWriter = odfStore.bodyWriter();
- KoShapeSavingContext *context = helper.context(bodyWriter, mainStyles, embeddedSaver);
+ QRectF boundingRect;
+ QList<KoShape*> shapes;
- if (!helper.writeBody()) {
- return false;
+ Q_FOREACH (KoShape *shape, originalShapes) {
+ boundingRect |= shape->boundingRect();
+ shapes.append(shape->cloneShape());
}
- mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, contentWriter);
+ qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
- odfStore.closeContentWriter();
-
- //add manifest line for content.xml
- manifestWriter->addManifestEntry("content.xml", "text/xml");
-
-
- if (!mainStyles.saveOdfStylesDotXml(store, manifestWriter)) {
- return false;
- }
-
- if (!context->saveDataCenter(store, manifestWriter)) {
- debugFlake << "save data centers failed";
- return false;
- }
+ QBuffer buffer;
+ QLatin1String mimeType("image/svg+xml");
- // Save embedded objects
- KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver);
- if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) {
- debugFlake << "save embedded documents failed";
- return false;
- }
+ buffer.open(QIODevice::WriteOnly);
- // Write out manifest file
- if (!odfStore.closeManifestWriter()) {
- return false;
- }
+ const QSizeF pageSize(boundingRect.right(), boundingRect.bottom());
+ SvgWriter writer(shapes, pageSize);
+ writer.save(buffer);
- delete store; // make sure the buffer if fully flushed.
- finally.store = 0;
- setData(mimeType, buffer.buffer());
+ 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/KoDrag.h b/libs/flake/KoDrag.h
index 8ef1c26b0f..a5a8823305 100644
--- a/libs/flake/KoDrag.h
+++ b/libs/flake/KoDrag.h
@@ -1,78 +1,77 @@
/* This file is part of the KDE project
* Copyright (C) 2007 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 KODRAG_H
#define KODRAG_H
#include "kritaflake_export.h"
+#include <QList>
+
class QMimeData;
class QString;
class QByteArray;
class KoDragOdfSaveHelper;
class KoDragPrivate;
+class KoShape;
/**
* Class for simplifying adding a odf to the clip board
*
* For saving the odf a KoDragOdfSaveHelper class is used.
* It implements the writing of the body of the document. The
* setOdf takes care of saving styles and all the other
* common stuff.
*/
class KRITAFLAKE_EXPORT KoDrag
{
public:
KoDrag();
~KoDrag();
+
/**
- * Set odf mime type
- *
- * This calls helper.writeBody();
- *
- * @param mimeType used for creating the odf document
- * @param helper helper for saving the body of the odf document
+ * Load SVG data into the current mime data
*/
- bool setOdf(const char *mimeType, KoDragOdfSaveHelper &helper);
+ bool setSvg(const QList<KoShape*> shapes);
/**
* Add additional mimeTypes
*/
void setData(const QString &mimeType, const QByteArray &data);
/**
* Add the mimeData to the clipboard
*/
void addToClipboard();
/**
* Get the mime data
*
* This transfers the ownership of the mimeData to the caller
*
* This function is for use in automated tests
*/
QMimeData *mimeData();
private:
KoDragPrivate * const d;
};
#endif /* KODRAG_H */
diff --git a/libs/flake/KoFlake.cpp b/libs/flake/KoFlake.cpp
index 17824efb91..a5616a3109 100644
--- a/libs/flake/KoFlake.cpp
+++ b/libs/flake/KoFlake.cpp
@@ -1,75 +1,336 @@
/* 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 <KoShape.h>
+#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 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) *
+ scale *
+ shape->absoluteTransformation(0).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) *
+ scale *
+ shape->absoluteTransformation(0).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);
+
+ 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 (anchor == NoAnchor) {
+ if (valid) {
+ *valid = false;
+ }
+ return rect.topLeft();
+ } else if (valid) {
+ *valid = true;
+ }
+
+ return KisAlgebra2D::relativeToAbsolute(anchorTable[int(anchor)], rect);
+}
diff --git a/libs/flake/KoFlake.h b/libs/flake/KoFlake.h
index 933b93dd2e..472c52b762 100644
--- a/libs/flake/KoFlake.h
+++ b/libs/flake/KoFlake.h
@@ -1,106 +1,150 @@
/* This file is part of the KDE project
* Copyright (C) 2006 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.
*/
#ifndef KOFLAKE_H
#define KOFLAKE_H
#include "kritaflake_export.h"
class QGradient;
+class QRectF;
class QPointF;
class QSizeF;
+class KoShape;
+class QTransform;
+
+#include <Qt>
+
/**
* Flake reference
*/
namespace KoFlake
{
+ enum FillVariant {
+ Fill,
+ StrokeFill
+ };
+
+ enum FillType {
+ None,
+ Solid,
+ Gradient,
+ Pattern
+ };
+
+ enum MarkerPosition {
+ StartMarker,
+ MidMarker,
+ EndMarker
+ };
+
/// the selection type for KoSelection::selectedObjects()
enum SelectionType {
FullSelection, ///< Create a list of all user-shapes in the selection. This excludes KoShapeGroup grouping objects that may be selected.
StrippedSelection, ///< Create a stripped list, without children if the container is also in the list.
TopLevelSelection ///< Create a list, much like the StrippedSelection, but have the KoShapeGroup instead of all of its children if one is selected.
};
/// Enum determining which handle is meant, used in KoInteractionTool
enum SelectionHandle {
TopMiddleHandle, ///< The handle that is at the top - center of a selection
TopRightHandle, ///< The handle that is at the top - right of a selection
RightMiddleHandle, ///< The handle that is at the right - center of a selection
BottomRightHandle, ///< The handle that is at the bottom right of a selection
BottomMiddleHandle, ///< The handle that is at the bottom center of a selection
BottomLeftHandle, ///< The handle that is at the bottom left of a selection
LeftMiddleHandle, ///< The handle that is at the left center of a selection
TopLeftHandle, ///< The handle that is at the top left of a selection
NoHandle ///< Value to indicate no handle
};
/**
* Used to change the behavior of KoShapeManager::shapeAt()
*/
enum ShapeSelection {
Selected, ///< return the first selected with the highest z-ordering (i.e. on top).
Unselected, ///< return the first unselected on top.
NextUnselected, ///< return the first unselected directly under a selected shape, or the top most one if nothing is selected.
ShapeOnTop ///< return the shape highest z-ordering, regardless of selection.
};
- /// position. See KoShape::absolutePosition()
- enum Position {
- TopLeftCorner, ///< the top left corner
- TopRightCorner, ///< the top right corner
- BottomLeftCorner, ///< the bottom left corner
- BottomRightCorner, ///< the bottom right corner
- CenteredPosition ///< the centred corner
- };
-
/**
* Used to see which style type is active
*/
enum StyleType {
Background, ///< the background / fill style is active
Foreground ///< the foreground / stroke style is active
};
+ enum AnchorPosition {
+ TopLeft,
+ Top,
+ TopRight,
+ Left,
+ Center,
+ Right,
+ BottomLeft,
+ Bottom,
+ BottomRight,
+ NoAnchor,
+ NumAnchorPositions
+ };
+
+ KRITAFLAKE_EXPORT QPointF anchorToPoint(AnchorPosition anchor, const QRectF rect, bool *valid = 0);
+
+ enum CanvasResource {
+ HotPosition = 1410100299
+ };
+
/// clones the given gradient
KRITAFLAKE_EXPORT QGradient *cloneGradient(const QGradient *gradient);
+ KRITAFLAKE_EXPORT QGradient *mergeGradient(const QGradient *coordsSource, const QGradient *fillSource);
+
/**
* Convert absolute to relative position
*
* @param absolute absolute position
* @param size for which the relative position needs to be calculated
*
* @return relative position
*/
KRITAFLAKE_EXPORT QPointF toRelative(const QPointF &absolute, const QSizeF &size);
/**
* Convert relative size to absolute size
*
* @param relative relative position
* @param size for which the absolute position needs to be calculated
*
* @return absolute position
*/
KRITAFLAKE_EXPORT QPointF toAbsolute(const QPointF &relative, const QSizeF &size);
+
+ KRITAFLAKE_EXPORT Qt::Orientation significantScaleOrientation(qreal scaleX, qreal scaleY);
+
+ KRITAFLAKE_EXPORT void resizeShape(KoShape *shape, qreal scaleX, qreal scaleY,
+ const QPointF &absoluteStillPoint,
+ bool useGlobalMode,
+ bool usePostScaling, const QTransform &postScalingCoveringTransform);
}
#endif
diff --git a/libs/flake/KoFlakeCoordinateSystem.h b/libs/flake/KoFlakeCoordinateSystem.h
new file mode 100644
index 0000000000..3bac12dc5c
--- /dev/null
+++ b/libs/flake/KoFlakeCoordinateSystem.h
@@ -0,0 +1,54 @@
+/*
+ * 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 KOFLAKECOORDINATE_SYSTEM_H
+#define KOFLAKECOORDINATE_SYSTEM_H
+
+#include <QString>
+
+namespace KoFlake {
+
+enum CoordinateSystem {
+ UserSpaceOnUse,
+ ObjectBoundingBox
+};
+
+inline CoordinateSystem coordinatesFromString(const QString &value, CoordinateSystem defaultValue)
+{
+ CoordinateSystem result = defaultValue;
+
+ if (value == "userSpaceOnUse") {
+ result = UserSpaceOnUse;
+ } else if (value == "objectBoundingBox") {
+ result = ObjectBoundingBox;
+ }
+
+ return result;
+}
+
+inline QString coordinateToString(CoordinateSystem value)
+{
+ return
+ value == ObjectBoundingBox?
+ "objectBoundingBox" :
+ "userSpaceOnUse";
+}
+}
+
+#endif // KOFLAKECOORDINATE_SYSTEM_H
+
diff --git a/libs/flake/KoFlakeTypes.h b/libs/flake/KoFlakeTypes.h
new file mode 100644
index 0000000000..bf861ccfd2
--- /dev/null
+++ b/libs/flake/KoFlakeTypes.h
@@ -0,0 +1,31 @@
+/*
+ * 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 KOFLAKETYPES_H
+#define KOFLAKETYPES_H
+
+class KoShapeStroke;
+class KoShapeStrokeModel;
+
+template<class T> class QSharedPointer;
+
+typedef QSharedPointer<KoShapeStrokeModel> KoShapeStrokeModelSP;
+typedef QSharedPointer<KoShapeStroke> KoShapeStrokeSP;
+
+#endif // KOFLAKETYPES_H
+
diff --git a/libs/flake/KoFlakeUtils.h b/libs/flake/KoFlakeUtils.h
new file mode 100644
index 0000000000..7053b0b428
--- /dev/null
+++ b/libs/flake/KoFlakeUtils.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KOFLAKEUTILS_H
+#define KOFLAKEUTILS_H
+
+#include <KoShape.h>
+#include <KoFlakeTypes.h>
+#include <KoShapeStroke.h>
+
+#include "kis_global.h"
+#include "KoShapeStrokeCommand.h"
+
+
+namespace KoFlake {
+
+template <typename ModifyFunction>
+ auto modifyShapesStrokes(QList<KoShape*> shapes, ModifyFunction modifyFunction)
+ -> decltype(modifyFunction(KoShapeStrokeSP()), (KUndo2Command*)(0))
+ {
+ if (shapes.isEmpty()) return 0;
+
+ QList<KoShapeStrokeModelSP> newStrokes;
+
+ Q_FOREACH(KoShape *shape, shapes) {
+ KoShapeStrokeSP shapeStroke = shape->stroke() ?
+ qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
+ KoShapeStrokeSP();
+
+ KoShapeStrokeSP newStroke =
+ toQShared(shapeStroke ?
+ new KoShapeStroke(*shapeStroke) :
+ new KoShapeStroke());
+
+ modifyFunction(newStroke);
+
+ newStrokes << newStroke;
+ }
+
+ return new KoShapeStrokeCommand(shapes, newStrokes);
+}
+
+template <class Policy>
+bool compareShapePropertiesEqual(const QList<KoShape*> shapes, const Policy &policy)
+{
+ if (shapes.size() == 1) return true;
+
+ typename Policy::PointerType bg =
+ policy.getProperty(shapes.first());
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ if (
+ !(
+ (!bg && !policy.getProperty(shape)) ||
+ (bg && policy.compareTo(bg, policy.getProperty(shape)))
+ )) {
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <class Policy>
+bool compareShapePropertiesEqual(const QList<KoShape*> shapes)
+{
+ return compareShapePropertiesEqual<Policy>(shapes, Policy());
+}
+
+}
+
+#endif // KOFLAKEUTILS_H
+
diff --git a/libs/flake/KoGradientBackground.cpp b/libs/flake/KoGradientBackground.cpp
index 940f8344cc..e8aa8f87d5 100644
--- a/libs/flake/KoGradientBackground.cpp
+++ b/libs/flake/KoGradientBackground.cpp
@@ -1,154 +1,194 @@
/* 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 "KoShapeBackground_p.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>
class KoGradientBackgroundPrivate : public KoShapeBackgroundPrivate
{
public:
KoGradientBackgroundPrivate()
: gradient(0)
{}
QGradient *gradient;
QTransform matrix;
};
KoGradientBackground::KoGradientBackground(QGradient * gradient, const QTransform &matrix)
: KoShapeBackground(*(new KoGradientBackgroundPrivate()))
{
Q_D(KoGradientBackground);
d->gradient = gradient;
d->matrix = matrix;
Q_ASSERT(d->gradient);
Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode);
}
KoGradientBackground::KoGradientBackground(const QGradient & gradient, const QTransform &matrix)
: KoShapeBackground(*(new KoGradientBackgroundPrivate()))
{
Q_D(KoGradientBackground);
d->gradient = KoFlake::cloneGradient(&gradient);
d->matrix = matrix;
Q_ASSERT(d->gradient);
Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode);
}
KoGradientBackground::~KoGradientBackground()
{
Q_D(KoGradientBackground);
delete d->gradient;
}
+bool KoGradientBackground::compareTo(const KoShapeBackground *other) const
+{
+ Q_D(const KoGradientBackground);
+ const KoGradientBackground *otherGradient = dynamic_cast<const KoGradientBackground*>(other);
+
+ return otherGradient &&
+ d->matrix == otherGradient->d_func()->matrix &&
+ *d->gradient == *otherGradient->d_func()->gradient;
+}
+
void KoGradientBackground::setTransform(const QTransform &matrix)
{
Q_D(KoGradientBackground);
d->matrix = matrix;
}
QTransform KoGradientBackground::transform() const
{
Q_D(const KoGradientBackground);
return d->matrix;
}
void KoGradientBackground::setGradient(const QGradient &gradient)
{
Q_D(KoGradientBackground);
delete d->gradient;
d->gradient = KoFlake::cloneGradient(&gradient);
Q_ASSERT(d->gradient);
Q_ASSERT(d->gradient->coordinateMode() == QGradient::ObjectBoundingMode);
}
const QGradient * KoGradientBackground::gradient() const
{
Q_D(const KoGradientBackground);
return d->gradient;
}
void KoGradientBackground::paint(QPainter &painter, const KoViewConverter &/*converter*/, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
{
Q_D(const KoGradientBackground);
if (!d->gradient) return;
- QBrush brush(*d->gradient);
- brush.setTransform(d->matrix);
- painter.setBrush(brush);
+ 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 exectly 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)
{
Q_D(KoGradientBackground);
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)
{
Q_D(KoGradientBackground);
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 f5cdf49649..be0b3d3559 100644
--- a/libs/flake/KoGradientBackground.h
+++ b/libs/flake/KoGradientBackground.h
@@ -1,77 +1,79 @@
/* 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>
class QGradient;
class KoGradientBackgroundPrivate;
/// 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
virtual ~KoGradientBackground();
+ 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
virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const;
/// reimplemented from KoShapeBackground
virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context);
/// reimplemented from KoShapeBackground
virtual bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize);
private:
Q_DECLARE_PRIVATE(KoGradientBackground)
Q_DISABLE_COPY(KoGradientBackground)
};
#endif // KOGRADIENTBACKGROUND_H
diff --git a/libs/flake/KoGradientHelper.h b/libs/flake/KoGradientHelper.h
index a558f9e1f0..1014bf692b 100644
--- a/libs/flake/KoGradientHelper.h
+++ b/libs/flake/KoGradientHelper.h
@@ -1,39 +1,39 @@
/* 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 KO_GRADIENT_HELPER_H
#define KO_GRADIENT_HELPER_H
#include <kritaflake_export.h>
#include <QGradient>
namespace KoGradientHelper
{
/// creates default gradient
KRITAFLAKE_EXPORT QGradient *defaultGradient(QGradient::Type type, QGradient::Spread spread, const QGradientStops &stops);
/// Converts gradient type, preserving as much data as possible
KRITAFLAKE_EXPORT QGradient *convertGradient(const QGradient *gradient, QGradient::Type newType);
/// Calculates color at given position from given gradient stops
KRITAFLAKE_EXPORT QColor colorAt(qreal position, const QGradientStops &stops);
-};
+}
#endif
diff --git a/libs/flake/KoImageData.cpp b/libs/flake/KoImageData.cpp
index 5882360d8c..a0d08369e4 100644
--- a/libs/flake/KoImageData.cpp
+++ b/libs/flake/KoImageData.cpp
@@ -1,374 +1,379 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2008 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 "KoImageData.h"
#include "KoImageData_p.h"
#include "KoImageCollection.h"
#include <KoUnit.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <FlakeDebug.h>
#include <QBuffer>
#include <QCryptographicHash>
#include <QTemporaryFile>
#include <QPainter>
/// the maximum amount of bytes the image can be while we store it in memory instead of
/// spooling it to disk in a temp-file.
#define MAX_MEMORY_IMAGESIZE 90000
KoImageData::KoImageData()
: d(0)
{
}
KoImageData::KoImageData(const KoImageData &imageData)
: KoShapeUserData(),
d(imageData.d)
{
if (d)
d->refCount.ref();
}
KoImageData::KoImageData(KoImageDataPrivate *priv)
: d(priv)
{
d->refCount.ref();
}
KoImageData::~KoImageData()
{
if (d && !d->refCount.deref())
delete d;
}
QPixmap KoImageData::pixmap(const QSize &size)
{
if (!d) return QPixmap();
QSize wantedSize = size;
if (! wantedSize.isValid()) {
if (d->pixmap.isNull()) // we have a problem, Houston..
wantedSize = QSize(100, 100);
else
wantedSize = d->pixmap.size();
}
if (d->pixmap.isNull() || d->pixmap.size() != wantedSize) {
switch (d->dataStoreState) {
case KoImageDataPrivate::StateEmpty: {
#if 0 // this is not possible as it gets called during the paint method
// and will crash. Therefore create a tmp pixmap and return it.
d->pixmap = QPixmap(1, 1);
QPainter p(&d->pixmap);
p.setPen(QPen(Qt::gray, 0));
p.drawPoint(0, 0);
p.end();
break;
#endif
QPixmap tmp(1, 1);
tmp.fill(Qt::gray);
return tmp;
}
case KoImageDataPrivate::StateNotLoaded:
image(); // forces load
// fall through
case KoImageDataPrivate::StateImageLoaded:
case KoImageDataPrivate::StateImageOnly:
if (!d->image.isNull()) {
// create pixmap from image.
// this is the highest quality and lowest memory usage way of doing the conversion.
d->pixmap = QPixmap::fromImage(d->image.scaled(wantedSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}
if (d->dataStoreState == KoImageDataPrivate::StateImageLoaded) {
if (d->cleanCacheTimer.isActive())
d->cleanCacheTimer.stop();
// schedule an auto-unload of the big QImage in a second.
d->cleanCacheTimer.start();
}
}
return d->pixmap;
}
bool KoImageData::hasCachedPixmap() const
{
return d && !d->pixmap.isNull();
}
QSizeF KoImageData::imageSize()
{
if (!d->imageSize.isValid()) {
// The imagesize have not yet been calculated
if (image().isNull()) // auto loads the image
return QSizeF(100, 100);
if (d->image.dotsPerMeterX())
d->imageSize.setWidth(DM_TO_POINT(d->image.width() / (qreal) d->image.dotsPerMeterX() * 10.0));
else
d->imageSize.setWidth(d->image.width() / 72.0);
if (d->image.dotsPerMeterY())
d->imageSize.setHeight(DM_TO_POINT(d->image.height() / (qreal) d->image.dotsPerMeterY() * 10.0));
else
d->imageSize.setHeight(d->image.height() / 72.0);
}
return d->imageSize;
}
QImage KoImageData::image() const
{
if (d->dataStoreState == KoImageDataPrivate::StateNotLoaded) {
// load image
if (d->temporaryFile) {
bool r = d->temporaryFile->open();
if (!r) {
d->errorCode = OpenFailed;
}
else if (d->errorCode == Success && !d->image.load(d->temporaryFile->fileName(), d->suffix.toLatin1())) {
d->errorCode = OpenFailed;
}
d->temporaryFile->close();
} else {
if (d->errorCode == Success && !d->image.load(d->imageLocation.toLocalFile())) {
d->errorCode = OpenFailed;
}
}
if (d->errorCode == Success) {
d->dataStoreState = KoImageDataPrivate::StateImageLoaded;
}
}
return d->image;
}
bool KoImageData::hasCachedImage() const
{
return d && !d->image.isNull();
}
void KoImageData::setImage(const QImage &image, KoImageCollection *collection)
{
qint64 oldKey = 0;
if (d) {
oldKey = d->key;
}
Q_ASSERT(!image.isNull());
if (collection) {
// let the collection first check if it already has one. If it doesn't it'll call this method
// again and well go to the other clause
KoImageData *other = collection->createImageData(image);
this->operator=(*other);
delete other;
} else {
if (d == 0) {
d = new KoImageDataPrivate(this);
d->refCount.ref();
}
delete d->temporaryFile;
d->temporaryFile = 0;
d->clear();
d->suffix = "png"; // good default for non-lossy storage.
if (image.byteCount() > MAX_MEMORY_IMAGESIZE) {
// store image
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
if (!image.save(&buffer, d->suffix.toLatin1())) {
warnFlake << "Write temporary file failed";
d->errorCode = StorageFailed;
delete d->temporaryFile;
d->temporaryFile = 0;
return;
}
buffer.close();
buffer.open(QIODevice::ReadOnly);
d->copyToTemporary(buffer);
} else {
d->image = image;
d->dataStoreState = KoImageDataPrivate::StateImageOnly;
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG"); // use .png for images we get as QImage
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(ba);
d->key = KoImageDataPrivate::generateKey(md5.result());
}
if (oldKey != 0 && d->collection) {
d->collection->update(oldKey, d->key);
}
}
}
void KoImageData::setImage(const QString &url, KoStore *store, KoImageCollection *collection)
{
if (collection) {
// Let the collection first check if it already has one. If it
// doesn't it'll call this method again and we'll go to the
// other clause.
KoImageData *other = collection->createImageData(url, store);
this->operator=(*other);
delete other;
} else {
if (d == 0) {
d = new KoImageDataPrivate(this);
d->refCount.ref();
} else {
d->clear();
}
d->setSuffix(url);
if (store->open(url)) {
struct Finalizer {
~Finalizer() { store->close(); }
KoStore *store;
};
Finalizer closer;
closer.store = store;
KoStoreDevice device(store);
const bool lossy = url.endsWith(".jpg", Qt::CaseInsensitive) || url.endsWith(".gif", Qt::CaseInsensitive);
if (!lossy && device.size() < MAX_MEMORY_IMAGESIZE) {
QByteArray data = device.readAll();
if (d->image.loadFromData(data)) {
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(data);
qint64 oldKey = d->key;
d->key = KoImageDataPrivate::generateKey(md5.result());
if (oldKey != 0 && d->collection) {
d->collection->update(oldKey, d->key);
}
d->dataStoreState = KoImageDataPrivate::StateImageOnly;
return;
}
}
if (!device.open(QIODevice::ReadOnly)) {
warnFlake << "open file from store " << url << "failed";
d->errorCode = OpenFailed;
return;
}
d->copyToTemporary(device);
} else {
warnFlake << "Find file in store " << url << "failed";
d->errorCode = OpenFailed;
return;
}
}
}
void KoImageData::setImage(const QByteArray &imageData, KoImageCollection *collection)
{
if (collection) {
// let the collection first check if it already has one. If it doesn't it'll call this method
// again and we'll go to the other clause
KoImageData *other = collection->createImageData(imageData);
this->operator=(*other);
delete other;
}
else {
if (d == 0) {
d = new KoImageDataPrivate(this);
d->refCount.ref();
}
d->suffix = "png"; // good default for non-lossy storage.
if (imageData.size() <= MAX_MEMORY_IMAGESIZE) {
QImage image;
if (!image.loadFromData(imageData)) {
// mark the image as invalid, but keep the data in memory
// even if Calligra cannot handle the format, the data should
// be retained
d->errorCode = OpenFailed;
}
d->image = image;
d->dataStoreState = KoImageDataPrivate::StateImageOnly;
}
if (imageData.size() > MAX_MEMORY_IMAGESIZE
|| d->errorCode == OpenFailed) {
d->image = QImage();
// store image data
QBuffer buffer;
buffer.setData(imageData);
buffer.open(QIODevice::ReadOnly);
d->copyToTemporary(buffer);
}
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(imageData);
qint64 oldKey = d->key;
d->key = KoImageDataPrivate::generateKey(md5.result());
if (oldKey != 0 && d->collection) {
d->collection->update(oldKey, d->key);
}
}
}
bool KoImageData::isValid() const
{
return d && d->dataStoreState != KoImageDataPrivate::StateEmpty
&& d->errorCode == Success;
}
bool KoImageData::operator==(const KoImageData &other) const
{
return other.d == d;
}
KoImageData &KoImageData::operator=(const KoImageData &other)
{
if (other.d)
other.d->refCount.ref();
if (d && !d->refCount.deref())
delete d;
d = other.d;
return *this;
}
+KoShapeUserData *KoImageData::clone() const
+{
+ return new KoImageData(*this);
+}
+
qint64 KoImageData::key() const
{
return d->key;
}
QString KoImageData::suffix() const
{
return d->suffix;
}
KoImageData::ErrorCode KoImageData::errorCode() const
{
return d->errorCode;
}
bool KoImageData::saveData(QIODevice &device)
{
return d->saveData(device);
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoImageData.cpp"
diff --git a/libs/flake/KoImageData.h b/libs/flake/KoImageData.h
index d960c78070..38190efa53 100644
--- a/libs/flake/KoImageData.h
+++ b/libs/flake/KoImageData.h
@@ -1,139 +1,141 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 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 KOIMAGEDATA_H
#define KOIMAGEDATA_H
#include "kritaflake_export.h"
#include <QSize>
#include <QMetaType>
#include <KoShapeUserData.h>
class QIODevice;
class QPixmap;
class QImage;
class QSizeF;
class QUrl;
class KoImageCollection;
class KoImageDataPrivate;
class KoStore;
/**
* This class is meant to represent the image data so it can be shared between image shapes.
*
* This class inherits from KoShapeUserData which means you can set it on any KoShape using
* KoShape::setUserData() and get it using KoShape::userData(). The pictureshape plugin
* uses this class to show its image data.
*
* Plugins should not make a copy of the pixmap data, but use the pixmap() method, which
* handles caching.
*/
class KRITAFLAKE_EXPORT KoImageData : public KoShapeUserData
{
Q_OBJECT
public:
/// Various error codes representing what has gone wrong
enum ErrorCode {
Success,
OpenFailed,
StorageFailed, ///< This is set if the image data has to be stored on disk in a temporary file, but we failed to do so
LoadFailed
};
/// default constructor, creates an invalid imageData object
KoImageData();
virtual ~KoImageData();
KoImageData(const KoImageData &imageData);
KoImageData &operator=(const KoImageData &other);
inline bool operator!=(const KoImageData &other) const { return !operator==(other); }
bool operator==(const KoImageData &other) const;
+ KoShapeUserData* clone() const override;
+
void setImage(const QString &location, KoStore *store, KoImageCollection *collection = 0);
/**
* Renders a pixmap the first time you request it is called and returns it.
* @returns the cached pixmap
* @see isValid(), hasCachedPixmap()
*/
QPixmap pixmap(const QSize &targetSize = QSize());
/**
* Return the internal store of the image.
* @see isValid(), hasCachedImage()
*/
QImage image() const;
/**
* The size of the image in points
*/
QSizeF imageSize();
/**
* Save the image data to the param device.
* The full file is saved.
* @param device the device that is used to get the data from.
* @return returns true if load was successful.
*/
bool saveData(QIODevice &device);
/**
* Get a unique key of the image data
*/
qint64 key() const;
/// @return the original image file's extension, e.g. "png" or "gif"
QString suffix() const;
ErrorCode errorCode() const;
/// returns if this is a valid imageData with actual image data present on it.
bool isValid() const;
/// \internal
KoImageDataPrivate *priv() { return d; }
private:
friend class KoImageCollection;
friend class TestImageCollection;
KoImageData(KoImageDataPrivate *priv);
/// returns true only if pixmap() would return immediately with a cached pixmap
bool hasCachedPixmap() const;
/// returns true only if image() would return immediately with a cached image
bool hasCachedImage() const;
void setImage(const QImage &image, KoImageCollection *collection = 0);
void setImage(const QByteArray &imageData, KoImageCollection *collection = 0);
private:
KoImageDataPrivate *d;
Q_PRIVATE_SLOT(d, void cleanupImageCache())
};
Q_DECLARE_METATYPE(KoImageData*)
#endif
diff --git a/libs/flake/KoMarker.cpp b/libs/flake/KoMarker.cpp
index c3566ee5c4..0c51d9144e 100644
--- a/libs/flake/KoMarker.cpp
+++ b/libs/flake/KoMarker.cpp
@@ -1,125 +1,417 @@
/* 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() {
+ 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;
- QString d;
- QPainterPath path;
- QRect viewBox;
+ 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)) {
+
+ 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;
}
-bool KoMarker::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
+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
{
- Q_UNUSED(context);
- // A shape uses draw:marker-end="Arrow" draw:marker-end-width="0.686cm" draw:marker-end-center="true" which marker and how the marker is used
+ return *d == *other.d;
+}
- //<draw:marker draw:name="Arrow" svg:viewBox="0 0 20 30" svg:d="m10 0-10 30h20z"/>
- //<draw:marker draw:name="Arrowheads_20_1" draw:display-name="Arrowheads 1" svg:viewBox="0 0 10 10" svg:d="m0 0h10v10h-10z"/>
+void KoMarker::setCoordinateSystem(KoMarker::MarkerCoordinateSystem value)
+{
+ d->coordinateSystem = value;
+}
- d->d =element.attributeNS(KoXmlNS::svg, "d");
- if (d->d.isEmpty()) {
- return false;
+KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystem() const
+{
+ return d->coordinateSystem;
+}
+
+KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystemFromString(const QString &value)
+{
+ MarkerCoordinateSystem result = StrokeWidth;
+
+ if (value == "userSpaceOnUse") {
+ result = UserSpaceOnUse;
}
-#ifndef NWORKAROUND_ODF_BUGS
- KoOdfWorkaround::fixMarkerPath(d->d);
-#endif
+ return result;
+}
+
+QString KoMarker::coordinateSystemToString(KoMarker::MarkerCoordinateSystem value)
+{
+ return
+ value == StrokeWidth ?
+ "strokeWidth" :
+ "userSpaceOnUse";
+}
+
+void KoMarker::setReferencePoint(const QPointF &value)
+{
+ d->referencePoint = value;
+}
- KoPathShape pathShape;
- KoPathShapeLoader loader(&pathShape);
- loader.parseSvg(d->d, true);
+QPointF KoMarker::referencePoint() const
+{
+ return d->referencePoint;
+}
- d->path = pathShape.outline();
- d->viewBox = KoPathShape::loadOdfViewbox(element);
+void KoMarker::setReferenceSize(const QSizeF &size)
+{
+ d->referenceSize = size;
+}
- QString displayName(element.attributeNS(KoXmlNS::draw, "display-name"));
- if (displayName.isEmpty()) {
- displayName = element.attributeNS(KoXmlNS::draw, "name");
+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);
}
- d->name = displayName;
- return true;
}
-QString KoMarker::saveOdf(KoShapeSavingContext &context) const
+QList<KoShape *> KoMarker::shapes() const
{
- KoGenStyle style(KoGenStyle::MarkerStyle);
- style.addAttribute("draw:display-name", d->name);
- style.addAttribute("svg:d", d->d);
- const QString viewBox = QString::fromLatin1("%1 %2 %3 %4")
- .arg(d->viewBox.x()).arg(d->viewBox.y())
- .arg(d->viewBox.width()).arg(d->viewBox.height());
- style.addAttribute(QLatin1String("svg:viewBox"), viewBox);
- QString name = QString(QUrl::toPercentEncoding(d->name, "", " ")).replace('%', '_');
- return context.mainStyles().insert(style, name, KoGenStyles::DontAddNumberToName);
+ return d->shapes;
}
-QString KoMarker::name() const
+void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
{
- return d->name;
+ 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);
+
+ painter->setTransform(oldTransform);
}
-QPainterPath KoMarker::path(qreal width) const
+qreal KoMarker::maxInset(qreal strokeWidth) const
{
- if (!d->viewBox.isValid() || width == 0) {
- return QPainterPath();
+ 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;
}
- // TODO: currently the <min-x>, <min-y> properties of viewbox are ignored, why? OOo-compat?
- qreal height = width * d->viewBox.height() / d->viewBox.width();
+ return result;
+}
+
+QRectF KoMarker::boundingRect(qreal strokeWidth, qreal nodeAngle) const
+{
+ QRectF shapesBounds = KoShape::boundingRect(d->shapes);
+
+ const QTransform t = d->markerTransform(strokeWidth, nodeAngle);
- QTransform transform;
- transform.scale(width / d->viewBox.width(), height / d->viewBox.height());
- return transform.map(d->path);
+ if (!t.isIdentity()) {
+ shapesBounds = t.mapRect(shapesBounds);
+ }
+
+ return shapesBounds;
}
-bool KoMarker::operator==(const KoMarker &other) const
+QPainterPath KoMarker::outline(qreal strokeWidth, qreal nodeAngle) const
{
- return (d->d == other.d->d && d->viewBox ==other.d->viewBox);
+ QPainterPath outline;
+ Q_FOREACH (KoShape *shape, d->shapes) {
+ outline |= shape->absoluteTransformation(0).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)
+{
+ 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();
+
+ // 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 5b32ae2d52..a50bc27b4f 100644
--- a/libs/flake/KoMarker.h
+++ b/libs/flake/KoMarker.h
@@ -1,81 +1,122 @@
/* 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>
class KoXmlElement;
class KoShapeLoadingContext;
class KoShapeSavingContext;
class QString;
class QPainterPath;
+class KoShape;
+class QPainter;
+class KoShapeStroke;
class KRITAFLAKE_EXPORT KoMarker : public QSharedData
{
public:
KoMarker();
~KoMarker();
/**
- * Load the marker
+ * Display name of the marker
*
- * @param element The xml element containing the marker
- * @param context The shape loading context
+ * @return Display name of the marker
*/
- bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
+ 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;
/**
- * Save the marker
- *
- * @return The reference of the marker.
+ * @brief paintAtOrigin paints the marker at the position \p pos.
+ * Scales and rotates the masrker if needed.
*/
- QString saveOdf(KoShapeSavingContext &context) const;
+ void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
/**
- * Display name of the marker
- *
- * @return Display name of the marker
+ * Return maximum distance that the marker can take outside the shape itself
*/
- QString name() const;
+ qreal maxInset(qreal strokeWidth) const;
/**
- * Get the path of the marker
- *
- * It calculates the offset depending on the line width
- *
- * @param The width of the line the marker is attached to.
- * @return the path of the marker
+ * 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)
*/
- QPainterPath path(qreal width) const;
+ QRectF boundingRect(qreal strokeWidth, qreal nodeAngle) const;
- bool operator==(const KoMarker &other) 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);
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoMarker*)
#endif /* KOMARKER_H */
diff --git a/libs/flake/KoMarkerCollection.cpp b/libs/flake/KoMarkerCollection.cpp
index 7feb7eba4f..22c53dffca 100644
--- a/libs/flake/KoMarkerCollection.cpp
+++ b/libs/flake/KoMarkerCollection.cpp
@@ -1,143 +1,142 @@
/* 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 "KoMarkerCollection.h"
#include <QFile>
+#include <klocalizedstring.h>
#include "KoMarker.h"
-#include "KoMarkerSharedLoadingData.h"
#include <KoXmlReader.h>
-#include <KoShapeLoadingContext.h>
-#include <KoOdfLoadingContext.h>
-#include <KoOdfStylesReader.h>
-#include <KoOdfReadStore.h>
-#include <QStandardPaths>
#include <FlakeDebug.h>
+#include <KoResourcePaths.h>
+#include <SvgParser.h>
+#include <QFileInfo>
+#include <KoDocumentResourceManager.h>
+#include <QXmlStreamReader>
+
+#include "kis_debug.h"
+
+// WARNING: there is a bug in GCC! It doesn't warn that we are
+// deleting an uninitialized type here!
+#include <KoShape.h>
+
class Q_DECL_HIDDEN KoMarkerCollection::Private
{
public:
~Private()
{
}
QList<QExplicitlySharedDataPointer<KoMarker> > markers;
};
KoMarkerCollection::KoMarkerCollection(QObject *parent)
: QObject(parent)
, d(new Private)
{
// Add no marker so the user can remove a marker from the line.
d->markers.append(QExplicitlySharedDataPointer<KoMarker>(0));
// Add default markers
loadDefaultMarkers();
}
KoMarkerCollection::~KoMarkerCollection()
{
delete d;
}
-bool KoMarkerCollection::loadOdf(KoShapeLoadingContext &context)
+void KoMarkerCollection::loadMarkersFromFile(const QString &svgFile)
{
- debugFlake;
- QHash<QString, KoMarker*> lookupTable;
+ QFile file(svgFile);
+ if (!file.exists()) return;
- const QHash<QString, KoXmlElement*> markers = context.odfLoadingContext().stylesReader().drawStyles("marker");
- loadOdfMarkers(markers, context, lookupTable);
+ if (!file.open(QIODevice::ReadOnly)) return;
- KoMarkerSharedLoadingData * sharedMarkerData = new KoMarkerSharedLoadingData(lookupTable);
- context.addSharedData(MARKER_SHARED_LOADING_ID, sharedMarkerData);
+ QXmlStreamReader reader(&file);
+ reader.setNamespaceProcessing(false);
- return true;
-}
+ QString errorMsg;
+ int errorLine = 0;
+ int errorColumn;
-void KoMarkerCollection::loadDefaultMarkers()
-{
- // use the same mechanism for loading the markers that are available
- // per default as when loading the normal markers.
- KoOdfStylesReader markerReader;
- KoOdfLoadingContext odfContext(markerReader, 0);
- KoShapeLoadingContext shapeContext(odfContext, 0);
KoXmlDocument doc;
- QString filePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, "styles/markers.xml");
+ bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn);
+ if (!ok) {
+ errKrita << "Parsing error in " << svgFile << "! 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;
+ }
- if (!filePath.isEmpty()) {
- QFile file(filePath);
- QString errorMessage;
- if (KoOdfReadStore::loadAndParse(&file, doc, errorMessage, filePath)) {
- markerReader.createStyleMap(doc, true);
+ KoDocumentResourceManager manager;
+ SvgParser parser(&manager);
+ parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
+ parser.setXmlBaseDir(QFileInfo(svgFile).absolutePath());
- QHash<QString, KoMarker*> lookupTable;
- const QHash<QString, KoXmlElement*> defaultMarkers = markerReader.drawStyles("marker");
- loadOdfMarkers(defaultMarkers, shapeContext, lookupTable);
- }
- else {
- warnFlake << "reading of" << filePath << "failed:" << errorMessage;
- }
- }
- else {
- debugFlake << "markers.xml not found";
+ parser.setFileFetcher(
+ [](const QString &fileName) {
+ QFile file(fileName);
+ if (!file.exists()) return QByteArray();
+
+ file.open(QIODevice::ReadOnly);
+ return file.readAll();
+ });
+
+ QSizeF fragmentSize;
+ QList<KoShape*> shapes = parser.parseSvg(doc.documentElement(), &fragmentSize);
+ qDeleteAll(shapes);
+
+ Q_FOREACH (const QExplicitlySharedDataPointer<KoMarker> &marker, parser.knownMarkers()) {
+ addMarker(marker.data());
}
}
-void KoMarkerCollection::loadOdfMarkers(const QHash<QString, KoXmlElement*> &markers, KoShapeLoadingContext &context, QHash<QString, KoMarker*> &lookupTable)
+void KoMarkerCollection::loadDefaultMarkers()
{
- QHash<QString, KoXmlElement*>::const_iterator it(markers.constBegin());
- for (; it != markers.constEnd(); ++it) {
- KoMarker *marker = new KoMarker();
- if (marker->loadOdf(*(it.value()), context)) {
- KoMarker *m = addMarker(marker);
- lookupTable.insert(it.key(), m);
- debugFlake << "loaded marker" << it.key() << marker << m;
- if (m != marker) {
- delete marker;
- }
- }
- else {
- delete marker;
- }
- }
+ QString filePath = KoResourcePaths::findResource("data", "styles/markers.svg");
+ loadMarkersFromFile(filePath);
}
QList<KoMarker*> KoMarkerCollection::markers() const
{
QList<KoMarker*> markerList;
foreach (const QExplicitlySharedDataPointer<KoMarker>& m, d->markers){
markerList.append(m.data());
}
return markerList;
}
KoMarker * KoMarkerCollection::addMarker(KoMarker *marker)
{
foreach (const QExplicitlySharedDataPointer<KoMarker>& m, d->markers) {
if (marker == m.data()) {
return marker;
}
if (m && *marker == *m) {
debugFlake << "marker is the same as other";
return m.data();
}
}
d->markers.append(QExplicitlySharedDataPointer<KoMarker>(marker));
return marker;
}
diff --git a/libs/flake/KoMarkerCollection.h b/libs/flake/KoMarkerCollection.h
index 66a0dd063c..56a280da9f 100644
--- a/libs/flake/KoMarkerCollection.h
+++ b/libs/flake/KoMarkerCollection.h
@@ -1,71 +1,68 @@
/* 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 KOMARKERCOLLECTION_H
#define KOMARKERCOLLECTION_H
#include "kritaflake_export.h"
#include <QObject>
#include <QList>
#include <QHash>
#include <QMetaType>
class KoMarker;
class KoXmlElement;
class KoShapeLoadingContext;
class KRITAFLAKE_EXPORT KoMarkerCollection : public QObject
{
Q_OBJECT
public:
explicit KoMarkerCollection(QObject *parent = 0);
virtual ~KoMarkerCollection();
- bool loadOdf(KoShapeLoadingContext &context);
- // For now we only save the used markers and that is done with a KoSharedSavingData when a marker usage is encountered.
- //void saveOdf(KoShapeSavingContext &context) const;
-
QList<KoMarker*> markers() const;
/**
* Add marker to collection
*
* The collection checks if a marker with the same content exists and if so deletes the
* passed marker and returns a pointer to an existing marker. If no such marker exists it
* adds the marker and return the same pointer as passed.
* Calling that function passes ownership of the marker to this class.
*
* @param marker Marker to add
* @return pointer to marker that should be used. This might be different to the marker passed
*/
KoMarker * addMarker(KoMarker *marker);
+ void loadMarkersFromFile(const QString &svgFile);
+
private:
/// load the markers that are available per default.
void loadDefaultMarkers();
- void loadOdfMarkers(const QHash<QString, KoXmlElement*> &markers, KoShapeLoadingContext &context, QHash<QString, KoMarker*> &lookupTable);
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoMarkerCollection *)
#endif /* KOMARKERCOLLECTION_H */
diff --git a/libs/flake/KoMarkerData.cpp b/libs/flake/KoMarkerData.cpp
deleted file mode 100644
index 0b55e70bdd..0000000000
--- a/libs/flake/KoMarkerData.cpp
+++ /dev/null
@@ -1,173 +0,0 @@
-/* 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 "KoMarkerData.h"
-
-#include <FlakeDebug.h>
-#include <KoStyleStack.h>
-#include <KoXmlNS.h>
-#include <KoUnit.h>
-#include <KoOdfLoadingContext.h>
-#include <KoGenStyle.h>
-#include "KoShapeLoadingContext.h"
-#include "KoMarker.h"
-#include "KoMarkerSharedLoadingData.h"
-
-/**
- * This defines the factor the width of the arrow is widened
- * when the width of the line is changed.
- */
-static const qreal ResizeFactor = 1.5;
-
-static const struct {
- const char * m_markerPositionLoad;
- const char * m_markerWidthLoad;
- const char * m_markerCenterLoad;
- const char * m_markerPositionSave;
- const char * m_markerWidthSave;
- const char * m_markerCenterSave;
-} markerOdfData[] = {
- { "marker-start", "marker-start-width", "marker-start-center", "draw:marker-start", "draw:marker-start-width", "draw:marker-start-center" },
- { "marker-end" , "marker-end-width", "marker-end-center", "draw:marker-end" , "draw:marker-end-width", "draw:marker-end-center" }
-};
-
-class Q_DECL_HIDDEN KoMarkerData::Private
-{
-public:
- Private(KoMarker *marker, qreal baseWidth, KoMarkerData::MarkerPosition position, bool center)
- : marker(marker)
- , baseWidth(baseWidth)
- , position(position)
- , center(center)
- {}
-
- QExplicitlySharedDataPointer<KoMarker> marker;
- qreal baseWidth;
- MarkerPosition position;
- bool center;
-};
-
-KoMarkerData::KoMarkerData(KoMarker *marker, qreal width, MarkerPosition position, bool center)
-: d(new Private(marker, width, position, center))
-{
-}
-
-KoMarkerData::KoMarkerData(MarkerPosition position)
-: d(new Private(0, 0, position, false))
-{
-}
-
-KoMarkerData::KoMarkerData()
-: d(0)
-{
- Q_ASSERT(0);
-}
-
-KoMarkerData::KoMarkerData(const KoMarkerData &other)
-: d(new Private(other.d->marker.data(), other.d->baseWidth, other.d->position, other.d->center))
-{
-}
-
-KoMarkerData::~KoMarkerData()
-{
- delete d;
-}
-
-
-KoMarker *KoMarkerData::marker() const
-{
- return d->marker.data();
-}
-
-void KoMarkerData::setMarker(KoMarker *marker)
-{
- d->marker = QExplicitlySharedDataPointer<KoMarker>(marker);
-}
-
-qreal KoMarkerData::width(qreal penWidth) const
-{
- return d->baseWidth + penWidth * ResizeFactor;
-}
-
-void KoMarkerData::setWidth(qreal width, qreal penWidth)
-{
- d->baseWidth = qMax(qreal(0.0), width - penWidth * ResizeFactor);
-}
-
-KoMarkerData::MarkerPosition KoMarkerData::position() const
-{
- return d->position;
-}
-
-void KoMarkerData::setPosition(MarkerPosition position)
-{
- d->position = position;
-}
-
-bool KoMarkerData::center() const
-{
- return d->center;
-}
-
-void KoMarkerData::setCenter(bool center)
-{
- d->center = center;
-}
-
-KoMarkerData &KoMarkerData::operator=(const KoMarkerData &other)
-{
- if (this != &other) {
- d->marker = other.d->marker;
- d->baseWidth = other.d->baseWidth;
- d->position = other.d->position;
- d->center = other.d->center;
- }
- return (*this);
-}
-
-bool KoMarkerData::loadOdf(qreal penWidth, KoShapeLoadingContext &context)
-{
- KoMarkerSharedLoadingData *markerShared = dynamic_cast<KoMarkerSharedLoadingData*>(context.sharedData(MARKER_SHARED_LOADING_ID));
- if (markerShared) {
- KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
- // draw:marker-end="Arrow" draw:marker-end-width="0.686cm" draw:marker-end-center="true"
- const QString markerStart(styleStack.property(KoXmlNS::draw, markerOdfData[d->position].m_markerPositionLoad));
- const QString markerStartWidth(styleStack.property(KoXmlNS::draw, markerOdfData[d->position].m_markerWidthLoad));
- if (!markerStart.isEmpty() && !markerStartWidth.isEmpty()) {
- KoMarker *marker = markerShared->marker(markerStart);
- if (marker) {
- setMarker(marker);
- qreal markerWidth = KoUnit::parseValue(markerStartWidth);
- setWidth(markerWidth, penWidth);
- setCenter(styleStack.property(KoXmlNS::draw, markerOdfData[d->position].m_markerCenterLoad) == "true");
- }
- }
- }
- return true;
-}
-
-void KoMarkerData::saveStyle(KoGenStyle &style, qreal penWidth, KoShapeSavingContext &context) const
-{
- if (d->marker) {
- QString markerRef = d->marker->saveOdf(context);
- style.addProperty(markerOdfData[d->position].m_markerPositionSave, markerRef, KoGenStyle::GraphicType);
- style.addPropertyPt(markerOdfData[d->position].m_markerWidthSave, width(penWidth), KoGenStyle::GraphicType);
- style.addProperty(markerOdfData[d->position].m_markerCenterSave, d->center, KoGenStyle::GraphicType);
- }
-}
diff --git a/libs/flake/KoMarkerData.h b/libs/flake/KoMarkerData.h
deleted file mode 100644
index c21b5bc0a3..0000000000
--- a/libs/flake/KoMarkerData.h
+++ /dev/null
@@ -1,136 +0,0 @@
-/* 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 KOMARKERDATA_H
-#define KOMARKERDATA_H
-
-#include <QtGlobal>
-
-#include "kritaflake_export.h"
-
-class KoGenStyle;
-class KoMarker;
-class KoShapeLoadingContext;
-class KoShapeSavingContext;
-
-class KRITAFLAKE_EXPORT KoMarkerData
-{
-public:
- /// Property enum
- enum MarkerPosition {
- MarkerStart, ///< it is the marker where the Path starts
- MarkerEnd ///< it is the marker where the Path ends
- };
-
- KoMarkerData(KoMarker *marker, qreal width, MarkerPosition position, bool center);
- explicit KoMarkerData(MarkerPosition position);
- KoMarkerData(const KoMarkerData &other);
- ~KoMarkerData();
-
- /**
- * Get the marker
- *
- * @return the marker or 0 if no marker is set
- */
- KoMarker *marker() const;
-
- /**
- * Set the marker
- *
- * @param marker The marker that is set or 0 to remove the marker
- */
- void setMarker(KoMarker *marker);
-
- /**
- * Get the with of the marker according to the pen width
- */
- qreal width(qreal penWidth) const;
-
- /**
- * Set the width of the marker
- *
- * This calculates a base width for the marker so the width of the marker changes
- * with the width of the line.
- *
- * @param width The width of the marker
- * @param penWidth The width of the used pen
- */
- void setWidth(qreal width, qreal penWidth);
-
- /**
- * Get the position of the marker
- *
- * @return Position of the marker
- */
- MarkerPosition position() const;
-
- /**
- * Set the position of the marker
- *
- * @param position Position of the marker
- */
- void setPosition(MarkerPosition position);
-
- /**
- * Get the center property of the marker
- *
- * If the marker is centered at the start of the stroke the line will get longer.
- *
- * @return Returns true if the marker is centered at the start of the stroke.
- */
- bool center() const;
-
- /**
- * Set the center property of the marker
- *
- * @param center If set to true the marker should be centered at the start of the stroke.
- */
- void setCenter(bool center);
-
- /**
- * Compare the marker data
- */
- KoMarkerData &operator=(const KoMarkerData &other);
-
- /**
- * Load the marker data
- *
- * @param penWidth the used pen width of the line
- * @param context The shape loading context
- */
- bool loadOdf(qreal penWidth, KoShapeLoadingContext &context);
-
- /**
- * Save the marker data to the style
- *
- * @param style The style that we add the marker data to
- * @param penWidth the used pen width of the line
- * @param context The shape saving context
- */
- void saveStyle(KoGenStyle &style, qreal penWidth, KoShapeSavingContext &context) const;
-
-private:
- // make private to be sure it is not used
- KoMarkerData();
-
- class Private;
- Private * const d;
-};
-
-#endif /* KOMARKERDATA_H */
diff --git a/libs/flake/KoMarkerSharedLoadingData.cpp b/libs/flake/KoMarkerSharedLoadingData.cpp
deleted file mode 100644
index 2d7a699627..0000000000
--- a/libs/flake/KoMarkerSharedLoadingData.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/* 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 "KoMarkerSharedLoadingData.h"
-
-#include <QString>
-
-class KoMarkerSharedLoadingData::Private
-{
-public:
- QHash<QString, KoMarker *> lookupTable;
-};
-
-KoMarkerSharedLoadingData::KoMarkerSharedLoadingData(const QHash<QString, KoMarker *> &lookupTable)
-: d(new Private())
-{
- d->lookupTable = lookupTable;
-}
-
-KoMarkerSharedLoadingData::~KoMarkerSharedLoadingData()
-{
- delete d;
-}
-
-KoMarker *KoMarkerSharedLoadingData::marker(const QString &name) const
-{
- return d->lookupTable.value(name, 0);
-}
diff --git a/libs/flake/KoMarkerSharedLoadingData.h b/libs/flake/KoMarkerSharedLoadingData.h
deleted file mode 100644
index d8f0af125e..0000000000
--- a/libs/flake/KoMarkerSharedLoadingData.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* 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 KOMARKERSHAREDLOADINGDATA_H
-#define KOMARKERSHAREDLOADINGDATA_H
-
-#include "KoSharedLoadingData.h"
-
-#include <QHash>
-
-#define MARKER_SHARED_LOADING_ID "KoMarkerShareadLoadingId"
-
-class KoMarker;
-class QString;
-
-class KoMarkerSharedLoadingData : public KoSharedLoadingData
-{
-public:
- KoMarkerSharedLoadingData(const QHash<QString, KoMarker *> &lookupTable);
- virtual ~KoMarkerSharedLoadingData();
-
- KoMarker *marker(const QString &name) const;
-
-private:
- class Private;
- Private * const d;
-};
-
-#endif /* KOMARKERSHAREDLOADINGDATA_H */
diff --git a/libs/flake/KoOdfGradientBackground.cpp b/libs/flake/KoOdfGradientBackground.cpp
index 702f01f772..40c496d01a 100644
--- a/libs/flake/KoOdfGradientBackground.cpp
+++ b/libs/flake/KoOdfGradientBackground.cpp
@@ -1,370 +1,376 @@
/* 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 "KoShapeBackground_p.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>
class KoOdfGradientBackgroundPrivate : public KoShapeBackgroundPrivate
{
public:
KoOdfGradientBackgroundPrivate()
: style(), cx(0), cy(0), startColor(), endColor(), angle(0), border(0), opacity(1.0) {};
~KoOdfGradientBackgroundPrivate() override{};
//data
QString style;
int cx;
int cy;
QColor startColor;
QColor endColor;
qreal angle;
qreal border;
qreal opacity;
};
KoOdfGradientBackground::KoOdfGradientBackground()
: KoShapeBackground(*(new KoOdfGradientBackgroundPrivate()))
{
}
KoOdfGradientBackground::~KoOdfGradientBackground()
{
}
+bool KoOdfGradientBackground::compareTo(const KoShapeBackground *other) const
+{
+ Q_UNUSED(other);
+ return false;
+}
+
bool KoOdfGradientBackground::loadOdf(const KoXmlElement& e)
{
Q_D(KoOdfGradientBackground);
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
{
Q_D(const KoOdfGradientBackground);
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
{
Q_D(const KoOdfGradientBackground);
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);
Q_D(KoOdfGradientBackground);
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
{
Q_D(const KoOdfGradientBackground);
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
{
Q_D(const KoOdfGradientBackground);
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
{
Q_D(const KoOdfGradientBackground);
qDebug() << "cx,cy: "<< d->cx << d->cy;
qDebug() << "style" << d->style;
qDebug() << "colors" << d->startColor << d->endColor;
qDebug() << "angle:" << d->angle;
qDebug() << "border" << d->border;
}
diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h
index 421210f74c..2170ef2aac 100644
--- a/libs/flake/KoOdfGradientBackground.h
+++ b/libs/flake/KoOdfGradientBackground.h
@@ -1,64 +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;
class KoOdfGradientBackgroundPrivate;
class KoXmlElement;
class KoGenStyles;
class KoGenStyle;
/// Gradients from odf that are not native to Qt
class KoOdfGradientBackground : public KoShapeBackground {
public:
// constructor
KoOdfGradientBackground();
// destructor
virtual ~KoOdfGradientBackground();
+ bool compareTo(const KoShapeBackground *other) const override;
+
/// reimplemented from KoShapeBackground
virtual void fillStyle(KoGenStyle& style, KoShapeSavingContext& context);
/// reimplemented from KoShapeBackground
virtual bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize);
/// reimplemented from KoShapeBackground
virtual void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const;
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:
Q_DECLARE_PRIVATE(KoOdfGradientBackground)
Q_DISABLE_COPY(KoOdfGradientBackground)
};
#endif
diff --git a/libs/flake/KoParameterShape.cpp b/libs/flake/KoParameterShape.cpp
index c6c0b0d265..684e5a09ee 100644
--- a/libs/flake/KoParameterShape.cpp
+++ b/libs/flake/KoParameterShape.cpp
@@ -1,172 +1,165 @@
/* This file is part of the KDE project
Copyright (C) 2006 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.
*/
#include "KoParameterShape.h"
#include "KoParameterShape_p.h"
+#include <KisHandlePainterHelper.h>
+
#include <QPainter>
#include <FlakeDebug.h>
+KoParameterShapePrivate::KoParameterShapePrivate(KoParameterShape *shape)
+ : KoPathShapePrivate(shape),
+ parametric(true)
+{
+}
+
+KoParameterShapePrivate::KoParameterShapePrivate(const KoParameterShapePrivate &rhs, KoParameterShape *q)
+ : KoPathShapePrivate(rhs, q),
+ handles(rhs.handles)
+{
+}
+
KoParameterShape::KoParameterShape()
- : KoPathShape(*(new KoParameterShapePrivate(this)))
+ : KoPathShape(new KoParameterShapePrivate(this))
{
}
-KoParameterShape::KoParameterShape(KoParameterShapePrivate &dd)
+KoParameterShape::KoParameterShape(KoParameterShapePrivate *dd)
: KoPathShape(dd)
{
}
KoParameterShape::~KoParameterShape()
{
}
void KoParameterShape::moveHandle(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers)
{
Q_D(KoParameterShape);
if (handleId >= d->handles.size()) {
warnFlake << "handleId out of bounds";
return;
}
update();
// function to do special stuff
moveHandleAction(handleId, documentToShape(point), modifiers);
updatePath(size());
update();
- d->shapeChanged(ParameterChanged);
}
int KoParameterShape::handleIdAt(const QRectF & rect) const
{
Q_D(const KoParameterShape);
int handle = -1;
for (int i = 0; i < d->handles.size(); ++i) {
if (rect.contains(d->handles.at(i))) {
handle = i;
break;
}
}
return handle;
}
QPointF KoParameterShape::handlePosition(int handleId) const
{
Q_D(const KoParameterShape);
return d->handles.value(handleId);
}
-void KoParameterShape::paintHandles(QPainter & painter, const KoViewConverter & converter, int handleRadius)
+void KoParameterShape::paintHandles(KisHandlePainterHelper &handlesHelper)
{
Q_D(KoParameterShape);
- applyConversion(painter, converter);
-
- QTransform worldMatrix = painter.worldTransform();
- painter.setTransform(QTransform());
-
- QTransform matrix;
- matrix.rotate(45.0);
- QPolygonF poly(d->handleRect(QPointF(0, 0), handleRadius));
- poly = matrix.map(poly);
QList<QPointF>::const_iterator it(d->handles.constBegin());
for (; it != d->handles.constEnd(); ++it) {
- QPointF moveVector = worldMatrix.map(*it);
- poly.translate(moveVector.x(), moveVector.y());
- painter.drawPolygon(poly);
- poly.translate(-moveVector.x(), -moveVector.y());
+ handlesHelper.drawGradientHandle(*it);
}
}
-void KoParameterShape::paintHandle(QPainter & painter, const KoViewConverter & converter, int handleId, int handleRadius)
+void KoParameterShape::paintHandle(KisHandlePainterHelper &handlesHelper, int handleId)
{
Q_D(KoParameterShape);
- applyConversion(painter, converter);
-
- QTransform worldMatrix = painter.worldTransform();
- painter.setTransform(QTransform());
-
- QTransform matrix;
- matrix.rotate(45.0);
- QPolygonF poly(d->handleRect(QPointF(0, 0), handleRadius));
- poly = matrix.map(poly);
- poly.translate(worldMatrix.map(d->handles[handleId]));
- painter.drawPolygon(poly);
+ handlesHelper.drawGradientHandle(d->handles[handleId]);
}
void KoParameterShape::setSize(const QSizeF &newSize)
{
Q_D(KoParameterShape);
QTransform matrix(resizeMatrix(newSize));
for (int i = 0; i < d->handles.size(); ++i) {
d->handles[i] = matrix.map(d->handles[i]);
}
KoPathShape::setSize(newSize);
}
QPointF KoParameterShape::normalize()
{
Q_D(KoParameterShape);
QPointF offset(KoPathShape::normalize());
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
for (int i = 0; i < d->handles.size(); ++i) {
d->handles[i] = matrix.map(d->handles[i]);
}
return offset;
}
bool KoParameterShape::isParametricShape() const
{
Q_D(const KoParameterShape);
return d->parametric;
}
void KoParameterShape::setParametricShape(bool parametric)
{
Q_D(KoParameterShape);
d->parametric = parametric;
update();
}
QList<QPointF> KoParameterShape::handles() const
{
Q_D(const KoParameterShape);
return d->handles;
}
void KoParameterShape::setHandles(const QList<QPointF> &handles)
{
Q_D(KoParameterShape);
d->handles = handles;
+
+ d->shapeChanged(ParameterChanged);
}
int KoParameterShape::handleCount() const
{
Q_D(const KoParameterShape);
return d->handles.count();
}
diff --git a/libs/flake/KoParameterShape.h b/libs/flake/KoParameterShape.h
index f20d483656..05ce7ce9a1 100644
--- a/libs/flake/KoParameterShape.h
+++ b/libs/flake/KoParameterShape.h
@@ -1,164 +1,165 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOPARAMETERSHAPE_H
#define KOPARAMETERSHAPE_H
#include "KoPathShape.h"
#include "kritaflake_export.h"
class KoParameterShapePrivate;
+class KisHandlePainterHelper;
/**
* KoParameterShape is the base class for all parametric shapes
* in flake.
* Parametric shapes are those whose appearance can be completely
* defined by a few numerical parameters. Rectangle, ellipse and star
* are examples of parametric shapes.
* In flake, these shape parameters can be manipulated visually by means
* of control points. These control points can be moved with the mouse
* on the canvas which changes the shapes parameter values and hence the
* shapes appearance in realtime.
* KoParameterShape is derived from the KoPathShape class that means
* by changing the shape parameters, the underlying path is manipulated.
* A parametric shape can be converted into a path shape by simply calling
* the setModified method. This makes the path tool know that it can handle
* the shape like a path shape, so that modifying the single path points
* is possible.
*/
class KRITAFLAKE_EXPORT KoParameterShape : public KoPathShape
{
public:
KoParameterShape();
virtual ~KoParameterShape();
/**
* @brief Move handle to point
*
* This method calls moveHandleAction. Overload moveHandleAction to get the behaviour you want.
* After that updatePath and a repaint is called.
*
* @param handleId the id of the handle to move
* @param point the point to move the handle to in document coordinates
* @param modifiers the keyboard modifiers used during moving the handle
*/
void moveHandle(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
/**
* @brief Get the id of the handle within the given rect
*
* @param rect the rect in shape coordinates
* @return id of the found handle or -1 if none was found
*/
int handleIdAt(const QRectF &rect) const;
/**
* @brief Get the handle position
*
* @param handleId the id of the handle for which to get the position in shape coordinates
*/
QPointF handlePosition(int handleId) const;
/**
* @brief Paint the handles
*
* @param painter the painter to paint the handles on
* @param converter the view converter for applying the actual zoom
* @param handleRadius the radius of the handles used for painting
*/
- void paintHandles(QPainter &painter, const KoViewConverter &converter, int handleRadius);
+ void paintHandles(KisHandlePainterHelper &handlesHelper);
/**
* @brief Paint the given handles
*
* @param painter the painter to paint the handles on
* @param converter the view converter for applying the actual zoom
* @param handleId of the handle which should be repainted
* @param handleRadius the radius of the handle used for painting
*/
- void paintHandle(QPainter &painter, const KoViewConverter &converter, int handleId, int handleRadius);
+ void paintHandle(KisHandlePainterHelper &handlesHelper, int handleId);
/// reimplemented from KoShape
virtual void setSize(const QSizeF &size);
/**
* @brief Check if object is a parametric shape
*
* It is no longer a parametric shape when the path was manipulated
*
* @return true if it is a parametic shape, false otherwise
*/
bool isParametricShape() const;
/**
* @brief Set if the shape can be modified using parameters
*
* After the state is set to false it is no longer possible to work
* with parameters on this shape.
*
* @param parametric the new state
* @see isParametricShape
*/
void setParametricShape(bool parametric);
virtual QPointF normalize();
/// return the number of handles set on the shape
int handleCount() const;
protected:
/**
* Get the handle positions for manipulating the parameters.
* @see setHandles, handleCount()
*/
QList<QPointF> handles() const;
/**
* Set the new handle positions which are used by the user to manipulate the parameters.
* @see handles(), handleCount()
*/
void setHandles(const QList<QPointF> &handles);
/// constructor
- KoParameterShape(KoParameterShapePrivate &);
+ KoParameterShape(KoParameterShapePrivate *);
/**
* @brief Updates the internal state of a KoParameterShape.
*
* This method is called from moveHandle.
*
* @param handleId of the handle
* @param point to move the handle to in shape coordinates
* @param modifiers used during move to point
*/
virtual void moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers = Qt::NoModifier) = 0;
/**
* @brief Update the path of the parameter shape
*
* @param size of the shape
*/
virtual void updatePath(const QSizeF &size) = 0;
-private:
+protected:
Q_DECLARE_PRIVATE(KoParameterShape)
};
#endif /* KOPARAMETERSHAPE_H */
diff --git a/libs/flake/KoParameterShape_p.h b/libs/flake/KoParameterShape_p.h
index 35d67cbb3a..2d7880de52 100644
--- a/libs/flake/KoParameterShape_p.h
+++ b/libs/flake/KoParameterShape_p.h
@@ -1,44 +1,45 @@
/* This file is part of the KDE project
Copyright (C) 2006 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 KOPARAMETERSHAPE_P_H
#define KOPARAMETERSHAPE_P_H
+#include "kritaflake_export.h"
+#include <KoParameterShape.h>
#include "KoPathShape_p.h"
#include <QList>
#include <QPointF>
-class KoParameterShapePrivate : public KoPathShapePrivate
+class KoParameterShape;
+
+class KRITAFLAKE_EXPORT KoParameterShapePrivate : public KoPathShapePrivate
{
public:
- explicit KoParameterShapePrivate(KoParameterShape *shape)
- : KoPathShapePrivate(shape),
- parametric(true)
- {
- }
+ explicit KoParameterShapePrivate(KoParameterShape *shape);
+ explicit KoParameterShapePrivate(const KoParameterShapePrivate &rhs, KoParameterShape *q);
bool parametric;
/// the handles that the user can grab and change
QList<QPointF> handles;
};
#endif
diff --git a/libs/flake/KoPathPoint.cpp b/libs/flake/KoPathPoint.cpp
index b022c9caaf..765207d566 100644
--- a/libs/flake/KoPathPoint.cpp
+++ b/libs/flake/KoPathPoint.cpp
@@ -1,407 +1,405 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoPathPoint.h"
#include "KoPathShape.h"
#include <FlakeDebug.h>
#include <QPainter>
#include <QPointF>
+#include <KisHandlePainterHelper.h>
#include <math.h>
#include <qnumeric.h> // for qIsNaN
static bool qIsNaNPoint(const QPointF &p) {
return qIsNaN(p.x()) || qIsNaN(p.y());
}
class Q_DECL_HIDDEN KoPathPoint::Private
{
public:
Private()
: shape(0), properties(Normal)
, activeControlPoint1(false), activeControlPoint2(false) {}
KoPathShape * shape;
QPointF point;
QPointF controlPoint1;
QPointF controlPoint2;
PointProperties properties;
bool activeControlPoint1;
bool activeControlPoint2;
};
KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint)
: d(new Private())
{
- d->shape = pathPoint.d->shape;
+ d->shape = 0;
d->point = pathPoint.d->point;
d->controlPoint1 = pathPoint.d->controlPoint1;
d->controlPoint2 = pathPoint.d->controlPoint2;
d->properties = pathPoint.d->properties;
d->activeControlPoint1 = pathPoint.d->activeControlPoint1;
d->activeControlPoint2 = pathPoint.d->activeControlPoint2;
}
+KoPathPoint::KoPathPoint(const KoPathPoint &pathPoint, KoPathShape *newParent)
+ : KoPathPoint(pathPoint)
+{
+ d->shape = newParent;
+}
+
KoPathPoint::KoPathPoint()
: d(new Private())
{
}
KoPathPoint::KoPathPoint(KoPathShape * path, const QPointF &point, PointProperties properties)
: d(new Private())
{
d->shape = path;
d->point = point;
d->controlPoint1 = point;
d->controlPoint2 = point;
d->properties = properties;
}
KoPathPoint::~KoPathPoint()
{
delete d;
}
KoPathPoint &KoPathPoint::operator=(const KoPathPoint &rhs)
{
if (this == &rhs)
return (*this);
d->shape = rhs.d->shape;
d->point = rhs.d->point;
d->controlPoint1 = rhs.d->controlPoint1;
d->controlPoint2 = rhs.d->controlPoint2;
d->properties = rhs.d->properties;
d->activeControlPoint1 = rhs.d->activeControlPoint1;
d->activeControlPoint2 = rhs.d->activeControlPoint2;
return (*this);
}
bool KoPathPoint::operator == (const KoPathPoint &rhs) const
{
if (d->point != rhs.d->point)
return false;
if (d->controlPoint1 != rhs.d->controlPoint1)
return false;
if (d->controlPoint2 != rhs.d->controlPoint2)
return false;
if (d->properties != rhs.d->properties)
return false;
if (d->activeControlPoint1 != rhs.d->activeControlPoint1)
return false;
if (d->activeControlPoint2 != rhs.d->activeControlPoint2)
return false;
return true;
}
void KoPathPoint::setPoint(const QPointF &point)
{
d->point = point;
if (d->shape)
d->shape->notifyChanged();
}
void KoPathPoint::setControlPoint1(const QPointF &point)
{
if (qIsNaNPoint(point)) return;
d->controlPoint1 = point;
d->activeControlPoint1 = true;
if (d->shape)
d->shape->notifyChanged();
}
void KoPathPoint::setControlPoint2(const QPointF &point)
{
if (qIsNaNPoint(point)) return;
d->controlPoint2 = point;
d->activeControlPoint2 = true;
if (d->shape)
d->shape->notifyChanged();
}
void KoPathPoint::removeControlPoint1()
{
d->activeControlPoint1 = false;
d->properties &= ~IsSmooth;
d->properties &= ~IsSymmetric;
if (d->shape)
d->shape->notifyChanged();
}
void KoPathPoint::removeControlPoint2()
{
d->activeControlPoint2 = false;
d->properties &= ~IsSmooth;
d->properties &= ~IsSymmetric;
if (d->shape)
d->shape->notifyChanged();
}
void KoPathPoint::setProperties(PointProperties properties)
{
d->properties = properties;
// CloseSubpath only allowed with StartSubpath or StopSubpath
if ((d->properties & StartSubpath) == 0 && (d->properties & StopSubpath) == 0)
d->properties &= ~CloseSubpath;
if (! activeControlPoint1() || ! activeControlPoint2()) {
// strip smooth and symmetric flags if point has not two control points
d->properties &= ~IsSmooth;
d->properties &= ~IsSymmetric;
}
if (d->shape)
d->shape->notifyChanged();
}
void KoPathPoint::setProperty(PointProperty property)
{
switch (property) {
case StartSubpath:
case StopSubpath:
case CloseSubpath:
// nothing special to do here
break;
case IsSmooth:
d->properties &= ~IsSymmetric;
break;
case IsSymmetric:
d->properties &= ~IsSmooth;
break;
default: return;
}
d->properties |= property;
if (! activeControlPoint1() || ! activeControlPoint2()) {
// strip smooth and symmetric flags if point has not two control points
d->properties &= ~IsSymmetric;
d->properties &= ~IsSmooth;
}
}
void KoPathPoint::unsetProperty(PointProperty property)
{
switch (property) {
case StartSubpath:
if (d->properties & StartSubpath && (d->properties & StopSubpath) == 0)
d->properties &= ~CloseSubpath;
break;
case StopSubpath:
if (d->properties & StopSubpath && (d->properties & StartSubpath) == 0)
d->properties &= ~CloseSubpath;
break;
case CloseSubpath:
if (d->properties & StartSubpath || d->properties & StopSubpath) {
d->properties &= ~IsSmooth;
d->properties &= ~IsSymmetric;
}
break;
case IsSmooth:
case IsSymmetric:
// no others depend on these
break;
default: return;
}
d->properties &= ~property;
}
bool KoPathPoint::activeControlPoint1() const
{
// only start point on closed subpaths can have a controlPoint1
if ((d->properties & StartSubpath) && (d->properties & CloseSubpath) == 0)
return false;
return d->activeControlPoint1;
}
bool KoPathPoint::activeControlPoint2() const
{
// only end point on closed subpaths can have a controlPoint2
if ((d->properties & StopSubpath) && (d->properties & CloseSubpath) == 0)
return false;
return d->activeControlPoint2;
}
void KoPathPoint::map(const QTransform &matrix)
{
d->point = matrix.map(d->point);
d->controlPoint1 = matrix.map(d->controlPoint1);
d->controlPoint2 = matrix.map(d->controlPoint2);
if (d->shape)
d->shape->notifyChanged();
}
-void KoPathPoint::paint(QPainter &painter, int handleRadius, PointTypes types, bool active)
+void KoPathPoint::paint(KisHandlePainterHelper &handlesHelper, PointTypes types, bool active)
{
- QRectF handle(-handleRadius, -handleRadius, 2*handleRadius, 2*handleRadius);
-
bool drawControlPoint1 = types & ControlPoint1 && (!active || activeControlPoint1());
bool drawControlPoint2 = types & ControlPoint2 && (!active || activeControlPoint2());
// draw lines at the bottom
- if (drawControlPoint2)
- painter.drawLine(point(), controlPoint2());
-
- if (drawControlPoint1)
- painter.drawLine(point(), controlPoint1());
-
-
- QTransform worldMatrix = painter.worldTransform();
+ if (drawControlPoint2) {
+ handlesHelper.drawConnectionLine(point(), controlPoint2());
+ }
- painter.setWorldTransform(QTransform());
+ if (drawControlPoint1) {
+ handlesHelper.drawConnectionLine(point(), controlPoint1());
+ }
// the point is lowest
if (types & Node) {
- if (properties() & IsSmooth)
- painter.drawRect(handle.translated(worldMatrix.map(point())));
- else if (properties() & IsSymmetric) {
- QTransform matrix;
- matrix.rotate(45.0);
- QPolygonF poly(handle);
- poly = matrix.map(poly);
- poly.translate(worldMatrix.map(point()));
- painter.drawPolygon(poly);
- } else
- painter.drawEllipse(handle.translated(worldMatrix.map(point())));
+ if (properties() & IsSmooth) {
+ handlesHelper.drawHandleRect(point());
+ } else if (properties() & IsSymmetric) {
+ handlesHelper.drawGradientHandle(point());
+ } else {
+ handlesHelper.drawHandleCircle(point());
+ }
}
// then comes control point 2
- if (drawControlPoint2)
- painter.drawEllipse(handle.translated(worldMatrix.map(controlPoint2())));
+ if (drawControlPoint2) {
+ handlesHelper.drawHandleSmallCircle(controlPoint2());
+ }
// then comes control point 1
- if (drawControlPoint1)
- painter.drawEllipse(handle.translated(worldMatrix.map(controlPoint1())));
-
- painter.setWorldTransform(worldMatrix);
+ if (drawControlPoint1) {
+ handlesHelper.drawHandleSmallCircle(controlPoint1());
+ }
}
void KoPathPoint::setParent(KoPathShape* parent)
{
// don't set to zero
//Q_ASSERT(parent);
d->shape = parent;
}
QRectF KoPathPoint::boundingRect(bool active) const
{
QRectF rect(d->point, QSize(1, 1));
if (!active && activeControlPoint1()) {
QRectF r1(d->point, QSize(1, 1));
r1.setBottomRight(d->controlPoint1);
rect = rect.united(r1);
}
if (!active && activeControlPoint2()) {
QRectF r2(d->point, QSize(1, 1));
r2.setBottomRight(d->controlPoint2);
rect = rect.united(r2);
}
if (d->shape)
return d->shape->shapeToDocument(rect);
else
return rect;
}
void KoPathPoint::reverse()
{
qSwap(d->controlPoint1, d->controlPoint2);
qSwap(d->activeControlPoint1, d->activeControlPoint2);
PointProperties newProps = Normal;
newProps |= d->properties & IsSmooth;
newProps |= d->properties & IsSymmetric;
newProps |= d->properties & StartSubpath;
newProps |= d->properties & StopSubpath;
newProps |= d->properties & CloseSubpath;
d->properties = newProps;
}
bool KoPathPoint::isSmooth(KoPathPoint * prev, KoPathPoint * next) const
{
QPointF t1, t2;
if (activeControlPoint1()) {
t1 = point() - controlPoint1();
} else {
// we need the previous path point but there is none provided
if (! prev)
return false;
if (prev->activeControlPoint2())
t1 = point() - prev->controlPoint2();
else
t1 = point() - prev->point();
}
if (activeControlPoint2()) {
t2 = controlPoint2() - point();
} else {
// we need the next path point but there is none provided
if (! next)
return false;
if (next->activeControlPoint1())
t2 = next->controlPoint1() - point();
else
t2 = next->point() - point();
}
// normalize tangent vectors
qreal l1 = sqrt(t1.x() * t1.x() + t1.y() * t1.y());
qreal l2 = sqrt(t2.x() * t2.x() + t2.y() * t2.y());
if (qFuzzyCompare(l1 + 1, qreal(1.0)) || qFuzzyCompare(l2 + 1, qreal(1.0)))
return true;
t1 /= l1;
t2 /= l2;
qreal scalar = t1.x() * t2.x() + t1.y() * t2.y();
// tangents are parallel if t1*t2 = |t1|*|t2|
return qFuzzyCompare(scalar, qreal(1.0));
}
KoPathPoint::PointProperties KoPathPoint::properties() const
{
return d->properties;
}
QPointF KoPathPoint::point() const
{
return d->point;
}
QPointF KoPathPoint::controlPoint1() const
{
return d->controlPoint1;
}
QPointF KoPathPoint::controlPoint2() const
{
return d->controlPoint2;
}
KoPathShape * KoPathPoint::parent() const
{
return d->shape;
}
diff --git a/libs/flake/KoPathPoint.h b/libs/flake/KoPathPoint.h
index 3c28011e83..778310315d 100644
--- a/libs/flake/KoPathPoint.h
+++ b/libs/flake/KoPathPoint.h
@@ -1,282 +1,284 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
Copyright (C) 2006-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 KOPATHPOINT_H
#define KOPATHPOINT_H
#include "kritaflake_export.h"
#include <QFlags>
class KoPathShape;
class QPointF;
class QTransform;
class QRectF;
class QPainter;
+class KisHandlePainterHelper;
/**
* @brief A KoPathPoint represents a point in a path.
*
* A KoPathPoint stores a point in a path. Additional to this point
* 2 control points are stored.
* controlPoint1 is used to describe the second point of a cubic
* bezier ending at the point. controlPoint2 is used to describe the
* first point of a cubic bezier curve starting at the point.
*/
class KRITAFLAKE_EXPORT KoPathPoint
{
public:
/// property enum
enum PointProperty {
Normal = 0, ///< it has no control points
StartSubpath = 1, ///< it starts a new subpath by a moveTo command
StopSubpath = 2, ///< it stops a subpath (last point of subpath)
CloseSubpath = 8, ///< it closes a subpath (only applicable on StartSubpath and StopSubpath)
IsSmooth = 16, ///< it is smooth, both control points on a line through the point
IsSymmetric = 32 ///< it is symmetric, like smooth but control points have same distance to point
};
Q_DECLARE_FLAGS(PointProperties, PointProperty)
/// the type for identifying part of a KoPathPoint
enum PointType {
Node = 1, ///< the node point
ControlPoint1 = 2, ///< the first control point
ControlPoint2 = 4, ///< the second control point
All = 7
};
Q_DECLARE_FLAGS(PointTypes, PointType)
/// Default constructor
KoPathPoint();
/**
* @brief Constructor
*
* @param path is a pointer to the path shape this point is used in
* @param point the position relative to the shape origin
* @param properties describing the point
*/
KoPathPoint(KoPathShape *path, const QPointF &point, PointProperties properties = Normal);
/**
* @brief Copy Constructor
*/
KoPathPoint(const KoPathPoint &pathPoint);
+ KoPathPoint(const KoPathPoint &pathPoint, KoPathShape *newParent);
/**
* @brief Assignment operator.
*/
KoPathPoint& operator=(const KoPathPoint &other);
/// Compare operator
bool operator == (const KoPathPoint &other) const;
/**
* @brief Destructor
*/
~KoPathPoint();
/**
* @brief return the position relative to the shape origin
*
* @return point
*/
QPointF point() const;
/**
* @brief get the control point 1
*
* This points is used for controlling a curve ending at this point
*
* @return control point 1 of this point
*/
QPointF controlPoint1() const;
/**
* @brief get the second control point
*
* This points is used for controlling a curve starting at this point
*
* @return control point 2 of this point
*/
QPointF controlPoint2() const;
/**
* @brief alter the point
*
* @param point to set
*/
void setPoint(const QPointF &point);
/**
* @brief Set the control point 1
*
* @param point to set
*/
void setControlPoint1(const QPointF &point);
/**
* @brief Set the control point 2
*
* @param point to set
*/
void setControlPoint2(const QPointF &point);
/// Removes the first control point
void removeControlPoint1();
/// Removes the second control point
void removeControlPoint2();
/**
* @brief Get the properties of a point
*
* @return properties of the point
*/
PointProperties properties() const;
/**
* @brief Set the properties of a point
* @param properties the new properties
*/
void setProperties(PointProperties properties);
/**
* @brief Sets a single property of a point.
* @param property the property to set
*/
void setProperty(PointProperty property);
/**
* @brief Removes a property from the point.
* @param property the property to remove
*/
void unsetProperty(PointProperty property);
/**
* @brief Checks if there is a controlPoint1
*
* The control point is active if a control point was set by
* calling setControlPoint1. However a start point of a subpath
* (StartSubpath) can only have an active control 1 if the
* subpath is closed (CloseSubpath on first and last point).
*
* @return true if control point is active, false otherwise
*/
bool activeControlPoint1() const;
/**
* @brief Checks if there is a controlPoint2
*
* The control point is active if a control point was set by
* calling setControlPoint2. However a end point of a subpath
* (StopSubpath) can only have an active control point 2 if there
* subpath is closed (CloseSubpath on first and last point).
*
* @return true if control point is active, false otherwise
*/
bool activeControlPoint2() const;
/**
* @brief apply matrix on the point
*
* This does a matrix multiplication on all points of the point
*
* @param matrix which will be applied to all points
*/
void map(const QTransform &matrix);
/**
* Paints the path point with the actual brush and pen
* @param painter used for painting the shape point
* @param handleRadius size of point handles in pixel
* @param types the points which should be painted
* @param active If true only the given active points are painted
* If false all given points are used.
*/
- void paint(QPainter &painter, int handleRadius, PointTypes types, bool active = true);
+ void paint(KisHandlePainterHelper &handlesHelper, PointTypes types, bool active = true);
/**
* @brief Sets the parent path shape.
* @param parent the new parent path shape
*/
void setParent(KoPathShape* parent);
/**
* @brief Get the path shape the point belongs to
* @return the path shape the point belongs to
*/
KoPathShape *parent() const;
/**
* @brief Get the bounding rect of the point.
*
* This takes into account if there are controlpoints
*
* @param active If true only the active points are used in calculation
* of the bounding rectangle. If false all points are used.
*
* @return bounding rect in document coordinates
*/
QRectF boundingRect(bool active = true) const;
/**
* @brief Reverses the path point.
*
* The control points are swapped and the point properties are adjusted.
* The position dependent properties like StartSubpath and CloseSubpath
* are not changed.
*/
void reverse();
/**
* Returns if this point is a smooth join of adjacent path segments.
*
* The smoothess is defined by the parallelness of the tangents emanating
* from the knot point, i.e. the normalized vectors from the knot to the
* first and second control point.
* The previous and next path points are used to determine the smoothness
* in case this path point has not two control points.
*
* @param previous the previous path point
* @param next the next path point
*/
bool isSmooth(KoPathPoint *previous, KoPathPoint *next) const;
protected:
friend class KoPathShapePrivate;
private:
class Private;
Private * const d;
};
// /// a KoSubpath contains a path from a moveTo until a close or a new moveTo
// typedef QList<KoPathPoint *> KoSubpath;
// typedef QList<KoSubpath *> KoSubpathList;
// /// A KoPathSegment is a pair two neighboring KoPathPoints
// typedef QPair<KoPathPoint*,KoPathPoint*> KoPathSegment;
// /// The position of a path point within a path shape
// typedef QPair<KoSubpath*, int> KoPointPosition;
Q_DECLARE_OPERATORS_FOR_FLAGS(KoPathPoint::PointProperties)
Q_DECLARE_OPERATORS_FOR_FLAGS(KoPathPoint::PointTypes)
#endif
diff --git a/libs/flake/KoPathPointData.h b/libs/flake/KoPathPointData.h
index 4175e00c15..80d6d5af01 100644
--- a/libs/flake/KoPathPointData.h
+++ b/libs/flake/KoPathPointData.h
@@ -1,57 +1,58 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOPATHPOINTDATA_H
#define KOPATHPOINTDATA_H
#include "KoPathShape.h"
+#include <boost/operators.hpp>
/**
* @brief Describe a KoPathPoint by a KoPathShape and its indices
*/
-class KoPathPointData
+class KoPathPointData : public boost::equality_comparable<KoPathPointData>
{
public:
/// contructor
KoPathPointData(KoPathShape * path, const KoPathPointIndex & pointIndex)
: pathShape(path)
, pointIndex(pointIndex) {}
/// operator used for sorting
bool operator<(const KoPathPointData & other) const {
return pathShape < other.pathShape ||
(pathShape == other.pathShape &&
(pointIndex.first < other.pointIndex.first ||
(pointIndex.first == other.pointIndex.first &&
pointIndex.second < other.pointIndex.second)));
}
bool operator==(const KoPathPointData & other) const {
return pathShape == other.pathShape &&
pointIndex.first == other.pointIndex.first &&
pointIndex.second == other.pointIndex.second;
}
/// path shape the path point belongs too
KoPathShape *pathShape;
/// position of the point in the path shape
KoPathPointIndex pointIndex;
};
#endif
diff --git a/libs/flake/KoPathShape.cpp b/libs/flake/KoPathShape.cpp
index 7f7e5e567a..e3a678294a 100644
--- a/libs/flake/KoPathShape.cpp
+++ b/libs/flake/KoPathShape.cpp
@@ -1,1740 +1,1675 @@
/* 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 "KoMarkerSharedLoadingData.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 <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());
}
static const qreal DefaultMarkerWidth = 3.0;
KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q)
: KoTosContainerPrivate(q),
fillRule(Qt::OddEvenFill),
- startMarker(KoMarkerData::MarkerStart),
- endMarker(KoMarkerData::MarkerEnd)
+ autoFillMarkers(false)
{
}
+KoPathShapePrivate::KoPathShapePrivate(const KoPathShapePrivate &rhs, KoPathShape *q)
+ : KoTosContainerPrivate(rhs, q),
+ fillRule(rhs.fillRule),
+ markersNew(rhs.markersNew),
+ autoFillMarkers(rhs.autoFillMarkers)
+{
+ Q_FOREACH (KoSubpath *subPath, rhs.subpaths) {
+ KoSubpath *clonedSubPath = new KoSubpath();
+
+ Q_FOREACH (KoPathPoint *point, *subPath) {
+ *clonedSubPath << new KoPathPoint(*point, q);
+ }
+
+ subpaths << clonedSubPath;
+ }
+}
+
QRectF KoPathShapePrivate::handleRect(const QPointF &p, qreal radius) const
{
return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius);
}
void KoPathShapePrivate::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(*(new KoPathShapePrivate(this)))
+ :KoTosContainer(new KoPathShapePrivate(this))
{
}
-KoPathShape::KoPathShape(KoPathShapePrivate &dd)
+KoPathShape::KoPathShape(KoPathShapePrivate *dd)
: KoTosContainer(dd)
{
}
+KoPathShape::KoPathShape(const KoPathShape &rhs)
+ : KoTosContainer(new KoPathShapePrivate(*rhs.d_func(), this))
+{
+}
+
KoPathShape::~KoPathShape()
{
clear();
}
+KoShape *KoPathShape::cloneShape() const
+{
+ return new KoPathShape(*this);
+}
+
void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const
{
Q_D(const KoPathShape);
- if (m_subpaths.length() <= 1) {
+ if (d->subpaths.length() <= 1) {
QTransform matrix;
matrix.scale(scaleFactor.width(), scaleFactor.height());
QString points;
- KoSubpath *subPath = m_subpaths.first();
+ 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().addAttributePt("svg:width", size().width());
context.xmlWriter().addAttributePt("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
{
Q_D(const KoPathShape);
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)
{
Q_D(KoPathShape);
// 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)
{
Q_D(KoPathShape);
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
{
Q_D(const KoPathShape);
style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero");
- KoShapeStroke *lineBorder = dynamic_cast<KoShapeStroke*>(stroke());
+ QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke());
qreal lineWidth = 0;
if (lineBorder) {
lineWidth = lineBorder->lineWidth();
}
- d->startMarker.saveStyle(style, lineWidth, context);
- d->endMarker.saveStyle(style, lineWidth, context);
+
+ Q_UNUSED(lineWidth)
return KoTosContainer::saveStyle(style, context);
}
void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context)
{
Q_D(KoPathShape);
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
}
- KoShapeStroke *lineBorder = dynamic_cast<KoShapeStroke*>(stroke());
+ QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke());
qreal lineWidth = 0;
if (lineBorder) {
lineWidth = lineBorder->lineWidth();
}
- d->startMarker.loadOdf(lineWidth, context);
- d->endMarker.loadOdf(lineWidth, context);
+ 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, m_subpaths) {
+ Q_D(KoPathShape);
+
+ Q_FOREACH (KoSubpath *subpath, d->subpaths) {
Q_FOREACH (KoPathPoint *point, *subpath)
delete point;
delete subpath;
}
- m_subpaths.clear();
+ d->subpaths.clear();
}
void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
Q_D(KoPathShape);
applyConversion(painter, converter);
QPainterPath path(outline());
path.setFillRule(d->fillRule);
if (background()) {
background()->paint(painter, converter, paintContext, path);
}
//d->paintDebug(painter);
}
#ifndef NDEBUG
void KoPathShapePrivate::paintDebug(QPainter &painter)
{
Q_Q(KoPathShape);
- KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin());
+ KoSubpathList::const_iterator pathIt(subpaths.constBegin());
int i = 0;
QPen pen(Qt::black, 0);
painter.save();
painter.setPen(pen);
- for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) {
+ 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 KoPathShapePrivate::debugPath() const
{
Q_Q(const KoPathShape);
- KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin());
- for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) {
+ 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(QPainter &painter, const KoViewConverter &converter, int handleRadius)
+void KoPathShape::paintPoints(KisHandlePainterHelper &handlesHelper)
{
- applyConversion(painter, converter);
+ Q_D(KoPathShape);
- KoSubpathList::const_iterator pathIt(m_subpaths.constBegin());
+ KoSubpathList::const_iterator pathIt(d->subpaths.constBegin());
- for (; pathIt != m_subpaths.constEnd(); ++pathIt) {
+ for (; pathIt != d->subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it)
- (*it)->paint(painter, handleRadius, KoPathPoint::Node);
+ (*it)->paint(handlesHelper, KoPathPoint::Node);
}
}
+QRectF KoPathShape::outlineRect() const
+{
+ return outline().boundingRect();
+}
+
QPainterPath KoPathShape::outline() const
{
+ Q_D(const KoPathShape);
+
QPainterPath path;
- Q_FOREACH (KoSubpath * subpath, m_subpaths) {
+ Q_FOREACH (KoSubpath * subpath, d->subpaths) {
KoPathPoint * lastPoint = subpath->first();
bool activeCP = false;
Q_FOREACH (KoPathPoint * currPoint, *subpath) {
KoPathPoint::PointProperties currProperties = currPoint->properties();
if (currPoint == subpath->first()) {
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
{
QTransform transform = absoluteTransformation(0);
// calculate the bounding rect of the transformed outline
QRectF bb;
- KoShapeStroke *lineBorder = dynamic_cast<KoShapeStroke*>(stroke());
+ const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(stroke());
QPen pen;
if (lineBorder) {
pen.setWidthF(lineBorder->lineWidth());
}
bb = transform.map(pathStroke(pen)).boundingRect();
if (stroke()) {
KoInsets inset;
stroke()->strokeInsets(this, inset);
// calculate transformed border insets
QPointF center = transform.map(QPointF());
QPointF tl = transform.map(QPointF(-inset.left,-inset.top)) - center;
QPointF br = transform.map(QPointF(inset.right,inset.bottom)) -center;
qreal left = qMin(tl.x(),br.x());
qreal right = qMax(tl.x(),br.x());
qreal top = qMin(tl.y(),br.y());
qreal bottom = qMax(tl.y(),br.y());
bb.adjust(left, top, right, bottom);
+
+ // TODO: take care about transformations!
+ // take care about markers!
+ bb = kisGrowRect(bb, stroke()->strokeMaxMarkersInset(this));
}
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 reccursion
- return outline().boundingRect().size();
+ return outlineRect().size();
}
void KoPathShape::setSize(const QSizeF &newSize)
{
Q_D(KoPathShape);
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)
{
+ Q_D(KoPathShape);
+
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath);
KoSubpath * path = new KoSubpath;
path->push_back(point);
- m_subpaths.push_back(path);
+ d->subpaths.push_back(path);
return point;
}
KoPathPoint * KoPathShape::lineTo(const QPointF &p)
{
Q_D(KoPathShape);
- if (m_subpaths.empty()) {
+ if (d->subpaths.empty()) {
moveTo(QPointF(0, 0));
}
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath);
- KoPathPoint * lastPoint = m_subpaths.last()->last();
+ KoPathPoint * lastPoint = d->subpaths.last()->last();
d->updateLast(&lastPoint);
- m_subpaths.last()->push_back(point);
+ d->subpaths.last()->push_back(point);
return point;
}
KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p)
{
Q_D(KoPathShape);
- if (m_subpaths.empty()) {
+ if (d->subpaths.empty()) {
moveTo(QPointF(0, 0));
}
- KoPathPoint * lastPoint = m_subpaths.last()->last();
+ KoPathPoint * lastPoint = d->subpaths.last()->last();
d->updateLast(&lastPoint);
lastPoint->setControlPoint2(c1);
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath);
point->setControlPoint1(c2);
- m_subpaths.last()->push_back(point);
+ d->subpaths.last()->push_back(point);
return point;
}
KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p)
{
Q_D(KoPathShape);
- if (m_subpaths.empty())
+ if (d->subpaths.empty())
moveTo(QPointF(0, 0));
- KoPathPoint * lastPoint = m_subpaths.last()->last();
+ KoPathPoint * lastPoint = d->subpaths.last()->last();
d->updateLast(&lastPoint);
lastPoint->setControlPoint2(c);
KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath);
- m_subpaths.last()->push_back(point);
+ d->subpaths.last()->push_back(point);
return point;
}
KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle)
{
- if (m_subpaths.empty()) {
+ Q_D(KoPathShape);
+
+ if (d->subpaths.empty()) {
moveTo(QPointF(0, 0));
}
- KoPathPoint * lastPoint = m_subpaths.last()->last();
+ KoPathPoint * lastPoint = d->subpaths.last()->last();
if (lastPoint->properties() & KoPathPoint::CloseSubpath) {
- lastPoint = m_subpaths.last()->first();
+ 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)
+ if (sweepAngle == 0.0)
return pointCnt;
- if (sweepAngle > 360)
- sweepAngle = 360;
- else if (sweepAngle < -360)
- sweepAngle = - 360;
+
+ sweepAngle = qBound(-360.0, sweepAngle, 360.0);
if (rx == 0 || ry == 0) {
//TODO
}
// split angles bigger than 90° so that it gives a good aproximation 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()
{
Q_D(KoPathShape);
- if (m_subpaths.empty()) {
+ if (d->subpaths.empty()) {
return;
}
- d->closeSubpath(m_subpaths.last());
+ d->closeSubpath(d->subpaths.last());
}
void KoPathShape::closeMerge()
{
Q_D(KoPathShape);
- if (m_subpaths.empty()) {
+ if (d->subpaths.empty()) {
return;
}
- d->closeMergeSubpath(m_subpaths.last());
+ d->closeMergeSubpath(d->subpaths.last());
}
QPointF KoPathShape::normalize()
{
Q_D(KoPathShape);
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());
d->shapeChanged(ContentChanged);
return tl;
}
void KoPathShapePrivate::map(const QTransform &matrix)
{
Q_Q(KoPathShape);
- KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin());
- for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) {
+ KoSubpathList::const_iterator pathIt(subpaths.constBegin());
+ for (; pathIt != subpaths.constEnd(); ++pathIt) {
KoSubpath::const_iterator it((*pathIt)->constBegin());
for (; it != (*pathIt)->constEnd(); ++it) {
(*it)->map(matrix);
}
}
}
void KoPathShapePrivate::updateLast(KoPathPoint **lastPoint)
{
Q_Q(KoPathShape);
// 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 = q->m_subpaths.last()->first();
+ KoPathPoint *subpathStart = subpaths.last()->first();
// clone the first point of the subpath...
- KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart);
+ KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart, q);
// ... 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);
- q->m_subpaths.push_back(path);
+ 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
{
+ Q_D(const KoPathShape);
+
QList<KoPathPoint*> result;
- KoSubpathList::const_iterator pathIt(m_subpaths.constBegin());
- for (; pathIt != m_subpaths.constEnd(); ++pathIt) {
+ 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
{
+ Q_D(const KoPathShape);
+
QList<KoPathSegment> segments;
- int subpathCount = m_subpaths.count();
+ int subpathCount = d->subpaths.count();
for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
- KoSubpath * subpath = m_subpaths[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 < m_subpaths.size(); ++subpathIndex) {
- KoSubpath * subpath = m_subpaths.at(subpathIndex);
+ Q_D(const KoPathShape);
+
+ 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
{
Q_D(const KoPathShape);
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
{
Q_D(const KoPathShape);
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
{
+ Q_D(const KoPathShape);
+
int i = 0;
- KoSubpathList::const_iterator pathIt(m_subpaths.constBegin());
- for (; pathIt != m_subpaths.constEnd(); ++pathIt) {
+ KoSubpathList::const_iterator pathIt(d->subpaths.constBegin());
+ for (; pathIt != d->subpaths.constEnd(); ++pathIt) {
i += (*pathIt)->size();
}
return i;
}
int KoPathShape::subpathCount() const
{
- return m_subpaths.count();
+ Q_D(const KoPathShape);
+
+ return d->subpaths.count();
}
int KoPathShape::subpathPointCount(int subpathIndex) const
{
Q_D(const KoPathShape);
KoSubpath *subpath = d->subPath(subpathIndex);
if (subpath == 0)
return -1;
return subpath->size();
}
bool KoPathShape::isClosedSubpath(int subpathIndex) const
{
Q_D(const KoPathShape);
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)
{
Q_D(KoPathShape);
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);
return true;
}
KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex)
{
Q_D(KoPathShape);
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);
}
}
return point;
}
bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex)
{
Q_D(KoPathShape);
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
- m_subpaths.insert(pointIndex.first + 1, newSubpath);
+ d->subpaths.insert(pointIndex.first + 1, newSubpath);
return true;
}
bool KoPathShape::join(int subpathIndex)
{
Q_D(KoPathShape);
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
- m_subpaths.removeAt(subpathIndex + 1);
+ d->subpaths.removeAt(subpathIndex + 1);
// delete it as it is no longer possible to use it
delete nextSubpath;
return true;
}
bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex)
{
Q_D(KoPathShape);
KoSubpath *subpath = d->subPath(oldSubpathIndex);
- if (subpath == 0 || newSubpathIndex >= m_subpaths.size())
+ if (subpath == 0 || newSubpathIndex >= d->subpaths.size())
return false;
if (oldSubpathIndex == newSubpathIndex)
return true;
- m_subpaths.removeAt(oldSubpathIndex);
- m_subpaths.insert(newSubpathIndex, subpath);
+ d->subpaths.removeAt(oldSubpathIndex);
+ d->subpaths.insert(newSubpathIndex, subpath);
return true;
}
KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex)
{
Q_D(KoPathShape);
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);
return pathPointIndex(oldStartPoint);
}
KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex)
{
Q_D(KoPathShape);
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);
d->closeSubpath(subpath);
return pathPointIndex(oldStartPoint);
}
bool KoPathShape::reverseSubpath(int subpathIndex)
{
Q_D(KoPathShape);
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);
return true;
}
KoSubpath * KoPathShape::removeSubpath(int subpathIndex)
{
Q_D(KoPathShape);
KoSubpath *subpath = d->subPath(subpathIndex);
- if (subpath != 0)
- m_subpaths.removeAt(subpathIndex);
+ if (subpath != 0) {
+ Q_FOREACH (KoPathPoint* point, *subpath) {
+ point->setParent(this);
+ }
+
+ d->subpaths.removeAt(subpathIndex);
+ }
return subpath;
}
bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex)
{
- if (subpathIndex < 0 || subpathIndex > m_subpaths.size())
+ Q_D(KoPathShape);
+
+ if (subpathIndex < 0 || subpathIndex > d->subpaths.size())
return false;
- m_subpaths.insert(subpathIndex, subpath);
+ Q_FOREACH (KoPathPoint* point, *subpath) {
+ point->setParent(this);
+ }
+
+ d->subpaths.insert(subpathIndex, subpath);
return true;
}
-
-bool KoPathShape::combine(KoPathShape *path)
+int KoPathShape::combine(KoPathShape *path)
{
- if (! path)
- return false;
+ Q_D(KoPathShape);
+ int insertSegmentPosition = -1;
+ if (!path) return insertSegmentPosition;
QTransform pathMatrix = path->absoluteTransformation(0);
QTransform myMatrix = absoluteTransformation(0).inverted();
- Q_FOREACH (KoSubpath* subpath, path->m_subpaths) {
+ Q_FOREACH (KoSubpath* subpath, path->d_func()->subpaths) {
KoSubpath *newSubpath = new KoSubpath();
Q_FOREACH (KoPathPoint* point, *subpath) {
- KoPathPoint *newPoint = new KoPathPoint(*point);
+ KoPathPoint *newPoint = new KoPathPoint(*point, this);
newPoint->map(pathMatrix);
newPoint->map(myMatrix);
- newPoint->setParent(this);
newSubpath->append(newPoint);
}
- m_subpaths.append(newSubpath);
+ d->subpaths.append(newSubpath);
+
+ if (insertSegmentPosition < 0) {
+ insertSegmentPosition = d->subpaths.size() - 1;
+ }
}
normalize();
- return true;
+ return insertSegmentPosition;
}
bool KoPathShape::separate(QList<KoPathShape*> & separatedPaths)
{
- if (! m_subpaths.size())
+ Q_D(KoPathShape);
+
+ if (! d->subpaths.size())
return false;
QTransform myMatrix = absoluteTransformation(0);
- Q_FOREACH (KoSubpath* subpath, m_subpaths) {
+ Q_FOREACH (KoSubpath* subpath, d->subpaths) {
KoPathShape *shape = new KoPathShape();
if (! shape) continue;
shape->setStroke(stroke());
shape->setShapeId(shapeId());
KoSubpath *newSubpath = new KoSubpath();
Q_FOREACH (KoPathPoint* point, *subpath) {
- KoPathPoint *newPoint = new KoPathPoint(*point);
+ KoPathPoint *newPoint = new KoPathPoint(*point, shape);
newPoint->map(myMatrix);
newSubpath->append(newPoint);
}
- shape->m_subpaths.append(newSubpath);
+ shape->d_func()->subpaths.append(newSubpath);
shape->normalize();
separatedPaths.append(shape);
}
return true;
}
void KoPathShapePrivate::closeSubpath(KoSubpath *subpath)
{
if (! subpath)
return;
subpath->last()->setProperty(KoPathPoint::CloseSubpath);
subpath->first()->setProperty(KoPathPoint::CloseSubpath);
}
void KoPathShapePrivate::closeMergeSubpath(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);
} else {
closeSubpath(subpath);
}
}
KoSubpath *KoPathShapePrivate::subPath(int subpathIndex) const
{
Q_Q(const KoPathShape);
- if (subpathIndex < 0 || subpathIndex >= q->m_subpaths.size())
+ if (subpathIndex < 0 || subpathIndex >= subpaths.size())
return 0;
- return q->m_subpaths.at(subpathIndex);
+ return subpaths.at(subpathIndex);
}
QString KoPathShape::pathShapeId() const
{
return KoPathShapeId;
}
QString KoPathShape::toString(const QTransform &matrix) const
{
- QString d;
+ Q_D(const KoPathShape);
+
+ QString pathString;
// iterate over all subpaths
- KoSubpathList::const_iterator pathIt(m_subpaths.constBegin());
- for (; pathIt != m_subpaths.constEnd(); ++pathIt) {
+ 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);
// first point of subpath ?
if (currPoint == firstPoint) {
// are we starting a subpath ?
if (currPoint->properties() & KoPathPoint::StartSubpath) {
const QPointF p = matrix.map(currPoint->point());
- d += QString("M%1 %2").arg(p.x()).arg(p.y());
+ 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();
const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2());
const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1());
const QPointF p = matrix.map(cubicSeg.second()->point());
- d += QString("C%1 %2 %3 %4 %5 %6")
+ 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());
- d += QString("L%1 %2").arg(p.x()).arg(p.y());
+ 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();
const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2());
const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1());
const QPointF p = matrix.map(cubicSeg.second()->point());
- d += QString("C%1 %2 %3 %4 %5 %6")
+ 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());
}
- d += QString("Z");
+ pathString += QString("Z");
}
activeControlPoint2 = currPoint->activeControlPoint2();
lastPoint = currPoint;
}
}
- return d;
+ 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 KoPathShapePrivate::nodeTypes() const
{
Q_Q(const KoPathShape);
QString types;
- KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin());
- for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) {
+ 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 KoPathShapePrivate::loadNodeTypes(const KoXmlElement &element)
{
Q_Q(KoPathShape);
if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) {
QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes");
QString::const_iterator nIt(nodeTypes.constBegin());
- KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin());
- for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) {
+ 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
{
Q_D(const KoPathShape);
return d->fillRule;
}
void KoPathShape::setFillRule(Qt::FillRule fillRule)
{
Q_D(KoPathShape);
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->normalize();
+ //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);
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());
return outlinePath.contains(point);
}
-void KoPathShape::setMarker(const KoMarkerData &markerData)
+void KoPathShape::setMarker(KoMarker *marker, KoFlake::MarkerPosition pos)
{
Q_D(KoPathShape);
- if (markerData.position() == KoMarkerData::MarkerStart) {
- d->startMarker = markerData;
- }
- else {
- d->endMarker = markerData;
+ if (!marker && d->markersNew.contains(pos)) {
+ d->markersNew.remove(pos);
+ } else {
+ d->markersNew[pos] = marker;
}
}
-void KoPathShape::setMarker(KoMarker *marker, KoMarkerData::MarkerPosition position)
+KoMarker *KoPathShape::marker(KoFlake::MarkerPosition pos) const
{
- Q_D(KoPathShape);
-
- if (position == KoMarkerData::MarkerStart) {
- if (!d->startMarker.marker()) {
- d->startMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0));
- }
- d->startMarker.setMarker(marker);
- }
- else {
- if (!d->endMarker.marker()) {
- d->endMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0));
- }
- d->endMarker.setMarker(marker);
- }
+ Q_D(const KoPathShape);
+ return d->markersNew[pos].data();
}
-KoMarker *KoPathShape::marker(KoMarkerData::MarkerPosition position) const
+bool KoPathShape::hasMarkers() const
{
Q_D(const KoPathShape);
-
- if (position == KoMarkerData::MarkerStart) {
- return d->startMarker.marker();
- }
- else {
- return d->endMarker.marker();
- }
+ return !d->markersNew.isEmpty();
}
-KoMarkerData KoPathShape::markerData(KoMarkerData::MarkerPosition position) const
+bool KoPathShape::autoFillMarkers() const
{
Q_D(const KoPathShape);
+ return d->autoFillMarkers;
+}
- if (position == KoMarkerData::MarkerStart) {
- return d->startMarker;
- }
- else {
- return d->endMarker;
- }
+void KoPathShape::setAutoFillMarkers(bool value)
+{
+ Q_D(KoPathShape);
+ d->autoFillMarkers = value;
}
QPainterPath KoPathShape::pathStroke(const QPen &pen) const
{
- if (m_subpaths.isEmpty()) {
+ Q_D(const KoPathShape);
+
+ if (d->subpaths.isEmpty()) {
return QPainterPath();
}
QPainterPath pathOutline;
QPainterPathStroker stroker;
stroker.setWidth(0);
stroker.setJoinStyle(Qt::MiterJoin);
QPair<KoPathSegment, KoPathSegment> firstSegments;
QPair<KoPathSegment, KoPathSegment> lastSegments;
KoPathPoint *firstPoint = 0;
KoPathPoint *lastPoint = 0;
KoPathPoint *secondPoint = 0;
KoPathPoint *preLastPoint = 0;
- KoSubpath *firstSubpath = m_subpaths.first();
- bool twoPointPath = subpathPointCount(0) == 2;
- bool closedPath = isClosedSubpath(0);
-
- /*
- * The geometry is horizontally centered. It is vertically positioned relative to an offset value which
- * is specified by a draw:marker-start-center attribute for markers referenced by a
- * draw:marker-start attribute, and by the draw:marker-end-center attribute for markers
- * referenced by a draw:marker-end attribute. The attribute value true defines an offset of 0.5
- * and the attribute value false defines an offset of 0.3, which is also the default value. The offset
- * specifies the marker's vertical position in a range from 0.0 to 1.0, where the value 0.0 means the
- * geometry's bottom bound is aligned to the X axis of the local coordinate system of the marker
- * geometry, and where the value 1.0 means the top bound to be aligned to the X axis of the local
- * coordinate system of the marker geometry.
- *
- * The shorten factor to use results of the 0.3 which means we need to start at 0.7 * height of the marker
- */
- static const qreal shortenFactor = 0.7;
-
- KoMarkerData mdStart = markerData(KoMarkerData::MarkerStart);
- KoMarkerData mdEnd = markerData(KoMarkerData::MarkerEnd);
- if (mdStart.marker() && !closedPath) {
- QPainterPath markerPath = mdStart.marker()->path(mdStart.width(pen.widthF()));
-
- KoPathSegment firstSegment = segmentByIndex(KoPathPointIndex(0, 0));
- if (firstSegment.isValid()) {
- QRectF pathBoundingRect = markerPath.boundingRect();
- qreal shortenLength = pathBoundingRect.height() * shortenFactor;
- debugFlake << "length" << firstSegment.length() << shortenLength;
- qreal t = firstSegment.paramAtLength(shortenLength);
- firstSegments = firstSegment.splitAt(t);
- // transform the marker so that it goes from the first point of the first segment to the second point of the first segment
- QPointF startPoint = firstSegments.first.first()->point();
- QPointF newStartPoint = firstSegments.first.second()->point();
- QLineF vector(newStartPoint, startPoint);
- qreal angle = -vector.angle() + 90;
- QTransform transform;
- transform.translate(startPoint.x(), startPoint.y())
- .rotate(angle)
- .translate(-pathBoundingRect.width() / 2.0, 0);
-
- markerPath = transform.map(markerPath);
- QPainterPath startOutline = stroker.createStroke(markerPath);
- startOutline = startOutline.united(markerPath);
- pathOutline.addPath(startOutline);
- firstPoint = firstSubpath->first();
- if (firstPoint->properties() & KoPathPoint::StartSubpath) {
- firstSegments.second.first()->setProperty(KoPathPoint::StartSubpath);
- }
- debugFlake << "start marker" << angle << startPoint << newStartPoint << firstPoint->point();
-
- if (!twoPointPath) {
- if (firstSegment.second()->activeControlPoint2()) {
- firstSegments.second.second()->setControlPoint2(firstSegment.second()->controlPoint2());
- }
- secondPoint = (*firstSubpath)[1];
- }
- else if (!mdEnd.marker()) {
- // in case it is two point path with no end marker we need to modify the last point via the secondPoint
- secondPoint = (*firstSubpath)[1];
- }
- }
- }
- if (mdEnd.marker() && !closedPath) {
- QPainterPath markerPath = mdEnd.marker()->path(mdEnd.width(pen.widthF()));
-
- KoPathSegment lastSegment;
-
- /*
- * if the path consits only of 2 point and it it has an marker on both ends
- * use the firstSegments.second as that is the path that needs to be shortened
- */
- if (twoPointPath && firstPoint) {
- lastSegment = firstSegments.second;
- }
- else {
- lastSegment = segmentByIndex(KoPathPointIndex(0, firstSubpath->count() - 2));
- }
-
- if (lastSegment.isValid()) {
- QRectF pathBoundingRect = markerPath.boundingRect();
- qreal shortenLength = lastSegment.length() - pathBoundingRect.height() * shortenFactor;
- qreal t = lastSegment.paramAtLength(shortenLength);
- lastSegments = lastSegment.splitAt(t);
- // transform the marker so that it goes from the last point of the first segment to the previous point of the last segment
- QPointF startPoint = lastSegments.second.second()->point();
- QPointF newStartPoint = lastSegments.second.first()->point();
- QLineF vector(newStartPoint, startPoint);
- qreal angle = -vector.angle() + 90;
- QTransform transform;
- transform.translate(startPoint.x(), startPoint.y()).rotate(angle).translate(-pathBoundingRect.width() / 2.0, 0);
-
- markerPath = transform.map(markerPath);
- QPainterPath endOutline = stroker.createStroke(markerPath);
- endOutline = endOutline.united(markerPath);
- pathOutline.addPath(endOutline);
- lastPoint = firstSubpath->last();
- debugFlake << "end marker" << angle << startPoint << newStartPoint << lastPoint->point();
- if (twoPointPath) {
- if (firstSegments.second.isValid()) {
- if (lastSegments.first.first()->activeControlPoint2()) {
- firstSegments.second.first()->setControlPoint2(lastSegments.first.first()->controlPoint2());
- }
- }
- else {
- // if there is no start marker we need the first point needs to be changed via the preLastPoint
- // the flag needs to be set so the moveTo is done
- lastSegments.first.first()->setProperty(KoPathPoint::StartSubpath);
- preLastPoint = (*firstSubpath)[firstSubpath->count()-2];
- }
- }
- else {
- if (lastSegment.first()->activeControlPoint1()) {
- lastSegments.first.first()->setControlPoint1(lastSegment.first()->controlPoint1());
- }
- preLastPoint = (*firstSubpath)[firstSubpath->count()-2];
- }
- }
- }
-
+ KoSubpath *firstSubpath = d->subpaths.first();
stroker.setWidth(pen.widthF());
stroker.setJoinStyle(pen.joinStyle());
stroker.setMiterLimit(pen.miterLimit());
stroker.setCapStyle(pen.capStyle());
stroker.setDashOffset(pen.dashOffset());
stroker.setDashPattern(pen.dashPattern());
// shortent the path to make it look nice
// replace the point temporarily in case there is an arrow
// BE AWARE: this changes the content of the path so that outline give the correct values.
if (firstPoint) {
firstSubpath->first() = firstSegments.second.first();
if (secondPoint) {
(*firstSubpath)[1] = firstSegments.second.second();
}
}
if (lastPoint) {
if (preLastPoint) {
(*firstSubpath)[firstSubpath->count() - 2] = lastSegments.first.first();
}
firstSubpath->last() = lastSegments.first.second();
}
QPainterPath path = stroker.createStroke(outline());
if (firstPoint) {
firstSubpath->first() = firstPoint;
if (secondPoint) {
(*firstSubpath)[1] = secondPoint;
}
}
if (lastPoint) {
if (preLastPoint) {
(*firstSubpath)[firstSubpath->count() - 2] = preLastPoint;
}
firstSubpath->last() = lastPoint;
}
pathOutline.addPath(path);
pathOutline.setFillRule(Qt::WindingFill);
return pathOutline;
}
diff --git a/libs/flake/KoPathShape.h b/libs/flake/KoPathShape.h
index c36f9f7a9c..089f535b74 100644
--- a/libs/flake/KoPathShape.h
+++ b/libs/flake/KoPathShape.h
@@ -1,505 +1,510 @@
/* 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"
-#include "KoMarkerData.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
*/
virtual ~KoPathShape();
+ KoShape *cloneShape() const;
+
/// reimplemented
virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
- virtual void paintPoints(QPainter &painter, const KoViewConverter &converter, int handleRadius);
+ virtual void paintPoints(KisHandlePainterHelper &handlesHelper);
+
+ /// reimplemented
+ QRectF outlineRect() const override;
/// reimplemented
- virtual QPainterPath outline() const;
+ QPainterPath outline() const override;
/// reimplemented
- virtual QRectF boundingRect() const;
+ QRectF boundingRect() const override;
/// reimplemented
- virtual QSizeF size() const;
+ 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()
*/
virtual void setSize(const QSizeF &size);
/// reimplemented
virtual bool hitTest(const QPointF &position) const;
// reimplemented
virtual void saveOdf(KoShapeSavingContext &context) const;
// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
// 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 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 semgent 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 succes, 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 true if combining was successful, else false
+ * @return index of the first segment inserted or -1 on failure
*/
- bool combine(KoPathShape *path);
+ 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 represenatation 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);
- /// Marker setter
- void setMarker(const KoMarkerData &markerData);
+ void setMarker(KoMarker *marker, KoFlake::MarkerPosition pos);
+ KoMarker* marker(KoFlake::MarkerPosition pos) const;
+ bool hasMarkers() const;
- /// Marker setter
- void setMarker(KoMarker *marker, KoMarkerData::MarkerPosition position);
+ bool autoFillMarkers() const;
+ void setAutoFillMarkers(bool value);
- /// returns the list of all the markers of the path
- KoMarker *marker(KoMarkerData::MarkerPosition position) const;
-
- KoMarkerData markerData(KoMarkerData::MarkerPosition position) const;
+private:
+ /// constructor: to be used in cloneShape(), not in descendants!
+ /// \internal
+ KoPathShape(const KoPathShape &rhs);
protected:
- /// constructor \internal
- KoPathShape(KoPathShapePrivate &);
+ /// constructor:to be used in descendant shapes
+ /// \internal
+ KoPathShape(KoPathShapePrivate *);
/// reimplemented
virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const;
/// reimplemented
virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context);
/**
* @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 cuve 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;
- KoSubpathList m_subpaths;
-
private:
Q_DECLARE_PRIVATE(KoPathShape)
};
Q_DECLARE_METATYPE(KoPathShape*)
#endif /* KOPATHSHAPE_H */
diff --git a/libs/flake/KoPathShapeFactory.cpp b/libs/flake/KoPathShapeFactory.cpp
index f93c83ccbb..2219151bea 100644
--- a/libs/flake/KoPathShapeFactory.cpp
+++ b/libs/flake/KoPathShapeFactory.cpp
@@ -1,89 +1,91 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* 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 "KoPathShapeFactory.h"
#include "KoPathShape.h"
#include "KoShapeStroke.h"
#include "KoImageCollection.h"
#include "KoMarkerCollection.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeLoadingContext.h"
#include <KoIcon.h>
#include "KoInsets.h"
#include <klocalizedstring.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
+#include "kis_pointer_utils.h"
+
KoPathShapeFactory::KoPathShapeFactory(const QStringList&)
: KoShapeFactoryBase(KoPathShapeId, i18n("Simple path shape"))
{
setToolTip(i18n("A simple path shape"));
setIconName(koIconNameCStr("pathshape"));
QStringList elementNames;
elementNames << "path" << "line" << "polyline" << "polygon";
setXmlElementNames(KoXmlNS::draw, elementNames);
setLoadingPriority(0);
}
KoShape *KoPathShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
KoPathShape* path = new KoPathShape();
path->moveTo(QPointF(0, 50));
path->curveTo(QPointF(0, 120), QPointF(50, 120), QPointF(50, 50));
path->curveTo(QPointF(50, -20), QPointF(100, -20), QPointF(100, 50));
path->normalize();
- path->setStroke(new KoShapeStroke(1.0));
+ path->setStroke(toQShared(new KoShapeStroke(1.0)));
return path;
}
bool KoPathShapeFactory::supports(const KoXmlElement & e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
if (e.namespaceURI() == KoXmlNS::draw) {
if (e.localName() == "path")
return true;
if (e.localName() == "line")
return true;
if (e.localName() == "polyline")
return true;
if (e.localName() == "polygon")
return true;
}
return false;
}
void KoPathShapeFactory::newDocumentResourceManager(KoDocumentResourceManager *manager) const
{
// as we need an image collection for the pattern background
// we want to make sure that there is always an image collection
// added to the data center map, in case the picture shape plugin
// is not loaded
if (manager->imageCollection() == 0) {
KoImageCollection *imgCol = new KoImageCollection(manager);
manager->setImageCollection(imgCol);
}
// we also need a MarkerCollection so add if it is not there yet
if (!manager->hasResource(KoDocumentResourceManager::MarkerCollection)) {
KoMarkerCollection *markerCollection = new KoMarkerCollection(manager);
manager->setResource(KoDocumentResourceManager::MarkerCollection, qVariantFromValue(markerCollection));
}
}
diff --git a/libs/flake/KoPathShape_p.h b/libs/flake/KoPathShape_p.h
index 875d850606..f104f0a73a 100644
--- a/libs/flake/KoPathShape_p.h
+++ b/libs/flake/KoPathShape_p.h
@@ -1,96 +1,100 @@
/* 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 "KoTosContainer_p.h"
-#include "KoMarkerData.h"
+#include "KoMarker.h"
class KoPathShapePrivate : public KoTosContainerPrivate
{
public:
explicit KoPathShapePrivate(KoPathShape *q);
+ explicit KoPathShapePrivate(const KoPathShapePrivate &rhs, KoPathShape *q);
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);
void updateLast(KoPathPoint **lastPoint);
/// closes specified subpath
void closeSubpath(KoSubpath *subpath);
/// close-merges specified subpath
void closeMergeSubpath(KoSubpath *subpath);
/**
* @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 symetric
*
* 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 elemeent
* E.g. you have a closed path with 2 points in it. The first one (start/end of path)
* is symetric 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 pojnt 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;
Q_DECLARE_PUBLIC(KoPathShape)
- KoMarkerData startMarker;
- KoMarkerData endMarker;
+ KoSubpathList subpaths;
+
+ QMap<KoFlake::MarkerPosition, QExplicitlySharedDataPointer<KoMarker>> markersNew;
+ bool autoFillMarkers;
};
#endif
diff --git a/libs/flake/KoPatternBackground.cpp b/libs/flake/KoPatternBackground.cpp
index 48d50b1327..d98ecf8632 100644
--- a/libs/flake/KoPatternBackground.cpp
+++ b/libs/flake/KoPatternBackground.cpp
@@ -1,494 +1,500 @@
/* 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 "KoShapeBackground_p.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>
class KoPatternBackgroundPrivate : public KoShapeBackgroundPrivate
{
public:
KoPatternBackgroundPrivate()
: repeat(KoPatternBackground::Tiled)
, refPoint(KoPatternBackground::TopLeft)
, imageCollection(0)
, imageData(0)
{
}
~KoPatternBackgroundPrivate() override {
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(*(new KoPatternBackgroundPrivate()))
{
Q_D(KoPatternBackground);
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)
{
Q_D(KoPatternBackground);
d->matrix = matrix;
}
QTransform KoPatternBackground::transform() const
{
Q_D(const KoPatternBackground);
return d->matrix;
}
void KoPatternBackground::setPattern(const QImage &pattern)
{
Q_D(KoPatternBackground);
delete d->imageData;
if (d->imageCollection) {
d->imageData = d->imageCollection->createImageData(pattern);
}
}
void KoPatternBackground::setPattern(KoImageData *imageData)
{
Q_D(KoPatternBackground);
delete d->imageData;
d->imageData = imageData;
}
QImage KoPatternBackground::pattern() const
{
Q_D(const KoPatternBackground);
if (d->imageData)
return d->imageData->image();
return QImage();
}
void KoPatternBackground::setRepeat(PatternRepeat repeat)
{
Q_D(KoPatternBackground);
d->repeat = repeat;
}
KoPatternBackground::PatternRepeat KoPatternBackground::repeat() const
{
Q_D(const KoPatternBackground);
return d->repeat;
}
KoPatternBackground::ReferencePoint KoPatternBackground::referencePoint() const
{
Q_D(const KoPatternBackground);
return d->refPoint;
}
void KoPatternBackground::setReferencePoint(ReferencePoint referencePoint)
{
Q_D(KoPatternBackground);
d->refPoint = referencePoint;
}
QPointF KoPatternBackground::referencePointOffset() const
{
Q_D(const KoPatternBackground);
return d->refPointOffsetPercent;
}
void KoPatternBackground::setReferencePointOffset(const QPointF &offset)
{
Q_D(KoPatternBackground);
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
{
Q_D(const KoPatternBackground);
return d->tileRepeatOffsetPercent;
}
void KoPatternBackground::setTileRepeatOffset(const QPointF &offset)
{
Q_D(KoPatternBackground);
d->tileRepeatOffsetPercent = offset;
}
QSizeF KoPatternBackground::patternDisplaySize() const
{
Q_D(const KoPatternBackground);
return d->targetSize();
}
void KoPatternBackground::setPatternDisplaySize(const QSizeF &size)
{
Q_D(KoPatternBackground);
d->targetImageSizePercent = QSizeF();
d->targetImageSize = size;
}
QSizeF KoPatternBackground::patternOriginalSize() const
{
Q_D(const KoPatternBackground);
return d->imageData->imageSize();
}
void KoPatternBackground::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &/*context*/, const QPainterPath &fillPath) const
{
Q_D(const KoPatternBackground);
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());
painter.drawPixmap(targetRect.topLeft(), d->imageData->pixmap(targetRect.size().toSize()));
}
painter.restore();
}
void KoPatternBackground::fillStyle(KoGenStyle &style, KoShapeSavingContext &context)
{
Q_D(KoPatternBackground);
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 &)
{
Q_D(KoPatternBackground);
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)
{
Q_D(KoPatternBackground);
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 abca390459..9a7bf6f765 100644
--- a/libs/flake/KoPatternBackground.h
+++ b/libs/flake/KoPatternBackground.h
@@ -1,126 +1,128 @@
/* 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"
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);
virtual ~KoPatternBackground();
+ 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
virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath &fillPath) const;
/// reimplemented from KoShapeBackground
virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context);
/// reimplemented from KoShapeBackground
virtual bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize);
/// Returns the bounding rect of the pattern image based on the given fill size
QRectF patternRectFromFillSize(const QSizeF &size);
private:
Q_DECLARE_PRIVATE(KoPatternBackground)
Q_DISABLE_COPY(KoPatternBackground)
};
#endif // KOPATTERNBACKGROUND_H
diff --git a/libs/flake/KoRTree.h b/libs/flake/KoRTree.h
index bcc8fa4ba5..8524e26a47 100644
--- a/libs/flake/KoRTree.h
+++ b/libs/flake/KoRTree.h
@@ -1,1090 +1,1165 @@
/* 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 Antomn Guttman
*
* It only supports 2 dimensional bounding boxes which are repesented by a QRectF.
* For node splitting the Quadratic-Cost Algorithm is used as descibed 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);
virtual ~NonLeafNode();
virtual void insert(const QRectF& bb, Node * data);
virtual void remove(int index);
virtual void move(Node * node, int index);
virtual LeafNode * chooseLeaf(const QRectF& bb);
virtual NonLeafNode * chooseNode(const QRectF& bb, int level);
virtual void intersects(const QRectF& rect, QMap<int, T> & result) const;
virtual void contains(const QPointF & point, QMap<int, T> & result) const;
+ virtual void contained(const QRectF & point, QMap<int, T> & result) const;
virtual void keys(QList<QRectF> & result) const;
virtual void values(QMap<int, T> & result) const;
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);
virtual ~LeafNode();
virtual void insert(const QRectF& bb, const T& data, int id);
virtual void remove(int index);
virtual void remove(const T& data);
virtual void move(Node * node, int index);
virtual LeafNode * chooseLeaf(const QRectF& bb);
virtual NonLeafNode * chooseNode(const QRectF& bb, int level);
virtual void intersects(const QRectF& rect, QMap<int, T> & result) const;
virtual void contains(const QPointF & point, QMap<int, T> & result) const;
+ virtual void contained(const QRectF & point, QMap<int, T> & result) const;
virtual void keys(QList<QRectF> & result) const;
virtual void values(QMap<int, T> & result) const;
virtual const T& getData(int index) const;
virtual int getDataId(int index) const;
virtual bool isLeaf() const {
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");
//qDebug() << "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()) {
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);
//qDebug() << " 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)
{
//qDebug() << "KoRTree remove";
LeafNode * leaf = m_leafMap[data];
- if (leaf == 0) {
- qWarning() << "KoRTree<T>::remove( const T&data) data not found";
- return;
- }
+
+ // Trying to remove unexistent 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)
{
//qDebug() << "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());
}
//qDebug() << " n1" << n1 << n1->nodeId();
//qDebug() << " 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);
}
//qDebug() << " 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();
//qDebug() << " 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)
{
//qDebug() << "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);
//qDebug() << " diff" << diff << i << d1 << d2;
if (diff > max) {
max = diff;
select = i;
//qDebug() << " i =" << i;
if (qAbs(d1) > qAbs(d2)) {
group = 1;
} else {
group = 0;
}
//qDebug() << " group =" << group;
}
}
}
marker[select] = true;
return qMakePair(group, select);
}
template <typename T>
void KoRTree<T>::adjustTree(Node *node1, Node *node2)
{
//qDebug() << "KoRTree::adjustTree";
if (node1->isRoot()) {
//qDebug() << " 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;
//qDebug() << "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();
//qDebug() << " bb1 =" << node1->boundingBox() << node1->place() << pbbold << "->" << parent->boundingBox() << parent->nodeId();
if (!node2) {
//qDebug() << " update";
adjustTree(parent, 0);
} else {
if (parent->childCount() < m_capacity) {
//qDebug() << " no split needed";
parent->insert(node2->boundingBox(), node2);
adjustTree(parent, 0);
} else {
//qDebug() << " 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)
{
//qDebug() << "KoRTree::condenseTree begin reinsert.size()" << reinsert.size();
if (!node->isRoot()) {
Node * parent = node->parent();
//qDebug() << " !node->isRoot us" << node->childCount();
if (node->childCount() < m_minimum) {
//qDebug() << " 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 guarenteed
+ * to be readded in remove().
+ */
+
} else {
//qDebug() << " update BB parent is root" << parent->isRoot();
parent->setChildBoundingBox(node->place(), node->boundingBox());
parent->updateBoundingBox();
}
condenseTree(parent, reinsert);
} else {
//qDebug() << " node->isRoot us" << node->childCount();
if (node->childCount() == 1 && !node->isLeaf()) {
//qDebug() << " 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);
//qDebug() << " new root" << m_root;
} else {
qFatal("KoRTree::condenseTree cast to NonLeafNode failed");
}
}
}
//qDebug() << "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)
{
//qDebug() << "NonLeafNode::NonLeafNode()" << this;
}
template <typename T>
KoRTree<T>::NonLeafNode::~NonLeafNode()
{
//qDebug() << "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);
//qDebug() << "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)
{
//qDebug() << "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
{
//qDebug() << "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];
//qDebug() << " min" << minIndex << minArea;
for (int i = 1; i < this->m_counter; ++i) {
if (area[i] < minArea) {
minIndex = i;
minArea = area[i];
//qDebug() << " 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)
{
//qDebug() << "LeafNode::LeafNode" << this;
}
template <typename T>
KoRTree<T>::LeafNode::~LeafNode()
{
//qDebug() << "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) {
//qDebug() << "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) {
//qDebug() << "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]));
qDebug() << 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/KoSelectedShapesProxy.cpp b/libs/flake/KoSelectedShapesProxy.cpp
new file mode 100644
index 0000000000..e5ba757ae4
--- /dev/null
+++ b/libs/flake/KoSelectedShapesProxy.cpp
@@ -0,0 +1,25 @@
+/*
+ * 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 "KoSelectedShapesProxy.h"
+
+KoSelectedShapesProxy::KoSelectedShapesProxy(QObject *parent)
+ : QObject(parent)
+{
+}
+
diff --git a/libs/flake/KoSelectedShapesProxy.h b/libs/flake/KoSelectedShapesProxy.h
new file mode 100644
index 0000000000..9b39dded81
--- /dev/null
+++ b/libs/flake/KoSelectedShapesProxy.h
@@ -0,0 +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.
+ */
+
+#ifndef KOSELECTEDSHAPESPROXY_H
+#define KOSELECTEDSHAPESPROXY_H
+
+#include <QObject>
+#include "kritaflake_export.h"
+
+class KoSelection;
+class KoShapeLayer;
+
+/**
+ * @brief The KoSelectedShapesProxy class is a special interface of KoCanvasBase to
+ * have a stable connection to shape selection signals in an environment when the
+ * active shape manager can switch (e.g. when shape layers are switched in Krita)
+ */
+
+class KRITAFLAKE_EXPORT KoSelectedShapesProxy : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KoSelectedShapesProxy(QObject *parent = 0);
+
+ /**
+ * Returns a pointer to a currently active shape selection. Don't connect to the
+ * selection, unless you really know what you are doing. Use the signals provided
+ * by KoSelectedShapesProxy itself. They are guaranteed to be valid all the time.
+ */
+ virtual KoSelection *selection() = 0;
+
+Q_SIGNALS:
+
+ // forwards a corresponding signal of KoShapeManager
+ void selectionChanged();
+
+ // forwards a corresponding signal of KoShapeManager
+ void selectionContentChanged();
+
+ // forwards a corresponding signal of KoSelection
+ void currentLayerChanged(const KoShapeLayer *layer);
+};
+
+#endif // KOSELECTEDSHAPESPROXY_H
diff --git a/libs/flake/KoSelectedShapesProxySimple.cpp b/libs/flake/KoSelectedShapesProxySimple.cpp
new file mode 100644
index 0000000000..d4b40459a9
--- /dev/null
+++ b/libs/flake/KoSelectedShapesProxySimple.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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 "KoSelectedShapesProxySimple.h"
+
+#include "kis_assert.h"
+#include <KoShapeManager.h>
+#include <KoSelection.h>
+
+KoSelectedShapesProxySimple::KoSelectedShapesProxySimple(KoShapeManager *shapeManager)
+ : m_shapeManager(shapeManager)
+{
+ KIS_ASSERT_RECOVER_RETURN(m_shapeManager);
+
+ connect(m_shapeManager.data(), SIGNAL(selectionChanged()), SIGNAL(selectionChanged()));
+ connect(m_shapeManager.data(), SIGNAL(selectionContentChanged()), SIGNAL(selectionContentChanged()));
+ connect(m_shapeManager->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)), SIGNAL(currentLayerChanged(const KoShapeLayer*)));
+}
+
+KoSelection *KoSelectedShapesProxySimple::selection()
+{
+ KIS_ASSERT_RECOVER_RETURN_VALUE(m_shapeManager, 0);
+ return m_shapeManager->selection();
+}
+
diff --git a/libs/flake/KoSelectedShapesProxySimple.h b/libs/flake/KoSelectedShapesProxySimple.h
new file mode 100644
index 0000000000..ead3971a92
--- /dev/null
+++ b/libs/flake/KoSelectedShapesProxySimple.h
@@ -0,0 +1,39 @@
+/*
+ * 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 KOSELECTEDSHAPESPROXYSIMPLE_H
+#define KOSELECTEDSHAPESPROXYSIMPLE_H
+
+#include <QPointer>
+#include <KoSelectedShapesProxy.h>
+
+class KoShapeManager;
+
+
+class KRITAFLAKE_EXPORT KoSelectedShapesProxySimple : public KoSelectedShapesProxy
+{
+ Q_OBJECT
+public:
+ KoSelectedShapesProxySimple(KoShapeManager *shapeManager);
+ KoSelection *selection();
+
+private:
+ QPointer<KoShapeManager> m_shapeManager;
+};
+
+#endif // KOSELECTEDSHAPESPROXYSIMPLE_H
diff --git a/libs/flake/KoSelection.cpp b/libs/flake/KoSelection.cpp
index c524341f7d..571acd6d41 100644
--- a/libs/flake/KoSelection.cpp
+++ b/libs/flake/KoSelection.cpp
@@ -1,344 +1,309 @@
/* 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 <QTimer>
-QRectF KoSelectionPrivate::sizeRect()
-{
- bool first = true;
- QRectF bb;
-
- QTransform invSelectionTransform = q->absoluteTransformation(0).inverted();
-
- QRectF bound;
-
- if (!selectedShapes.isEmpty()) {
- QList<KoShape*>::const_iterator it = selectedShapes.constBegin();
- for (; it != selectedShapes.constEnd(); ++it) {
- if (dynamic_cast<KoShapeGroup*>(*it))
- continue;
-
- const QTransform shapeTransform = (*it)->absoluteTransformation(0);
- const QRectF shapeRect(QRectF(QPointF(), (*it)->size()));
-
- if (first) {
- bb = (shapeTransform * invSelectionTransform).mapRect(shapeRect);
- bound = shapeTransform.mapRect(shapeRect);
- first = false;
- } else {
- bb = bb.united((shapeTransform * invSelectionTransform).mapRect(shapeRect));
- bound = bound.united(shapeTransform.mapRect(shapeRect));
- }
- }
- }
-
- globalBound = bound;
- return bb;
-}
+#include "kis_debug.h"
-void KoSelectionPrivate::requestSelectionChangedEvent()
+KoSelection::KoSelection()
+ : KoShape(new KoSelectionPrivate(this))
{
- if (eventTriggered)
- return;
- eventTriggered = true;
- QTimer::singleShot(0, q, SLOT(selectionChangedEvent()));
+ Q_D(KoSelection);
+ connect(&d->selectionChangedCompressor, SIGNAL(timeout()), SIGNAL(selectionChanged()));
}
-void KoSelectionPrivate::selectionChangedEvent()
+KoSelection::~KoSelection()
{
- eventTriggered = false;
- emit q->selectionChanged();
}
-void KoSelectionPrivate::selectGroupChildren(KoShapeGroup *group)
+void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
{
- if (! group)
- return;
-
- Q_FOREACH (KoShape *shape, group->shapes()) {
- if (selectedShapes.contains(shape))
- continue;
- selectedShapes << shape;
-
- KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(shape);
- if (childGroup)
- selectGroupChildren(childGroup);
- }
+ Q_UNUSED(painter);
+ Q_UNUSED(converter);
+ Q_UNUSED(paintcontext);
}
-void KoSelectionPrivate::deselectGroupChildren(KoShapeGroup *group)
+void KoSelection::setSize(const QSizeF &size)
{
- if (! group)
- return;
-
- Q_FOREACH (KoShape *shape, group->shapes()) {
- if (selectedShapes.contains(shape))
- selectedShapes.removeAll(shape);
-
- KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(shape);
- if (childGroup)
- deselectGroupChildren(childGroup);
- }
+ Q_UNUSED(size);
+ qWarning() << "WARNING: KoSelection::setSize() should never be used!";
}
-////////////
-
-KoSelection::KoSelection()
- : KoShape(*(new KoSelectionPrivate(this)))
+QSizeF KoSelection::size() const
{
+ return outlineRect().size();
}
-KoSelection::~KoSelection()
+QRectF KoSelection::outlineRect() const
{
+ Q_D(const KoSelection);
+
+ QPolygonF globalPolygon;
+ Q_FOREACH (KoShape *shape, d->selectedShapes) {
+ globalPolygon = globalPolygon.united(
+ shape->absoluteTransformation(0).map(QPolygonF(shape->outlineRect())));
+ }
+ const QPolygonF localPolygon = transformation().inverted().map(globalPolygon);
+
+ return localPolygon.boundingRect();
}
-void KoSelection::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
+QRectF KoSelection::boundingRect() const
{
- Q_UNUSED(painter);
- Q_UNUSED(converter);
- Q_UNUSED(paintcontext);
+ Q_D(const KoSelection);
+ return KoShape::boundingRect(d->selectedShapes);
}
-void KoSelection::select(KoShape *shape, bool recursive)
+void KoSelection::select(KoShape *shape)
{
Q_D(KoSelection);
- Q_ASSERT(shape != this);
- Q_ASSERT(shape);
- if (!shape->isSelectable() || !shape->isVisible(true))
+ KIS_SAFE_ASSERT_RECOVER_RETURN(shape != this);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
+
+ if (!shape->isSelectable() || !shape->isVisible(true)) {
return;
+ }
- // save old number of selected shapes
- int oldSelectionCount = d->selectedShapes.count();
+ // check recursively
+ if (isSelected(shape)) {
+ return;
+ }
- if (!d->selectedShapes.contains(shape))
- d->selectedShapes << shape;
-
- // automatically recursively select all child shapes downwards in the hierarchy
- KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
- if (group)
- d->selectGroupChildren(group);
-
- if (recursive) {
- // recursively select all parents and their children upwards the hierarchy
- KoShapeContainer *parent = shape->parent();
- while (parent) {
- KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(parent);
- if (! parentGroup) break;
- if (! d->selectedShapes.contains(parentGroup)) {
- d->selectedShapes << parentGroup;
- d->selectGroupChildren(parentGroup);
- }
- parent = parentGroup->parent();
- }
+ // find the topmost parent to select
+ while (KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent())) {
+ shape = parentGroup;
}
- if (d->selectedShapes.count() == 1) {
+ d->selectedShapes << shape;
+ shape->addShapeChangeListener(this);
+
+ d->savedMatrices = d->fetchShapesMatrices();
+
+ if (d->selectedShapes.size() == 1) {
setTransformation(shape->absoluteTransformation(0));
- updateSizeAndPosition();
} else {
- // reset global bound if there were no shapes selected before
- if (!oldSelectionCount)
- d->globalBound = QRectF();
-
setTransformation(QTransform());
- // we are resetting the transformation here anyway,
- // so we can just add the newly selected shapes to the bounding box
- // in document coordinates and then use that size and position
- int newSelectionCount = d->selectedShapes.count();
- for (int i = oldSelectionCount; i < newSelectionCount; ++i) {
- KoShape *shape = d->selectedShapes[i];
-
- // don't add the rect of the group rect, as it can be invalid
- if (dynamic_cast<KoShapeGroup*>(shape)) {
- continue;
- }
- const QTransform shapeTransform = shape->absoluteTransformation(0);
- const QRectF shapeRect(QRectF(QPointF(), shape->size()));
-
- d->globalBound = d->globalBound.united(shapeTransform.mapRect(shapeRect));
- }
- setSize(d->globalBound.size());
- setPosition(d->globalBound.topLeft());
}
- d->requestSelectionChangedEvent();
+ d->selectionChangedCompressor.start();
}
-void KoSelection::deselect(KoShape *shape, bool recursive)
+void KoSelection::deselect(KoShape *shape)
{
Q_D(KoSelection);
- if (! d->selectedShapes.contains(shape))
+ if (!d->selectedShapes.contains(shape))
return;
d->selectedShapes.removeAll(shape);
+ shape->removeShapeChangeListener(this);
+ d->savedMatrices = d->fetchShapesMatrices();
- KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(shape);
- if (recursive) {
- // recursively find the top group upwards int the hierarchy
- KoShapeGroup *parentGroup = dynamic_cast<KoShapeGroup*>(shape->parent());
- while (parentGroup) {
- group = parentGroup;
- parentGroup = dynamic_cast<KoShapeGroup*>(parentGroup->parent());
- }
+ if (d->selectedShapes.size() == 1) {
+ setTransformation(d->selectedShapes.first()->absoluteTransformation(0));
}
- if (group)
- d->deselectGroupChildren(group);
-
- if (count() == 1)
- setTransformation(firstSelectedShape()->absoluteTransformation(0));
-
- updateSizeAndPosition();
- d->requestSelectionChangedEvent();
+ d->selectionChangedCompressor.start();
}
void KoSelection::deselectAll()
{
Q_D(KoSelection);
- // reset the transformation matrix of the selection
- setTransformation(QTransform());
if (d->selectedShapes.isEmpty())
return;
+
+ Q_FOREACH (KoShape *shape, d->selectedShapes) {
+ shape->removeShapeChangeListener(this);
+ }
+ d->savedMatrices = d->fetchShapesMatrices();
+
+ // reset the transformation matrix of the selection
+ setTransformation(QTransform());
+
d->selectedShapes.clear();
- d->requestSelectionChangedEvent();
+ d->selectionChangedCompressor.start();
}
int KoSelection::count() const
{
Q_D(const KoSelection);
- int count = 0;
- Q_FOREACH (KoShape *shape, d->selectedShapes)
- if (dynamic_cast<KoShapeGroup*>(shape) == 0)
- ++count;
- return count;
+ return d->selectedShapes.size();
}
bool KoSelection::hitTest(const QPointF &position) const
{
Q_D(const KoSelection);
- if (count() > 1) {
- QRectF bb(boundingRect());
- return bb.contains(position);
- } else if (count() == 1) {
- return (*d->selectedShapes.begin())->hitTest(position);
- } else { // count == 0
- return false;
+
+ Q_FOREACH (KoShape *shape, d->selectedShapes) {
+ if (shape->hitTest(position)) return true;
}
+
+ return false;
}
-void KoSelection::updateSizeAndPosition()
+
+const QList<KoShape*> KoSelection::selectedShapes() const
{
- Q_D(KoSelection);
- QRectF bb = d->sizeRect();
- QTransform matrix = absoluteTransformation(0);
- setSize(bb.size());
- QPointF p = matrix.map(bb.topLeft() + matrix.inverted().map(position()));
- setPosition(p);
+ Q_D(const KoSelection);
+ return d->selectedShapes;
}
-QRectF KoSelection::boundingRect() const
+const QList<KoShape *> KoSelection::selectedEditableShapes() const
{
- return absoluteTransformation(0).mapRect(QRectF(QPointF(), size()));
+ Q_D(const KoSelection);
+
+ QList<KoShape*> shapes = selectedShapes();
+
+ KritaUtils::filterContainer (shapes, [](KoShape *shape) {
+ return shape->isEditable();
+ });
+
+ return shapes;
}
-const QList<KoShape*> KoSelection::selectedShapes(KoFlake::SelectionType strip) const
+const QList<KoShape *> KoSelection::selectedEditableShapesAndDelegates() const
{
- Q_D(const KoSelection);
- QList<KoShape*> answer;
- // strip the child objects when there is also a parent included.
- bool doStripping = strip == KoFlake::StrippedSelection;
- Q_FOREACH (KoShape *shape, d->selectedShapes) {
- KoShapeContainer *container = shape->parent();
- if (strip != KoFlake::TopLevelSelection && dynamic_cast<KoShapeGroup*>(shape))
- // since a KoShapeGroup
- // guarentees all its children are selected at the same time as itself
- // is selected we will only return its children.
- continue;
- bool add = true;
- while (doStripping && add && container) {
- if (dynamic_cast<KoShapeGroup*>(container) == 0 && d->selectedShapes.contains(container))
- add = false;
- container = container->parent();
+ 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);
+ }
}
- if (strip == KoFlake::TopLevelSelection && container && d->selectedShapes.contains(container))
- add = false;
- if (add)
- answer << shape;
}
- return answer;
+ return shapes;
}
bool KoSelection::isSelected(const KoShape *shape) const
{
Q_D(const KoSelection);
if (shape == this)
return true;
- foreach (KoShape *s, d->selectedShapes) {
- if (s == shape)
- return true;
+ const KoShape *tmpShape = shape;
+ while (tmpShape && std::find(d->selectedShapes.begin(), d->selectedShapes.end(), tmpShape) == d->selectedShapes.end()/*d->selectedShapes.contains(tmpShape)*/) {
+ tmpShape = tmpShape->parent();
}
- return false;
+ return tmpShape;
}
-KoShape *KoSelection::firstSelectedShape(KoFlake::SelectionType strip) const
+KoShape *KoSelection::firstSelectedShape() const
{
- QList<KoShape*> set = selectedShapes(strip);
- if (set.isEmpty())
- return 0;
- return *(set.begin());
+ Q_D(const KoSelection);
+ return !d->selectedShapes.isEmpty() ? d->selectedShapes.first() : 0;
}
void KoSelection::setActiveLayer(KoShapeLayer *layer)
{
Q_D(KoSelection);
d->activeLayer = layer;
emit currentLayerChanged(layer);
}
KoShapeLayer* KoSelection::activeLayer() const
{
Q_D(const KoSelection);
return d->activeLayer;
}
+void KoSelection::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
+{
+ Q_UNUSED(shape);
+ Q_D(KoSelection);
+
+ if (type == KoShape::Deleted) {
+ deselect(shape);
+ // HACK ALERT: the caller will also remove the listener, so re-add it here
+ shape->addShapeChangeListener(this);
+
+ } else if (type >= KoShape::PositionChanged && type <= KoShape::GenericMatrixChange) {
+ QList<QTransform> matrices = d->fetchShapesMatrices();
+
+ QTransform newTransform;
+ if (d->checkMatricesConsistent(matrices, &newTransform)) {
+ d->savedMatrices = matrices;
+ setTransformation(newTransform);
+ } else {
+ d->savedMatrices = matrices;
+ setTransformation(QTransform());
+ }
+ }
+}
+
void KoSelection::saveOdf(KoShapeSavingContext &) const
{
}
bool KoSelection::loadOdf(const KoXmlElement &, KoShapeLoadingContext &)
{
return true;
}
-//have to include this because of Q_PRIVATE_SLOT
-#include "moc_KoSelection.cpp"
+
+QList<QTransform> KoSelectionPrivate::fetchShapesMatrices() const
+{
+ QList<QTransform> result;
+ Q_FOREACH (KoShape *shape, selectedShapes) {
+ result << shape->absoluteTransformation(0);
+ }
+ return result;
+}
+
+bool KoSelectionPrivate::checkMatricesConsistent(const QList<QTransform> &matrices, QTransform *newTransform)
+{
+ Q_Q(KoSelection);
+
+ QTransform commonDiff;
+ bool haveCommonDiff = false;
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(matrices.size() == selectedShapes.size(), false);
+
+ for (int i = 0; i < matrices.size(); i++) {
+ QTransform t = savedMatrices[i];
+ QTransform diff = t.inverted() * matrices[i];
+
+
+ if (haveCommonDiff) {
+ if (!KisAlgebra2D::fuzzyMatrixCompare(commonDiff, diff, 1e-5)) {
+ return false;
+ }
+ } else {
+ commonDiff = diff;
+ }
+ }
+
+ *newTransform = q->transformation() * commonDiff;
+ return true;
+}
diff --git a/libs/flake/KoSelection.h b/libs/flake/KoSelection.h
index bdb4cdf1b7..e5919cebed 100644
--- a/libs/flake/KoSelection.h
+++ b/libs/flake/KoSelection.h
@@ -1,153 +1,165 @@
/* 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
+class KRITAFLAKE_EXPORT KoSelection : public QObject, public KoShape, public KoShape::ShapeChangeListener
{
Q_OBJECT
public:
KoSelection();
virtual ~KoSelection();
- virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext);
+ void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) 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
* @param recursive enables recursively selecting shapes of parent groups
*/
- void select(KoShape *shape, bool recursive = true);
+ 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
* @param recursive enables recursively deselecting shapes of parent groups
*/
- void deselect(KoShape *shape, bool recursive = true);
+ void deselect(KoShape *shape);
/// clear the selections list
void deselectAll();
/**
* Return the list of selected shapes
* @return the list of selected shapes
* @param strip if StrippedSelection, the returned list will not include any children
* of a container shape if the container-parent is itself also in the set.
*/
- const QList<KoShape*> selectedShapes(KoFlake::SelectionType strip = KoFlake::FullSelection) const;
+ const QList<KoShape*> selectedShapes() 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.
* @param strip if StrippedSelection, the returned list will not include any children
* of a grouped shape if the group-parent is itself also in the set.
*/
- KoShape *firstSelectedShape(KoFlake::SelectionType strip = KoFlake::FullSelection) const;
+ 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;
virtual bool hitTest(const QPointF &position) const;
- virtual QRectF boundingRect() const;
-
/**
* 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;
- /// Updates the size and position of the selection
- void updateSizeAndPosition();
+ void notifyShapeChanged(ChangeType type, KoShape *shape);
Q_SIGNALS:
/// emitted when the selection is changed
void selectionChanged();
/// emitted when the current layer is changed
void currentLayerChanged(const KoShapeLayer *layer);
private:
virtual void saveOdf(KoShapeSavingContext &) const;
virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &);
- Q_PRIVATE_SLOT(d_func(), void selectionChangedEvent())
Q_DECLARE_PRIVATE_D(KoShape::d_ptr, KoSelection)
};
#endif
diff --git a/libs/flake/KoSelection_p.h b/libs/flake/KoSelection_p.h
index 0321cd0224..39c12de9f0 100644
--- a/libs/flake/KoSelection_p.h
+++ b/libs/flake/KoSelection_p.h
@@ -1,48 +1,50 @@
/* 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 KOSELECTIONPRIVATE_H
#define KOSELECTIONPRIVATE_H
#include "KoShape_p.h"
+#include "kis_signal_compressor.h"
+
class KoShapeGroup;
class KoSelectionPrivate : public KoShapePrivate
{
public:
explicit KoSelectionPrivate(KoSelection *parent)
- : KoShapePrivate(parent), eventTriggered(false), activeLayer(0), q(parent) {}
+ : KoShapePrivate(parent),
+ activeLayer(0),
+ selectionChangedCompressor(1, KisSignalCompressor::FIRST_INACTIVE)
+ {}
QList<KoShape*> selectedShapes;
- bool eventTriggered;
-
KoShapeLayer *activeLayer;
- void requestSelectionChangedEvent();
- void selectGroupChildren(KoShapeGroup *group);
- void deselectGroupChildren(KoShapeGroup *group);
+ KisSignalCompressor selectionChangedCompressor;
+
- void selectionChangedEvent();
+ QList<QTransform> savedMatrices;
- QRectF sizeRect();
+ QList<QTransform> fetchShapesMatrices() const;
+ bool checkMatricesConsistent(const QList<QTransform> &matrices, QTransform *newTransform);
- KoSelection *q;
- QRectF globalBound;
+ Q_DECLARE_PUBLIC(KoSelection)
};
#endif
diff --git a/libs/flake/KoShape.cpp b/libs/flake/KoShape.cpp
index e0d2ec7ee6..b8fcb148b9 100644
--- a/libs/flake/KoShape.cpp
+++ b/libs/flake/KoShape.cpp
@@ -1,2288 +1,2459 @@
/* 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@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 "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 <limits>
#include "KoOdfGradientBackground.h"
+#include <KisHandlePainterHelper.h>
// KoShapePrivate
KoShapePrivate::KoShapePrivate(KoShape *shape)
: q_ptr(shape),
size(50, 50),
parent(0),
- userData(0),
- appData(0),
- stroke(0),
shadow(0),
border(0),
- clipPath(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),
- anchor(0),
- minimumHeight(0.0)
+ textRunAroundContour(KoShape::ContourFull)
{
connectors[KoConnectionPoint::TopConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::TopConnectionPoint);
connectors[KoConnectionPoint::RightConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::RightConnectionPoint);
connectors[KoConnectionPoint::BottomConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::BottomConnectionPoint);
connectors[KoConnectionPoint::LeftConnectionPoint] = KoConnectionPoint::defaultConnectionPoint(KoConnectionPoint::LeftConnectionPoint);
connectors[KoConnectionPoint::FirstCustomConnectionPoint] = KoConnectionPoint(QPointF(0.5, 0.5), KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
}
+KoShapePrivate::KoShapePrivate(const KoShapePrivate &rhs, KoShape *q)
+ : q_ptr(q),
+ size(rhs.size),
+ shapeId(rhs.shapeId),
+ name(rhs.name),
+ localMatrix(rhs.localMatrix),
+ connectors(rhs.connectors),
+ parent(0), // to be initialized later
+ shapeManagers(), // to be initialized later
+ toolDelegates(), // FIXME: how to initialize them?
+ userData(rhs.userData ? rhs.userData->clone() : 0),
+ stroke(rhs.stroke),
+ fill(rhs.fill),
+ dependees(), // FIXME: how to initialize them?
+ shadow(0), // WARNING: not implemented in Krita
+ border(0), // WARNING: not implemented in Krita
+ clipPath(rhs.clipPath ? rhs.clipPath->clone() : 0),
+ clipMask(rhs.clipMask ? rhs.clipMask->clone() : 0),
+ additionalAttributes(rhs.additionalAttributes),
+ additionalStyleAttributes(rhs.additionalStyleAttributes),
+ filterEffectStack(0), // WARNING: not implemented in Krita
+ transparency(rhs.transparency),
+ hyperLink(rhs.hyperLink),
+
+ zIndex(rhs.zIndex),
+ runThrough(rhs.runThrough),
+ visible(rhs.visible),
+ printable(rhs.visible),
+ geometryProtected(rhs.geometryProtected),
+ keepAspect(rhs.keepAspect),
+ selectable(rhs.selectable),
+ detectCollision(rhs.detectCollision),
+ protectContent(rhs.protectContent),
+
+ textRunAroundSide(rhs.textRunAroundSide),
+ textRunAroundDistanceLeft(rhs.textRunAroundDistanceLeft),
+ textRunAroundDistanceTop(rhs.textRunAroundDistanceTop),
+ textRunAroundDistanceRight(rhs.textRunAroundDistanceRight),
+ textRunAroundDistanceBottom(rhs.textRunAroundDistanceBottom),
+ textRunAroundThreshold(rhs.textRunAroundThreshold),
+ textRunAroundContour(rhs.textRunAroundContour)
+{
+}
+
KoShapePrivate::~KoShapePrivate()
{
Q_Q(KoShape);
- if (parent)
+ if (parent) {
parent->removeShape(q);
+ }
+
Q_FOREACH (KoShapeManager *manager, shapeManagers) {
- manager->remove(q);
- manager->removeAdditional(q);
+ manager->shapeInterface()->notifyShapeDestructed(q);
}
- delete userData;
- delete appData;
- if (stroke && !stroke->deref())
- delete stroke;
+ shapeManagers.clear();
+
if (shadow && !shadow->deref())
delete shadow;
if (filterEffectStack && !filterEffectStack->deref())
delete filterEffectStack;
- delete clipPath;
}
void KoShapePrivate::shapeChanged(KoShape::ChangeType type)
{
Q_Q(KoShape);
if (parent)
parent->model()->childChanged(q, type);
+
q->shapeChanged(type);
- Q_FOREACH (KoShape * shape, dependees)
+
+ Q_FOREACH (KoShape * shape, dependees) {
shape->shapeChanged(type, q);
+ }
+
+ Q_FOREACH (KoShape::ShapeChangeListener *listener, listeners) {
+ listener->notifyShapeChangedImpl(type, q);
+ }
}
void KoShapePrivate::updateStroke()
{
Q_Q(KoShape);
- if (stroke == 0)
- return;
+ if (!stroke) return;
+
KoInsets insets;
stroke->strokeInsets(q, insets);
QSizeF inner = q->size();
// update left
q->update(QRectF(-insets.left, -insets.top, insets.left,
inner.height() + insets.top + insets.bottom));
// update top
q->update(QRectF(-insets.left, -insets.top,
inner.width() + insets.left + insets.right, insets.top));
// update right
q->update(QRectF(inner.width(), -insets.top, insets.right,
inner.height() + insets.top + insets.bottom));
// update bottom
q->update(QRectF(-insets.left, inner.height(),
inner.width() + insets.left + insets.right, insets.bottom));
}
void KoShapePrivate::addShapeManager(KoShapeManager *manager)
{
shapeManagers.insert(manager);
}
void KoShapePrivate::removeShapeManager(KoShapeManager *manager)
{
shapeManagers.remove(manager);
}
void KoShapePrivate::convertFromShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
switch(point.alignment) {
case KoConnectionPoint::AlignNone:
point.position = KoFlake::toRelative(point.position, shapeSize);
point.position.rx() = qBound<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();
case KoConnectionPoint::AlignLeft:
point.position.ry() = 0.5*shapeSize.height();
break;
case KoConnectionPoint::AlignBottom:
point.position.ry() -= shapeSize.height();
case KoConnectionPoint::AlignTop:
point.position.rx() = 0.5*shapeSize.width();
break;
case KoConnectionPoint::AlignTopLeft:
// nothing to do here
break;
case KoConnectionPoint::AlignTopRight:
point.position.rx() -= shapeSize.width();
break;
case KoConnectionPoint::AlignBottomLeft:
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignBottomRight:
point.position.rx() -= shapeSize.width();
point.position.ry() -= shapeSize.height();
break;
case KoConnectionPoint::AlignCenter:
point.position.rx() -= 0.5 * shapeSize.width();
point.position.ry() -= 0.5 * shapeSize.height();
break;
}
}
void KoShapePrivate::convertToShapeCoordinates(KoConnectionPoint &point, const QSizeF &shapeSize) const
{
switch(point.alignment) {
case KoConnectionPoint::AlignNone:
point.position = KoFlake::toAbsolute(point.position, shapeSize);
break;
case KoConnectionPoint::AlignRight:
point.position.rx() += shapeSize.width();
case KoConnectionPoint::AlignLeft:
point.position.ry() = 0.5*shapeSize.height();
break;
case KoConnectionPoint::AlignBottom:
point.position.ry() += shapeSize.height();
case KoConnectionPoint::AlignTop:
point.position.rx() = 0.5*shapeSize.width();
break;
case KoConnectionPoint::AlignTopLeft:
// nothing to do here
break;
case KoConnectionPoint::AlignTopRight:
point.position.rx() += shapeSize.width();
break;
case KoConnectionPoint::AlignBottomLeft:
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignBottomRight:
point.position.rx() += shapeSize.width();
point.position.ry() += shapeSize.height();
break;
case KoConnectionPoint::AlignCenter:
point.position.rx() += 0.5 * shapeSize.width();
point.position.ry() += 0.5 * shapeSize.height();
break;
}
}
// static
QString KoShapePrivate::getStyleProperty(const char *property, KoShapeLoadingContext &context)
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
QString value;
if (styleStack.hasProperty(KoXmlNS::draw, property)) {
value = styleStack.property(KoXmlNS::draw, property);
}
return value;
}
// ======== KoShape
KoShape::KoShape()
: d_ptr(new KoShapePrivate(this))
{
notifyChanged();
}
-KoShape::KoShape(KoShapePrivate &dd)
- : d_ptr(&dd)
+KoShape::KoShape(KoShapePrivate *dd)
+ : d_ptr(dd)
{
}
KoShape::~KoShape()
{
Q_D(KoShape);
d->shapeChanged(Deleted);
delete d_ptr;
}
+KoShape *KoShape::cloneShape() const
+{
+ return 0;
+}
+
void KoShape::scale(qreal sx, qreal sy)
{
Q_D(KoShape);
QPointF pos = position();
QTransform scaleMatrix;
scaleMatrix.translate(pos.x(), pos.y());
scaleMatrix.scale(sx, sy);
scaleMatrix.translate(-pos.x(), -pos.y());
d->localMatrix = d->localMatrix * scaleMatrix;
notifyChanged();
d->shapeChanged(ScaleChanged);
}
void KoShape::rotate(qreal angle)
{
Q_D(KoShape);
QPointF center = d->localMatrix.map(QPointF(0.5 * size().width(), 0.5 * size().height()));
QTransform rotateMatrix;
rotateMatrix.translate(center.x(), center.y());
rotateMatrix.rotate(angle);
rotateMatrix.translate(-center.x(), -center.y());
d->localMatrix = d->localMatrix * rotateMatrix;
notifyChanged();
d->shapeChanged(RotationChanged);
}
void KoShape::shear(qreal sx, qreal sy)
{
Q_D(KoShape);
QPointF pos = position();
QTransform shearMatrix;
shearMatrix.translate(pos.x(), pos.y());
shearMatrix.shear(sx, sy);
shearMatrix.translate(-pos.x(), -pos.y());
d->localMatrix = d->localMatrix * shearMatrix;
notifyChanged();
d->shapeChanged(ShearChanged);
}
void KoShape::setSize(const QSizeF &newSize)
{
Q_D(KoShape);
QSizeF oldSize(size());
// always set size, as d->size and size() may vary
d->size = newSize;
if (oldSize == newSize)
return;
notifyChanged();
d->shapeChanged(SizeChanged);
}
void KoShape::setPosition(const QPointF &newPosition)
{
Q_D(KoShape);
QPointF currentPos = position();
if (newPosition == currentPos)
return;
QTransform translateMatrix;
translateMatrix.translate(newPosition.x() - currentPos.x(), newPosition.y() - currentPos.y());
d->localMatrix = d->localMatrix * translateMatrix;
notifyChanged();
d->shapeChanged(PositionChanged);
}
bool KoShape::hitTest(const QPointF &position) const
{
Q_D(const KoShape);
if (d->parent && d->parent->isClipped(this) && !d->parent->hitTest(position))
return false;
QPointF point = absoluteTransformation(0).inverted().map(position);
QRectF bb(QPointF(), size());
if (d->stroke) {
KoInsets insets;
d->stroke->strokeInsets(this, insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
if (bb.contains(point))
return true;
// if there is no shadow we can as well just leave
if (! d->shadow)
return false;
// the shadow has an offset to the shape, so we simply
// check if the position minus the shadow offset hits the shape
point = absoluteTransformation(0).inverted().map(position - d->shadow->offset());
return bb.contains(point);
}
QRectF KoShape::boundingRect() const
{
Q_D(const KoShape);
QTransform transform = absoluteTransformation(0);
QRectF bb = outlineRect();
if (d->stroke) {
KoInsets insets;
d->stroke->strokeInsets(this, insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
bb = transform.mapRect(bb);
if (d->shadow) {
KoInsets insets;
d->shadow->insets(insets);
bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom);
}
if (d->filterEffectStack) {
QRectF clipRect = d->filterEffectStack->clipRectForBoundingRect(outlineRect());
bb |= transform.mapRect(clipRect);
}
return bb;
}
+QRectF KoShape::boundingRect(const QList<KoShape *> &shapes)
+{
+ QRectF boundingRect;
+ Q_FOREACH (KoShape *shape, shapes) {
+ boundingRect |= shape->boundingRect();
+ }
+ return boundingRect;
+}
+
+QRectF KoShape::absoluteOutlineRect(KoViewConverter *converter) const
+{
+ return absoluteTransformation(converter).map(outline()).boundingRect();
+}
+
+QRectF KoShape::absoluteOutlineRect(const QList<KoShape *> &shapes, KoViewConverter *converter)
+{
+ QRectF absoluteOutlineRect;
+ Q_FOREACH (KoShape *shape, shapes) {
+ absoluteOutlineRect |= shape->absoluteOutlineRect(converter);
+ }
+ return absoluteOutlineRect;
+}
+
QTransform KoShape::absoluteTransformation(const KoViewConverter *converter) const
{
Q_D(const KoShape);
QTransform matrix;
// apply parents matrix to inherit any transformations done there.
KoShapeContainer * container = d->parent;
if (container) {
if (container->inheritsTransform(this)) {
// We do need to pass the converter here, otherwise the parent's
// translation is not inherited.
matrix = container->absoluteTransformation(converter);
} else {
QSizeF containerSize = container->size();
QPointF containerPos = container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height());
if (converter)
containerPos = converter->documentToView(containerPos);
matrix.translate(containerPos.x(), containerPos.y());
}
}
if (converter) {
QPointF pos = d->localMatrix.map(QPointF());
QPointF trans = converter->documentToView(pos) - pos;
matrix.translate(trans.x(), trans.y());
}
return d->localMatrix * matrix;
}
void KoShape::applyAbsoluteTransformation(const QTransform &matrix)
{
QTransform globalMatrix = absoluteTransformation(0);
// the transformation is relative to the global coordinate system
// but we want to change the local matrix, so convert the matrix
// to be relative to the local coordinate system
QTransform transformMatrix = globalMatrix * matrix * globalMatrix.inverted();
applyTransformation(transformMatrix);
}
void KoShape::applyTransformation(const QTransform &matrix)
{
Q_D(KoShape);
d->localMatrix = matrix * d->localMatrix;
notifyChanged();
d->shapeChanged(GenericMatrixChange);
}
void KoShape::setTransformation(const QTransform &matrix)
{
Q_D(KoShape);
d->localMatrix = matrix;
notifyChanged();
d->shapeChanged(GenericMatrixChange);
}
QTransform KoShape::transformation() const
{
Q_D(const KoShape);
return d->localMatrix;
}
KoShape::ChildZOrderPolicy KoShape::childZOrderPolicy()
{
return ChildZDefault;
}
bool KoShape::compareShapeZIndex(KoShape *s1, KoShape *s2)
{
+ /**
+ * WARNING: Our definition of zIndex is not yet compatible with SVG2's
+ * definition. In SVG stacking context of groups with the same
+ * zIndex are **merged**, while in Krita the contents of groups
+ * is never merged. One group will always below than the other.
+ * Therefore, when zIndex of two groups inside the same parent
+ * coinside, the resulting painting order in Krita is
+ * **UNDEFINED**.
+ *
+ * To avoid this trouble we use KoShapeReorderCommand::mergeInShape()
+ * inside KoShapeCreateCommand.
+ */
+
// First sort according to runThrough which is sort of a master level
KoShape *parentShapeS1 = s1->parent();
KoShape *parentShapeS2 = s2->parent();
int runThrough1 = s1->runThrough();
int runThrough2 = s2->runThrough();
while (parentShapeS1) {
if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
runThrough1 = parentShapeS1->runThrough();
} else {
runThrough1 = runThrough1 + parentShapeS1->runThrough();
}
parentShapeS1 = parentShapeS1->parent();
}
while (parentShapeS2) {
if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
runThrough2 = parentShapeS2->runThrough();
} else {
runThrough2 = runThrough2 + parentShapeS2->runThrough();
}
parentShapeS2 = parentShapeS2->parent();
}
if (runThrough1 > runThrough2) {
return false;
}
if (runThrough1 < runThrough2) {
return true;
}
// If on the same runThrough level then the zIndex is all that matters.
//
// We basically walk up through the parents until we find a common base parent
// To do that we need two loops where the inner loop walks up through the parents
// of s2 every time we step up one parent level on s1
//
// We don't update the index value until after we have seen that it's not a common base
// That way we ensure that two children of a common base are sorted according to their respective
// z value
bool foundCommonParent = false;
int index1 = s1->zIndex();
int index2 = s2->zIndex();
parentShapeS1 = s1;
parentShapeS2 = s2;
while (parentShapeS1 && !foundCommonParent) {
parentShapeS2 = s2;
index2 = parentShapeS2->zIndex();
while (parentShapeS2) {
if (parentShapeS2 == parentShapeS1) {
foundCommonParent = true;
break;
}
if (parentShapeS2->childZOrderPolicy() == KoShape::ChildZParentChild) {
index2 = parentShapeS2->zIndex();
}
parentShapeS2 = parentShapeS2->parent();
}
if (!foundCommonParent) {
if (parentShapeS1->childZOrderPolicy() == KoShape::ChildZParentChild) {
index1 = parentShapeS1->zIndex();
}
parentShapeS1 = parentShapeS1->parent();
}
}
// If the one shape is a parent/child of the other then sort so.
if (s1 == parentShapeS2) {
return true;
}
if (s2 == parentShapeS1) {
return false;
}
// If we went that far then the z-Index is used for sorting.
return index1 < index2;
}
void KoShape::setParent(KoShapeContainer *parent)
{
Q_D(KoShape);
- if (d->parent == parent)
+
+ if (d->parent == parent) {
return;
+ }
+
KoShapeContainer *oldParent = d->parent;
d->parent = 0; // avoids recursive removing
- if (oldParent)
- oldParent->removeShape(this);
+
+ if (oldParent) {
+ oldParent->shapeInterface()->removeShape(this);
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(parent != this);
+
if (parent && parent != this) {
d->parent = parent;
- parent->addShape(this);
+ parent->shapeInterface()->addShape(this);
}
+
notifyChanged();
d->shapeChanged(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;
+}
+
int KoShape::zIndex() const
{
Q_D(const KoShape);
return d->zIndex;
}
void KoShape::update() const
{
Q_D(const KoShape);
if (!d->shapeManagers.empty()) {
QRectF rect(boundingRect());
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->update(rect, this, true);
}
}
}
void KoShape::update(const QRectF &rect) const
{
if (rect.isEmpty() && !rect.isNull()) {
return;
}
Q_D(const KoShape);
if (!d->shapeManagers.empty() && isVisible()) {
QRectF rc(absoluteTransformation(0).mapRect(rect));
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->update(rc);
}
}
}
QPainterPath KoShape::outline() const
{
QPainterPath path;
path.addRect(outlineRect());
return path;
}
QRectF KoShape::outlineRect() const
{
const QSizeF s = size();
return QRectF(QPointF(0, 0), QSizeF(qMax(s.width(), qreal(0.0001)),
qMax(s.height(), qreal(0.0001))));
}
QPainterPath KoShape::shadowOutline() const
{
Q_D(const KoShape);
if (d->fill) {
return outline();
}
return QPainterPath();
}
-QPointF KoShape::absolutePosition(KoFlake::Position anchor) const
+QPointF KoShape::absolutePosition(KoFlake::AnchorPosition anchor) const
{
- QPointF point;
- switch (anchor) {
- case KoFlake::TopLeftCorner: break;
- case KoFlake::TopRightCorner: point = QPointF(size().width(), 0.0); break;
- case KoFlake::BottomLeftCorner: point = QPointF(0.0, size().height()); break;
- case KoFlake::BottomRightCorner: point = QPointF(size().width(), size().height()); break;
- case KoFlake::CenteredPosition: point = QPointF(size().width() / 2.0, size().height() / 2.0); break;
+ const QRectF rc = outlineRect();
+
+ QPointF point = rc.topLeft();
+
+ bool valid = false;
+ QPointF anchoredPoint = KoFlake::anchorToPoint(anchor, rc, &valid);
+ if (valid) {
+ point = anchoredPoint;
}
+
return absoluteTransformation(0).map(point);
}
-void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::Position anchor)
+void KoShape::setAbsolutePosition(const QPointF &newPosition, KoFlake::AnchorPosition anchor)
{
Q_D(KoShape);
QPointF currentAbsPosition = absolutePosition(anchor);
QPointF translate = newPosition - currentAbsPosition;
QTransform translateMatrix;
translateMatrix.translate(translate.x(), translate.y());
applyAbsoluteTransformation(translateMatrix);
notifyChanged();
d->shapeChanged(PositionChanged);
}
void KoShape::copySettings(const KoShape *shape)
{
Q_D(KoShape);
d->size = shape->size();
d->connectors.clear();
Q_FOREACH (const KoConnectionPoint &point, shape->connectionPoints())
addConnectionPoint(point);
d->zIndex = shape->zIndex();
d->visible = shape->isVisible();
// Ensure printable is true by default
if (!d->visible)
d->printable = true;
else
d->printable = shape->isPrintable();
d->geometryProtected = shape->isGeometryProtected();
d->protectContent = shape->isContentProtected();
d->selectable = shape->isSelectable();
d->keepAspect = shape->keepAspectRatio();
d->localMatrix = shape->d_ptr->localMatrix;
}
void KoShape::notifyChanged()
{
Q_D(KoShape);
Q_FOREACH (KoShapeManager * manager, d->shapeManagers) {
manager->notifyShapeChanged(this);
}
}
void KoShape::setUserData(KoShapeUserData *userData)
{
Q_D(KoShape);
- delete d->userData;
- d->userData = userData;
+ d->userData.reset(userData);
}
KoShapeUserData *KoShape::userData() const
{
Q_D(const KoShape);
- return d->userData;
-}
-
-void KoShape::setApplicationData(KoShapeApplicationData *appData)
-{
- Q_D(KoShape);
- // appdata is deleted by the application.
- d->appData = appData;
-}
-
-KoShapeApplicationData *KoShape::applicationData() const
-{
- Q_D(const KoShape);
- return d->appData;
+ return d->userData.data();
}
bool KoShape::hasTransparency() const
{
Q_D(const KoShape);
if (! d->fill)
return true;
else
return d->fill->hasTransparency() || d->transparency > 0.0;
}
void KoShape::setTransparency(qreal transparency)
{
Q_D(KoShape);
d->transparency = qBound<qreal>(0.0, transparency, 1.0);
+
+ d->shapeChanged(TransparencyChanged);
+ notifyChanged();
}
qreal KoShape::transparency(bool recursive) const
{
Q_D(const KoShape);
if (!recursive || !parent()) {
return d->transparency;
} else {
const qreal parentOpacity = 1.0-parent()->transparency(recursive);
const qreal childOpacity = 1.0-d->transparency;
return 1.0-(parentOpacity*childOpacity);
}
}
KoInsets KoShape::strokeInsets() const
{
Q_D(const KoShape);
KoInsets answer;
if (d->stroke)
d->stroke->strokeInsets(this, answer);
return answer;
}
qreal KoShape::rotation() const
{
Q_D(const KoShape);
// try to extract the rotation angle out of the local matrix
// if it is a pure rotation matrix
// check if the matrix has shearing mixed in
if (fabs(fabs(d->localMatrix.m12()) - fabs(d->localMatrix.m21())) > 1e-10)
return std::numeric_limits<qreal>::quiet_NaN();
// check if the matrix has scaling mixed in
if (fabs(d->localMatrix.m11() - d->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;
if (angle < 0.0)
angle += 360.0;
return angle;
}
QSizeF KoShape::size() const
{
Q_D(const KoShape);
return d->size;
}
QPointF KoShape::position() const
{
Q_D(const KoShape);
- QPointF center(0.5*size().width(), 0.5*size().height());
+ QPointF center = outlineRect().center();
return d->localMatrix.map(center) - center;
}
int KoShape::addConnectionPoint(const KoConnectionPoint &point)
{
Q_D(KoShape);
// get next glue point id
int nextConnectionPointId = KoConnectionPoint::FirstCustomConnectionPoint;
if (d->connectors.size())
nextConnectionPointId = qMax(nextConnectionPointId, (--d->connectors.end()).key()+1);
KoConnectionPoint p = point;
d->convertFromShapeCoordinates(p, size());
d->connectors[nextConnectionPointId] = p;
return nextConnectionPointId;
}
bool KoShape::setConnectionPoint(int connectionPointId, const KoConnectionPoint &point)
{
Q_D(KoShape);
if (connectionPointId < 0)
return false;
const bool insertPoint = !hasConnectionPoint(connectionPointId);
switch(connectionPointId) {
case KoConnectionPoint::TopConnectionPoint:
case KoConnectionPoint::RightConnectionPoint:
case KoConnectionPoint::BottomConnectionPoint:
case KoConnectionPoint::LeftConnectionPoint:
{
KoConnectionPoint::PointId id = static_cast<KoConnectionPoint::PointId>(connectionPointId);
d->connectors[id] = KoConnectionPoint::defaultConnectionPoint(id);
break;
}
default:
{
KoConnectionPoint p = point;
d->convertFromShapeCoordinates(p, size());
d->connectors[connectionPointId] = p;
break;
}
}
if(!insertPoint)
d->shapeChanged(ConnectionPointChanged);
return true;
}
bool KoShape::hasConnectionPoint(int connectionPointId) const
{
Q_D(const KoShape);
return d->connectors.contains(connectionPointId);
}
KoConnectionPoint KoShape::connectionPoint(int connectionPointId) const
{
Q_D(const KoShape);
KoConnectionPoint p = d->connectors.value(connectionPointId, KoConnectionPoint());
// convert glue point to shape coordinates
d->convertToShapeCoordinates(p, size());
return p;
}
KoConnectionPoints KoShape::connectionPoints() const
{
Q_D(const KoShape);
QSizeF s = size();
KoConnectionPoints points = d->connectors;
KoConnectionPoints::iterator point = points.begin();
KoConnectionPoints::iterator lastPoint = points.end();
// convert glue points to shape coordinates
for(; point != lastPoint; ++point) {
d->convertToShapeCoordinates(point.value(), s);
}
return points;
}
void KoShape::removeConnectionPoint(int connectionPointId)
{
Q_D(KoShape);
d->connectors.remove(connectionPointId);
d->shapeChanged(ConnectionPointChanged);
}
void KoShape::clearConnectionPoints()
{
Q_D(KoShape);
d->connectors.clear();
}
KoShape::TextRunAroundSide KoShape::textRunAroundSide() const
{
Q_D(const KoShape);
return d->textRunAroundSide;
}
void KoShape::setTextRunAroundSide(TextRunAroundSide side, RunThroughLevel runThrought)
{
Q_D(KoShape);
if (side == RunThrough) {
if (runThrought == Background) {
setRunThrough(-1);
} else {
setRunThrough(1);
}
} else {
setRunThrough(0);
}
if ( d->textRunAroundSide == side) {
return;
}
d->textRunAroundSide = side;
notifyChanged();
d->shapeChanged(TextRunAroundChanged);
}
qreal KoShape::textRunAroundDistanceTop() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceTop;
}
void KoShape::setTextRunAroundDistanceTop(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceTop = distance;
}
qreal KoShape::textRunAroundDistanceLeft() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceLeft;
}
void KoShape::setTextRunAroundDistanceLeft(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceLeft = distance;
}
qreal KoShape::textRunAroundDistanceRight() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceRight;
}
void KoShape::setTextRunAroundDistanceRight(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceRight = distance;
}
qreal KoShape::textRunAroundDistanceBottom() const
{
Q_D(const KoShape);
return d->textRunAroundDistanceBottom;
}
void KoShape::setTextRunAroundDistanceBottom(qreal distance)
{
Q_D(KoShape);
d->textRunAroundDistanceBottom = distance;
}
qreal KoShape::textRunAroundThreshold() const
{
Q_D(const KoShape);
return d->textRunAroundThreshold;
}
void KoShape::setTextRunAroundThreshold(qreal threshold)
{
Q_D(KoShape);
d->textRunAroundThreshold = threshold;
}
KoShape::TextRunAroundContour KoShape::textRunAroundContour() const
{
Q_D(const KoShape);
return d->textRunAroundContour;
}
void KoShape::setTextRunAroundContour(KoShape::TextRunAroundContour contour)
{
Q_D(KoShape);
d->textRunAroundContour = contour;
}
-void KoShape::setAnchor(KoShapeAnchor *anchor)
-{
- Q_D(KoShape);
- d->anchor = anchor;
-}
-
-KoShapeAnchor *KoShape::anchor() const
-{
- Q_D(const KoShape);
- return d->anchor;
-}
-
-void KoShape::setMinimumHeight(qreal height)
-{
- Q_D(KoShape);
- d->minimumHeight = height;
-}
-
-qreal KoShape::minimumHeight() const
-{
- Q_D(const KoShape);
- return d->minimumHeight;
-}
-
-
void KoShape::setBackground(QSharedPointer<KoShapeBackground> fill)
{
Q_D(KoShape);
d->fill = fill;
d->shapeChanged(BackgroundChanged);
notifyChanged();
}
QSharedPointer<KoShapeBackground> KoShape::background() const
{
Q_D(const KoShape);
return d->fill;
}
void KoShape::setZIndex(int zIndex)
{
Q_D(KoShape);
if (d->zIndex == zIndex)
return;
d->zIndex = zIndex;
notifyChanged();
}
int KoShape::runThrough()
{
Q_D(const KoShape);
return d->runThrough;
}
void KoShape::setRunThrough(short int runThrough)
{
Q_D(KoShape);
d->runThrough = runThrough;
}
void KoShape::setVisible(bool on)
{
Q_D(KoShape);
int _on = (on ? 1 : 0);
if (d->visible == _on) return;
d->visible = _on;
}
bool KoShape::isVisible(bool recursive) const
{
Q_D(const KoShape);
if (! recursive)
return d->visible;
if (recursive && ! d->visible)
return false;
KoShapeContainer * parentShape = parent();
while (parentShape) {
if (! parentShape->isVisible())
return false;
parentShape = parentShape->parent();
}
return true;
}
void KoShape::setPrintable(bool on)
{
Q_D(KoShape);
d->printable = on;
}
bool KoShape::isPrintable() const
{
Q_D(const KoShape);
if (d->visible)
return d->printable;
else
return false;
}
void KoShape::setSelectable(bool selectable)
{
Q_D(KoShape);
d->selectable = selectable;
}
bool KoShape::isSelectable() const
{
Q_D(const KoShape);
return d->selectable;
}
void KoShape::setGeometryProtected(bool on)
{
Q_D(KoShape);
d->geometryProtected = on;
}
bool KoShape::isGeometryProtected() const
{
Q_D(const KoShape);
return d->geometryProtected;
}
void KoShape::setContentProtected(bool protect)
{
Q_D(KoShape);
d->protectContent = protect;
}
bool KoShape::isContentProtected() const
{
Q_D(const KoShape);
return d->protectContent;
}
KoShapeContainer *KoShape::parent() const
{
Q_D(const KoShape);
return d->parent;
}
void KoShape::setKeepAspectRatio(bool keepAspect)
{
Q_D(KoShape);
d->keepAspect = keepAspect;
+
+ d->shapeChanged(KeepAspectRatioChange);
+ notifyChanged();
}
bool KoShape::keepAspectRatio() const
{
Q_D(const KoShape);
return d->keepAspect;
}
QString KoShape::shapeId() const
{
Q_D(const KoShape);
return d->shapeId;
}
void KoShape::setShapeId(const QString &id)
{
Q_D(KoShape);
d->shapeId = id;
}
void KoShape::setCollisionDetection(bool detect)
{
Q_D(KoShape);
d->detectCollision = detect;
}
bool KoShape::collisionDetection()
{
Q_D(KoShape);
return d->detectCollision;
}
-KoShapeStrokeModel *KoShape::stroke() const
+KoShapeStrokeModelSP KoShape::stroke() const
{
Q_D(const KoShape);
return d->stroke;
}
-void KoShape::setStroke(KoShapeStrokeModel *stroke)
+void KoShape::setStroke(KoShapeStrokeModelSP stroke)
{
Q_D(KoShape);
- if (stroke)
- stroke->ref();
+
+ // TODO: check if it really updates stuff
d->updateStroke();
- if (d->stroke)
- d->stroke->deref();
+
d->stroke = stroke;
d->updateStroke();
+
d->shapeChanged(StrokeChanged);
notifyChanged();
}
void KoShape::setShadow(KoShapeShadow *shadow)
{
Q_D(KoShape);
if (d->shadow)
d->shadow->deref();
d->shadow = shadow;
if (d->shadow) {
d->shadow->ref();
// TODO update changed area
}
d->shapeChanged(ShadowChanged);
notifyChanged();
}
KoShapeShadow *KoShape::shadow() const
{
Q_D(const KoShape);
return d->shadow;
}
void KoShape::setBorder(KoBorder *border)
{
Q_D(KoShape);
if (d->border) {
// The shape owns the border.
delete d->border;
}
d->border = border;
d->shapeChanged(BorderChanged);
notifyChanged();
}
KoBorder *KoShape::border() const
{
Q_D(const KoShape);
return d->border;
}
void KoShape::setClipPath(KoClipPath *clipPath)
{
Q_D(KoShape);
- d->clipPath = clipPath;
+ d->clipPath.reset(clipPath);
d->shapeChanged(ClipPathChanged);
notifyChanged();
}
KoClipPath * KoShape::clipPath() const
{
Q_D(const KoShape);
- return d->clipPath;
+ return d->clipPath.data();
+}
+
+void KoShape::setClipMask(KoClipMask *clipMask)
+{
+ Q_D(KoShape);
+ d->clipMask.reset(clipMask);
+}
+
+KoClipMask* KoShape::clipMask() const
+{
+ Q_D(const KoShape);
+ return d->clipMask.data();
}
QTransform KoShape::transform() const
{
Q_D(const KoShape);
return d->localMatrix;
}
QString KoShape::name() const
{
Q_D(const KoShape);
return d->name;
}
void KoShape::setName(const QString &name)
{
Q_D(KoShape);
d->name = name;
}
void KoShape::waitUntilReady(const KoViewConverter &converter, bool asynchronous) const
{
Q_UNUSED(converter);
Q_UNUSED(asynchronous);
}
bool KoShape::isEditable() const
{
Q_D(const KoShape);
if (!d->visible || d->geometryProtected)
return false;
if (d->parent && d->parent->isChildLocked(this))
return false;
return true;
}
// painting
void KoShape::paintBorder(QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(converter);
KoBorder *bd = border();
if (!bd) {
return;
}
QRectF borderRect = QRectF(QPointF(0, 0), size());
// Paint the border.
bd->paint(painter, borderRect, KoBorder::PaintInsideLine);
}
// loading & saving methods
QString KoShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
Q_D(const KoShape);
// and fill the style
- KoShapeStrokeModel *sm = stroke();
+ KoShapeStrokeModelSP sm = stroke();
if (sm) {
sm->fillStyle(style, context);
}
else {
style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
}
KoShapeShadow *s = shadow();
if (s)
s->fillStyle(style, context);
QSharedPointer<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) {
style.addProperty(it.key(), it.value());
}
if (parent() && parent()->isClipped(this)) {
/*
* In Calligra clipping is done using a parent shape which can be rotated, sheared etc
* and even non-square. So the ODF interoperability version we write here is really
* just a very simple version of that...
*/
qreal top = -position().y();
qreal left = -position().x();
qreal right = parent()->size().width() - size().width() - left;
qreal bottom = parent()->size().height() - size().height() - top;
style.addProperty("fo:clip", QString("rect(%1pt, %2pt, %3pt, %4pt)")
.arg(top, 10, 'f').arg(right, 10, 'f')
.arg(bottom, 10, 'f').arg(left, 10, 'f'), KoGenStyle::GraphicType);
}
QString wrap;
switch (textRunAroundSide()) {
case BiggestRunAroundSide:
wrap = "biggest";
break;
case LeftRunAroundSide:
wrap = "left";
break;
case RightRunAroundSide:
wrap = "right";
break;
case EnoughRunAroundSide:
wrap = "dynamic";
break;
case BothRunAroundSide:
wrap = "parallel";
break;
case NoRunAround:
wrap = "none";
break;
case RunThrough:
wrap = "run-through";
break;
}
style.addProperty("style:wrap", wrap, KoGenStyle::GraphicType);
switch (textRunAroundContour()) {
case ContourBox:
style.addProperty("style:wrap-contour", "false", KoGenStyle::GraphicType);
break;
case ContourFull:
style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType);
style.addProperty("style:wrap-contour-mode", "full", KoGenStyle::GraphicType);
break;
case ContourOutside:
style.addProperty("style:wrap-contour", "true", KoGenStyle::GraphicType);
style.addProperty("style:wrap-contour-mode", "outside", KoGenStyle::GraphicType);
break;
}
style.addPropertyPt("style:wrap-dynamic-threshold", textRunAroundThreshold(), KoGenStyle::GraphicType);
if ((textRunAroundDistanceLeft() == textRunAroundDistanceRight())
&& (textRunAroundDistanceTop() == textRunAroundDistanceBottom())
&& (textRunAroundDistanceLeft() == textRunAroundDistanceTop())) {
style.addPropertyPt("fo:margin", textRunAroundDistanceLeft(), KoGenStyle::GraphicType);
} else {
style.addPropertyPt("fo:margin-left", textRunAroundDistanceLeft(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-top", textRunAroundDistanceTop(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-right", textRunAroundDistanceRight(), KoGenStyle::GraphicType);
style.addPropertyPt("fo:margin-bottom", textRunAroundDistanceBottom(), KoGenStyle::GraphicType);
}
return context.mainStyles().insert(style, context.isSet(KoShapeSavingContext::PresentationShape) ? "pr" : "gr");
}
void KoShape::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_D(KoShape);
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.setTypeProperties("graphic");
d->fill.clear();
- if (d->stroke && !d->stroke->deref()) {
- delete d->stroke;
- d->stroke = 0;
- }
+ d->stroke.clear();
+
if (d->shadow && !d->shadow->deref()) {
delete d->shadow;
d->shadow = 0;
}
setBackground(loadOdfFill(context));
setStroke(loadOdfStroke(element, context));
setShadow(d->loadOdfShadow(context));
setBorder(d->loadOdfBorder(context));
QString protect(styleStack.property(KoXmlNS::style, "protect"));
setGeometryProtected(protect.contains("position") || protect.contains("size"));
setContentProtected(protect.contains("content"));
QString margin = styleStack.property(KoXmlNS::fo, "margin");
if (!margin.isEmpty()) {
setTextRunAroundDistanceLeft(KoUnit::parseValue(margin));
setTextRunAroundDistanceTop(KoUnit::parseValue(margin));
setTextRunAroundDistanceRight(KoUnit::parseValue(margin));
setTextRunAroundDistanceBottom(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-left");
if (!margin.isEmpty()) {
setTextRunAroundDistanceLeft(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-top");
if (!margin.isEmpty()) {
setTextRunAroundDistanceTop(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-right");
if (!margin.isEmpty()) {
setTextRunAroundDistanceRight(KoUnit::parseValue(margin));
}
margin = styleStack.property(KoXmlNS::fo, "margin-bottom");
if (!margin.isEmpty()) {
setTextRunAroundDistanceBottom(KoUnit::parseValue(margin));
}
QString wrap;
if (styleStack.hasProperty(KoXmlNS::style, "wrap")) {
wrap = styleStack.property(KoXmlNS::style, "wrap");
} else {
// no value given in the file, but guess biggest
wrap = "biggest";
}
if (wrap == "none") {
setTextRunAroundSide(KoShape::NoRunAround);
} else if (wrap == "run-through") {
QString runTrought = styleStack.property(KoXmlNS::style, "run-through", "background");
if (runTrought == "background") {
setTextRunAroundSide(KoShape::RunThrough, KoShape::Background);
} else {
setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground);
}
} else {
if (wrap == "biggest")
setTextRunAroundSide(KoShape::BiggestRunAroundSide);
else if (wrap == "left")
setTextRunAroundSide(KoShape::LeftRunAroundSide);
else if (wrap == "right")
setTextRunAroundSide(KoShape::RightRunAroundSide);
else if (wrap == "dynamic")
setTextRunAroundSide(KoShape::EnoughRunAroundSide);
else if (wrap == "parallel")
setTextRunAroundSide(KoShape::BothRunAroundSide);
}
if (styleStack.hasProperty(KoXmlNS::style, "wrap-dynamic-threshold")) {
QString wrapThreshold = styleStack.property(KoXmlNS::style, "wrap-dynamic-threshold");
if (!wrapThreshold.isEmpty()) {
setTextRunAroundThreshold(KoUnit::parseValue(wrapThreshold));
}
}
if (styleStack.property(KoXmlNS::style, "wrap-contour", "false") == "true") {
if (styleStack.property(KoXmlNS::style, "wrap-contour-mode", "full") == "full") {
setTextRunAroundContour(KoShape::ContourFull);
} else {
setTextRunAroundContour(KoShape::ContourOutside);
}
} else {
setTextRunAroundContour(KoShape::ContourBox);
}
}
bool KoShape::loadOdfAttributes(const KoXmlElement &element, KoShapeLoadingContext &context, int attributes)
{
if (attributes & OdfPosition) {
QPointF pos(position());
if (element.hasAttributeNS(KoXmlNS::svg, "x"))
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
if (element.hasAttributeNS(KoXmlNS::svg, "y"))
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
setPosition(pos);
}
if (attributes & OdfSize) {
QSizeF s(size());
if (element.hasAttributeNS(KoXmlNS::svg, "width"))
s.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
if (element.hasAttributeNS(KoXmlNS::svg, "height"))
s.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
setSize(s);
}
if (attributes & OdfLayer) {
if (element.hasAttributeNS(KoXmlNS::draw, "layer")) {
KoShapeLayer *layer = context.layer(element.attributeNS(KoXmlNS::draw, "layer"));
if (layer) {
setParent(layer);
}
}
}
if (attributes & OdfId) {
KoElementReference ref;
ref.loadOdf(element);
if (ref.isValid()) {
context.addShapeId(this, ref.toString());
}
}
if (attributes & OdfZIndex) {
if (element.hasAttributeNS(KoXmlNS::draw, "z-index")) {
setZIndex(element.attributeNS(KoXmlNS::draw, "z-index").toInt());
} else {
setZIndex(context.zIndex());
}
}
if (attributes & OdfName) {
if (element.hasAttributeNS(KoXmlNS::draw, "name")) {
setName(element.attributeNS(KoXmlNS::draw, "name"));
}
}
if (attributes & OdfStyle) {
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.save();
if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::draw, "style-name", "graphic");
}
if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) {
context.odfLoadingContext().fillStyleStack(element, KoXmlNS::presentation, "style-name", "presentation");
}
loadStyle(element, context);
styleStack.restore();
}
if (attributes & OdfTransformation) {
QString transform = element.attributeNS(KoXmlNS::draw, "transform", QString());
if (! transform.isEmpty())
applyAbsoluteTransformation(parseOdfTransform(transform));
}
if (attributes & OdfAdditionalAttributes) {
QSet<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 = KoShapePrivate::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 = KoShapePrivate::getStyleProperty("fill-gradient-name", context);
KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient")[styleName];
QString style;
if (e) {
style = e->attributeNS(KoXmlNS::draw, "style", QString());
}
if ((style == "rectangular") || (style == "square")) {
bg = QSharedPointer<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;
}
-KoShapeStrokeModel *KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const
+KoShapeStrokeModelSP KoShape::loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
QString stroke = KoShapePrivate::getStyleProperty("stroke", context);
if (stroke == "solid" || stroke == "dash") {
QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader);
- KoShapeStroke *stroke = new KoShapeStroke();
+ 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)) {
- KoShapeStroke *stroke = new KoShapeStroke();
+ 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 0;
+ return KoShapeStrokeModelSP();
}
KoShapeShadow *KoShapePrivate::loadOdfShadow(KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
QString shadowStyle = KoShapePrivate::getStyleProperty("shadow", context);
if (shadowStyle == "visible" || shadowStyle == "hidden") {
KoShapeShadow *shadow = new KoShapeShadow();
QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color"));
qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x"));
qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y"));
shadow->setOffset(QPointF(offsetX, offsetY));
qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius"));
shadow->setBlur(blur);
QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity");
if (! opacity.isEmpty() && opacity.right(1) == "%")
shadowColor.setAlphaF(opacity.left(opacity.length() - 1).toFloat() / 100.0);
shadow->setColor(shadowColor);
shadow->setVisible(shadowStyle == "visible");
return shadow;
}
return 0;
}
KoBorder *KoShapePrivate::loadOdfBorder(KoShapeLoadingContext &context) const
{
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
KoBorder *border = new KoBorder();
if (border->loadOdf(styleStack)) {
return border;
}
delete border;
return 0;
}
void KoShape::loadOdfGluePoints(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_D(KoShape);
KoXmlElement child;
bool hasCenterGluePoint = false;
forEachElement(child, element) {
if (child.namespaceURI() != KoXmlNS::draw)
continue;
if (child.localName() != "glue-point")
continue;
// NOTE: this uses draw:id, but apparently while ODF 1.2 has deprecated
// all use of draw:id for xml:id, it didn't specify that here, so it
// doesn't support xml:id (and so, maybe, shouldn't use KoElementReference.
const QString id = child.attributeNS(KoXmlNS::draw, "id", QString());
const int index = id.toInt();
// connection point in center should be default but odf doesn't support,
// in new shape, first custom point is in center, it's okay to replace that point
// with point from xml now, we'll add it back later
if(id.isEmpty() || index < KoConnectionPoint::FirstCustomConnectionPoint ||
(index != KoConnectionPoint::FirstCustomConnectionPoint && d->connectors.contains(index))) {
warnFlake << "glue-point with no or invalid id";
continue;
}
QString xStr = child.attributeNS(KoXmlNS::svg, "x", QString()).simplified();
QString yStr = child.attributeNS(KoXmlNS::svg, "y", QString()).simplified();
if(xStr.isEmpty() || yStr.isEmpty()) {
warnFlake << "glue-point with invald position";
continue;
}
KoConnectionPoint connector;
const QString align = child.attributeNS(KoXmlNS::draw, "align", QString());
if (align.isEmpty()) {
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixGluePointPosition(xStr, context);
KoOdfWorkaround::fixGluePointPosition(yStr, context);
#endif
if(!xStr.endsWith('%') || !yStr.endsWith('%')) {
warnFlake << "glue-point with invald position";
continue;
}
// x and y are relative to drawing object center
connector.position.setX(xStr.remove('%').toDouble()/100.0);
connector.position.setY(yStr.remove('%').toDouble()/100.0);
// convert position to be relative to top-left corner
connector.position += QPointF(0.5, 0.5);
connector.position.rx() = qBound<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;
debugFlake << "loaded glue-point" << index << "at position" << connector.position;
if (d->connectors[index].position == QPointF(0.5, 0.5)) {
hasCenterGluePoint = true;
debugFlake << "center glue-point found at id " << index;
}
}
if (!hasCenterGluePoint) {
d->connectors[d->connectors.count()] = KoConnectionPoint(QPointF(0.5, 0.5),
KoConnectionPoint::AllDirections, KoConnectionPoint::AlignCenter);
}
debugFlake << "shape has now" << d->connectors.count() << "glue-points";
}
void KoShape::loadOdfClipContour(const KoXmlElement &element, KoShapeLoadingContext &context, const QSizeF &scaleFactor)
{
Q_D(KoShape);
KoXmlElement child;
forEachElement(child, element) {
if (child.namespaceURI() != KoXmlNS::draw)
continue;
if (child.localName() != "contour-polygon")
continue;
debugFlake << "shape loads contour-polygon";
KoPathShape *ps = new KoPathShape();
ps->loadContourOdf(child, context, scaleFactor);
ps->setTransformation(transformation());
- KoClipData *cd = new KoClipData(ps);
- KoClipPath *clipPath = new KoClipPath(this, cd);
- d->clipPath = clipPath;
+ KoClipPath *clipPath = new KoClipPath({ps}, KoFlake::UserSpaceOnUse);
+ d->clipPath.reset(clipPath);
}
}
QTransform KoShape::parseOdfTransform(const QString &transform)
{
QTransform matrix;
// Split string for handling 1 transform statement at a time
QStringList subtransforms = transform.split(')', QString::SkipEmptyParts);
QStringList::ConstIterator it = subtransforms.constBegin();
QStringList::ConstIterator end = subtransforms.constEnd();
for (; it != end; ++it) {
QStringList subtransform = (*it).split('(', QString::SkipEmptyParts);
subtransform[0] = subtransform[0].trimmed().toLower();
subtransform[1] = subtransform[1].simplified();
QRegExp reg("[,( ]");
QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts);
if (subtransform[0].startsWith(';') || subtransform[0].startsWith(','))
subtransform[0] = subtransform[0].right(subtransform[0].length() - 1);
QString cmd = subtransform[0].toLower();
if (cmd == "rotate") {
QTransform rotMatrix;
if (params.count() == 3) {
qreal x = KoUnit::parseValue(params[1]);
qreal y = KoUnit::parseValue(params[2]);
rotMatrix.translate(x, y);
// oo2 rotates by radians
rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI);
rotMatrix.translate(-x, -y);
} else {
// oo2 rotates by radians
rotMatrix.rotate(-params[0].toDouble()*180.0 / M_PI);
}
matrix = matrix * rotMatrix;
} else if (cmd == "translate") {
QTransform moveMatrix;
if (params.count() == 2) {
qreal x = KoUnit::parseValue(params[0]);
qreal y = KoUnit::parseValue(params[1]);
moveMatrix.translate(x, y);
} else // Spec : if only one param given, assume 2nd param to be 0
moveMatrix.translate(KoUnit::parseValue(params[0]) , 0);
matrix = matrix * moveMatrix;
} else if (cmd == "scale") {
QTransform scaleMatrix;
if (params.count() == 2)
scaleMatrix.scale(params[0].toDouble(), params[1].toDouble());
else // Spec : if only one param given, assume uniform scaling
scaleMatrix.scale(params[0].toDouble(), params[0].toDouble());
matrix = matrix * scaleMatrix;
} else if (cmd == "skewx") {
- QPointF p = absolutePosition(KoFlake::TopLeftCorner);
+ 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::TopLeftCorner);
+ QPointF p = absolutePosition(KoFlake::TopLeft);
QTransform shearMatrix;
shearMatrix.translate(p.x(), p.y());
shearMatrix.shear(0.0F, tan(-params[0].toDouble()));
shearMatrix.translate(-p.x(), -p.y());
matrix = matrix * shearMatrix;
} else if (cmd == "matrix") {
QTransform m;
if (params.count() >= 6) {
m.setMatrix(params[0].toDouble(), params[1].toDouble(), 0,
params[2].toDouble(), params[3].toDouble(), 0,
KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1);
}
matrix = matrix * m;
}
}
return matrix;
}
void KoShape::saveOdfAttributes(KoShapeSavingContext &context, int attributes) const
{
Q_D(const KoShape);
if (attributes & OdfStyle) {
KoGenStyle style;
// all items that should be written to 'draw:frame' and any other 'draw:' object that inherits this shape
if (context.isSet(KoShapeSavingContext::PresentationShape)) {
style = KoGenStyle(KoGenStyle::PresentationAutoStyle, "presentation");
context.xmlWriter().addAttribute("presentation:style-name", saveStyle(style, context));
} else {
style = KoGenStyle(KoGenStyle::GraphicAutoStyle, "graphic");
context.xmlWriter().addAttribute("draw:style-name", saveStyle(style, context));
}
}
if (attributes & OdfId) {
if (context.isSet(KoShapeSavingContext::DrawId)) {
KoElementReference ref = context.xmlid(this, "shape", KoElementReference::Counter);
ref.saveOdf(&context.xmlWriter(), KoElementReference::DrawId);
}
}
if (attributes & OdfName) {
if (! name().isEmpty())
context.xmlWriter().addAttribute("draw:name", name());
}
if (attributes & OdfLayer) {
KoShape *parent = d->parent;
while (parent) {
if (dynamic_cast<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().addAttributePt("svg:width", s.width());
context.xmlWriter().addAttributePt("svg:height", s.height());
}
// The position is implicitly stored in the transformation matrix
// if the transformation is saved as well
if ((attributes & OdfPosition) && !(attributes & OdfTransformation)) {
const QPointF p(position() * context.shapeOffset(this));
context.xmlWriter().addAttributePt("svg:x", p.x());
context.xmlWriter().addAttributePt("svg:y", p.y());
}
if (attributes & OdfTransformation) {
QTransform matrix = absoluteTransformation(0) * context.shapeOffset(this);
if (! matrix.isIdentity()) {
if (qAbs(matrix.m11() - 1) < 1E-5 // 1
&& qAbs(matrix.m12()) < 1E-5 // 0
&& qAbs(matrix.m21()) < 1E-5 // 0
&& qAbs(matrix.m22() - 1) < 1E-5) { // 1
context.xmlWriter().addAttributePt("svg:x", matrix.dx());
context.xmlWriter().addAttributePt("svg:y", matrix.dy());
} else {
QString m = QString("matrix(%1 %2 %3 %4 %5pt %6pt)")
.arg(matrix.m11(), 0, 'f', 11)
.arg(matrix.m12(), 0, 'f', 11)
.arg(matrix.m21(), 0, 'f', 11)
.arg(matrix.m22(), 0, 'f', 11)
.arg(matrix.dx(), 0, 'f', 11)
.arg(matrix.dy(), 0, 'f', 11);
context.xmlWriter().addAttribute("draw:transform", m);
}
}
}
if (attributes & OdfViewbox) {
const QSizeF s(size());
QString viewBox = QString("0 0 %1 %2").arg(qRound(s.width())).arg(qRound(s.height()));
context.xmlWriter().addAttribute("svg:viewBox", viewBox);
}
if (attributes & OdfAdditionalAttributes) {
QMap<QString, QString>::const_iterator it(d->additionalAttributes.constBegin());
for (; it != d->additionalAttributes.constEnd(); ++it) {
context.xmlWriter().addAttribute(it.key().toUtf8(), it.value());
}
}
}
void KoShape::saveOdfCommonChildElements(KoShapeSavingContext &context) const
{
Q_D(const KoShape);
// save glue points see ODF 9.2.19 Glue Points
if(d->connectors.count()) {
KoConnectionPoints::const_iterator cp = d->connectors.constBegin();
KoConnectionPoints::const_iterator lastCp = d->connectors.constEnd();
for(; cp != lastCp; ++cp) {
// do not save default glue points
if(cp.key() < 4)
continue;
context.xmlWriter().startElement("draw:glue-point");
context.xmlWriter().addAttribute("draw:id", QString("%1").arg(cp.key()));
if (cp.value().alignment == KoConnectionPoint::AlignNone) {
// convert to percent from center
const qreal x = cp.value().position.x() * 100.0 - 50.0;
const qreal y = cp.value().position.y() * 100.0 - 50.0;
context.xmlWriter().addAttribute("svg:x", QString("%1%").arg(x));
context.xmlWriter().addAttribute("svg:y", QString("%1%").arg(y));
} else {
context.xmlWriter().addAttributePt("svg:x", cp.value().position.x());
context.xmlWriter().addAttributePt("svg:y", cp.value().position.y());
}
QString escapeDirection;
switch(cp.value().escapeDirection) {
case KoConnectionPoint::HorizontalDirections:
escapeDirection = "horizontal";
break;
case KoConnectionPoint::VerticalDirections:
escapeDirection = "vertical";
break;
case KoConnectionPoint::LeftDirection:
escapeDirection = "left";
break;
case KoConnectionPoint::RightDirection:
escapeDirection = "right";
break;
case KoConnectionPoint::UpDirection:
escapeDirection = "up";
break;
case KoConnectionPoint::DownDirection:
escapeDirection = "down";
break;
default:
// fall through
break;
}
if(!escapeDirection.isEmpty()) {
context.xmlWriter().addAttribute("draw:escape-direction", escapeDirection);
}
QString alignment;
switch(cp.value().alignment) {
case KoConnectionPoint::AlignTopLeft:
alignment = "top-left";
break;
case KoConnectionPoint::AlignTop:
alignment = "top";
break;
case KoConnectionPoint::AlignTopRight:
alignment = "top-right";
break;
case KoConnectionPoint::AlignLeft:
alignment = "left";
break;
case KoConnectionPoint::AlignCenter:
alignment = "center";
break;
case KoConnectionPoint::AlignRight:
alignment = "right";
break;
case KoConnectionPoint::AlignBottomLeft:
alignment = "bottom-left";
break;
case KoConnectionPoint::AlignBottom:
alignment = "bottom";
break;
case KoConnectionPoint::AlignBottomRight:
alignment = "bottom-right";
break;
default:
// fall through
break;
}
if(!alignment.isEmpty()) {
context.xmlWriter().addAttribute("draw:align", alignment);
}
context.xmlWriter().endElement();
}
}
}
void KoShape::saveOdfClipContour(KoShapeSavingContext &context, const QSizeF &originalSize) const
{
Q_D(const KoShape);
debugFlake << "shape saves contour-polygon";
if (d->clipPath && !d->clipPath->clipPathShapes().isEmpty()) {
// This will loose data as odf can only save one set of contour wheras
// svg loading and at least karbon editing can produce more than one
// TODO, FIXME see if we can save more than one clipshape to odf
d->clipPath->clipPathShapes().first()->saveContourOdf(context, originalSize);
}
}
// end loading & saving methods
// static
void KoShape::applyConversion(QPainter &painter, const KoViewConverter &converter)
{
qreal zoomX, zoomY;
converter.zoom(&zoomX, &zoomY);
painter.scale(zoomX, zoomY);
}
+KisHandlePainterHelper KoShape::createHandlePainterHelper(QPainter *painter, KoShape *shape, const KoViewConverter &converter, qreal handleRadius)
+{
+ const QTransform originalPainterTransform = painter->transform();
+
+ painter->setTransform(shape->absoluteTransformation(&converter) * painter->transform());
+ KoShape::applyConversion(*painter, converter);
+
+ // move c-tor
+ return KisHandlePainterHelper(painter, originalPainterTransform, handleRadius);
+}
+
QPointF KoShape::shapeToDocument(const QPointF &point) const
{
return absoluteTransformation(0).map(point);
}
QRectF KoShape::shapeToDocument(const QRectF &rect) const
{
return absoluteTransformation(0).mapRect(rect);
}
QPointF KoShape::documentToShape(const QPointF &point) const
{
return absoluteTransformation(0).inverted().map(point);
}
QRectF KoShape::documentToShape(const QRectF &rect) const
{
return absoluteTransformation(0).inverted().mapRect(rect);
}
bool KoShape::addDependee(KoShape *shape)
{
Q_D(KoShape);
if (! shape)
return false;
// refuse to establish a circular dependency
if (shape->hasDependee(this))
return false;
if (! d->dependees.contains(shape))
d->dependees.append(shape);
return true;
}
void KoShape::removeDependee(KoShape *shape)
{
Q_D(KoShape);
int index = d->dependees.indexOf(shape);
if (index >= 0)
d->dependees.removeAt(index);
}
bool KoShape::hasDependee(KoShape *shape) const
{
Q_D(const KoShape);
return d->dependees.contains(shape);
}
QList<KoShape*> KoShape::dependees() const
{
Q_D(const KoShape);
return d->dependees;
}
void KoShape::shapeChanged(ChangeType type, KoShape *shape)
{
Q_UNUSED(type);
Q_UNUSED(shape);
}
KoSnapData KoShape::snapData() const
{
return KoSnapData();
}
void KoShape::setAdditionalAttribute(const QString &name, const QString &value)
{
Q_D(KoShape);
d->additionalAttributes.insert(name, value);
}
void KoShape::removeAdditionalAttribute(const QString &name)
{
Q_D(KoShape);
d->additionalAttributes.remove(name);
}
bool KoShape::hasAdditionalAttribute(const QString &name) const
{
Q_D(const KoShape);
return d->additionalAttributes.contains(name);
}
QString KoShape::additionalAttribute(const QString &name) const
{
Q_D(const KoShape);
return d->additionalAttributes.value(name);
}
void KoShape::setAdditionalStyleAttribute(const char *name, const QString &value)
{
Q_D(KoShape);
d->additionalStyleAttributes.insert(name, value);
}
void KoShape::removeAdditionalStyleAttribute(const char *name)
{
Q_D(KoShape);
d->additionalStyleAttributes.remove(name);
}
KoFilterEffectStack *KoShape::filterEffectStack() const
{
Q_D(const KoShape);
return d->filterEffectStack;
}
void KoShape::setFilterEffectStack(KoFilterEffectStack *filterEffectStack)
{
Q_D(KoShape);
if (d->filterEffectStack)
d->filterEffectStack->deref();
d->filterEffectStack = filterEffectStack;
if (d->filterEffectStack) {
d->filterEffectStack->ref();
}
notifyChanged();
}
QSet<KoShape*> KoShape::toolDelegates() const
{
Q_D(const KoShape);
return d->toolDelegates;
}
void KoShape::setToolDelegates(const QSet<KoShape*> &delegates)
{
Q_D(KoShape);
d->toolDelegates = delegates;
}
QString KoShape::hyperLink () const
{
Q_D(const KoShape);
return d->hyperLink;
}
void KoShape::setHyperLink(const QString &hyperLink)
{
Q_D(KoShape);
d->hyperLink = hyperLink;
}
KoShapePrivate *KoShape::priv()
{
Q_D(KoShape);
return d;
}
+
+KoShape::ShapeChangeListener::~ShapeChangeListener()
+{
+ Q_FOREACH(KoShape *shape, m_registeredShapes) {
+ shape->removeShapeChangeListener(this);
+ }
+}
+
+void KoShape::ShapeChangeListener::registerShape(KoShape *shape)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!m_registeredShapes.contains(shape));
+ m_registeredShapes.append(shape);
+}
+
+void KoShape::ShapeChangeListener::unregisterShape(KoShape *shape)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape));
+ m_registeredShapes.removeAll(shape);
+}
+
+void KoShape::ShapeChangeListener::notifyShapeChangedImpl(KoShape::ChangeType type, KoShape *shape)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_registeredShapes.contains(shape));
+
+ notifyShapeChanged(type, shape);
+
+ if (type == KoShape::Deleted) {
+ unregisterShape(shape);
+ }
+}
+
+void KoShape::addShapeChangeListener(KoShape::ShapeChangeListener *listener)
+{
+ Q_D(KoShape);
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!d->listeners.contains(listener));
+ listener->registerShape(this);
+ d->listeners.append(listener);
+}
+
+void KoShape::removeShapeChangeListener(KoShape::ShapeChangeListener *listener)
+{
+ Q_D(KoShape);
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(d->listeners.contains(listener));
+ d->listeners.removeAll(listener);
+ listener->unregisterShape(this);
+}
+
+QList<KoShape *> 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;
+}
diff --git a/libs/flake/KoShape.h b/libs/flake/KoShape.h
index ca0af8470d..16906e1de9 100644
--- a/libs/flake/KoShape.h
+++ b/libs/flake/KoShape.h
@@ -1,1189 +1,1251 @@
/* 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 <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 KoShapePrivate;
class KoFilterEffectStack;
class KoSnapData;
class KoClipPath;
+class KoClipMask;
class KoShapePaintingContext;
class KoShapeAnchor;
class KoBorder;
struct KoInsets;
class KoShapeBackground;
+class KisHandlePainterHelper;
/**
*
* 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
+ ClipPathChanged, ///< 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 thie shape or shapes subtree
+ * @return a cloned shape
+ */
+ virtual KoShape* cloneShape() const;
+
/**
* @brief Paint the shape
* 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 equavalent 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.
* @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;
/**
* @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);
/**
* 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;
+
+ /**
+ * 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);
+
/**
* @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 runThrought 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 minimumShapeHeight 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.
* @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;
/**
* 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()
*/
int 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(int zIndex);
/**
* 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();
/**
* 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 = false) 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;
+
/**
* 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 points (the internal coordinates system of KoShape) 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 update(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.
*/
- KoShapeStrokeModel *stroke() const;
+ KoShapeStrokeModelSP stroke() const;
/**
* Set a new stroke, removing the old one.
* @param stroke the new stroke, or 0 if there should be no stroke.
*/
- void setStroke(KoShapeStrokeModel *stroke);
+ void setStroke(KoShapeStrokeModelSP stroke);
/**
* 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/hight 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/hight 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::Position anchor = KoFlake::CenteredPosition) const;
+ 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::Position anchor = KoFlake::CenteredPosition);
+ 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;
- /**
- * Set a data object on the shape to be used by an application.
- * This is specifically useful when an application wants to have data that is per shape
- * and should be deleted when the shape is destructed.
- * @param applicationData the new application data, or 0 to delete the current one.
- */
- void setApplicationData(KoShapeApplicationData *applicationData);
-
- /**
- * Return the current applicationData.
- */
- KoShapeApplicationData *applicationData() 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;
/**
* 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);
+
/**
* @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 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 finised.
* 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;
/// checks recursively if the shape or one of its parents is not visible or locked
bool isEditable() 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);
/**
* \internal
* Returns the private object for use within the flake lib
*/
KoShapePrivate *priv();
+public:
+
+ struct ShapeChangeListener {
+ virtual ~ShapeChangeListener();
+ virtual void notifyShapeChanged(ChangeType type, KoShape *shape) = 0;
+
+ private:
+ friend class KoShape;
+ friend class KoShapePrivate;
+ 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);
+
+public:
+ static QList<KoShape*> linearizeSubtree(const QList<KoShape*> &shapes);
+
protected:
/// constructor
- KoShape(KoShapePrivate &);
+ KoShape(KoShapePrivate *);
/* ** 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
- KoShapeStrokeModel *loadOdfStroke(const KoXmlElement &element, KoShapeLoadingContext &context) const;
+ 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.
*/
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
/// return the current matrix that contains the rotation/scale/position of this shape
QTransform transform() const;
KoShapePrivate *d_ptr;
private:
Q_DECLARE_PRIVATE(KoShape)
};
Q_DECLARE_METATYPE(KoShape*)
#endif
diff --git a/libs/flake/KoShapeBackground.h b/libs/flake/KoShapeBackground.h
index ca601930d8..91a3d45d46 100644
--- a/libs/flake/KoShapeBackground.h
+++ b/libs/flake/KoShapeBackground.h
@@ -1,73 +1,75 @@
/* 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 KoShapeBackgroundPrivate;
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;
/// 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;
protected:
KoShapeBackground(KoShapeBackgroundPrivate &);
KoShapeBackgroundPrivate *d_ptr;
private:
Q_DECLARE_PRIVATE(KoShapeBackground)
};
#endif // KOSHAPEBACKGROUND_H
diff --git a/libs/flake/KoShapeBasedDocumentBase.cpp b/libs/flake/KoShapeBasedDocumentBase.cpp
index 6883607383..ad5eab41d2 100644
--- a/libs/flake/KoShapeBasedDocumentBase.cpp
+++ b/libs/flake/KoShapeBasedDocumentBase.cpp
@@ -1,84 +1,92 @@
/* This file is part of the KDE project
Copyright (C) 2006, 2010 Thomas Zander <zander@kde.org>
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 <QTransform>
#include <QPointer>
#include "KoShapeBasedDocumentBase.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeRegistry.h"
#include "KoShapeFactoryBase.h"
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
class KoShapeBasedDocumentBasePrivate
{
public:
KoShapeBasedDocumentBasePrivate()
: resourceManager(new KoDocumentResourceManager())
{
KoShapeRegistry *registry = KoShapeRegistry::instance();
foreach (const QString &id, registry->keys()) {
KoShapeFactoryBase *shapeFactory = registry->value(id);
shapeFactory->newDocumentResourceManager(resourceManager);
}
// read persistent application wide resources
KSharedConfigPtr config = KSharedConfig::openConfig();
- if (config->hasGroup("Misc")) {
- KConfigGroup miscGroup = config->group("Misc");
- const qreal pasteOffset = miscGroup.readEntry("CopyOffset", 10.0);
- resourceManager->setPasteOffset(pasteOffset);
- const bool pasteAtCursor = miscGroup.readEntry("PasteAtCursor", true);
- resourceManager->enablePasteAtCursor(pasteAtCursor);
- const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3);
- resourceManager->setGrabSensitivity(grabSensitivity);
- const uint handleRadius = miscGroup.readEntry("HandleRadius", 3);
- resourceManager->setHandleRadius(handleRadius);
- }
+ KConfigGroup miscGroup = config->group("Misc");
+ const uint grabSensitivity = miscGroup.readEntry("GrabSensitivity", 3);
+ resourceManager->setGrabSensitivity(grabSensitivity);
+ const uint handleRadius = miscGroup.readEntry("HandleRadius", 3);
+ resourceManager->setHandleRadius(handleRadius);
}
~KoShapeBasedDocumentBasePrivate()
{
delete resourceManager;
}
QPointer<KoDocumentResourceManager> resourceManager;
};
KoShapeBasedDocumentBase::KoShapeBasedDocumentBase()
: d(new KoShapeBasedDocumentBasePrivate())
{
}
KoShapeBasedDocumentBase::~KoShapeBasedDocumentBase()
{
delete d;
}
+void KoShapeBasedDocumentBase::addShape(KoShape *shape)
+{
+ addShapes({shape});
+}
+
void KoShapeBasedDocumentBase::shapesRemoved(const QList<KoShape*> & /*shapes*/, KUndo2Command * /*command*/)
{
}
KoDocumentResourceManager *KoShapeBasedDocumentBase::resourceManager() const
{
return d->resourceManager;
}
+
+QRectF KoShapeBasedDocumentBase::documentRect() const
+{
+ const qreal pxToPt = 72.0 / pixelsPerInch();
+
+ QTransform t = QTransform::fromScale(pxToPt, pxToPt);
+ return t.mapRect(documentRectInPixels());
+}
diff --git a/libs/flake/KoShapeBasedDocumentBase.h b/libs/flake/KoShapeBasedDocumentBase.h
index b2f3565b03..527ad3392c 100644
--- a/libs/flake/KoShapeBasedDocumentBase.h
+++ b/libs/flake/KoShapeBasedDocumentBase.h
@@ -1,85 +1,110 @@
/* This file is part of the KDE project
Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006, 2010 Thomas Zander <zander@kde.org>
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.
*/
#ifndef KOSHAPEBASEDDOCUMENTBASE_H
#define KOSHAPEBASEDDOCUMENTBASE_H
#include "kritaflake_export.h"
#include <QList>
+class QRectF;
class KoShape;
class KoShapeBasedDocumentBasePrivate;
class KoDocumentResourceManager;
class KUndo2Command;
/**
* The KoShapeBasedDocumentBase is an abstract interface that the application's class
* that owns the shapes should implement. This tends to be the document.
* @see KoShapeDeleteCommand, KoShapeCreateCommand
*/
class KRITAFLAKE_EXPORT KoShapeBasedDocumentBase
{
public:
KoShapeBasedDocumentBase();
virtual ~KoShapeBasedDocumentBase();
/**
* Add a shape to the shape controller, allowing it to be seen and saved.
* The controller should add the shape to the ShapeManager instance(s) manually
* if the shape is one that should be currently shown on screen.
* @param shape the new shape
*/
- virtual void addShape(KoShape *shape) = 0;
+ void addShape(KoShape *shape);
+
+ /**
+ * Add shapes to the shape controller, allowing it to be seen and saved.
+ * The controller should add the shape to the ShapeManager instance(s) manually
+ * if the shape is one that should be currently shown on screen.
+ * @param shape the new shape
+ */
+ virtual void addShapes(const QList<KoShape*> shapes) = 0;
/**
* Remove a shape from the shape controllers control, allowing it to be deleted shortly after
* The controller should remove the shape from all the ShapeManager instance(s) manually
* @param shape the shape to remove
*/
virtual void removeShape(KoShape *shape) = 0;
/**
* This method gets called after the KoShapeDeleteCommand is executed
*
* This passes the KoShapeDeleteCommand as the command parameter. This makes it possible
* for applications that need to do something after the KoShapeDeleteCommand is done, e.g.
* adding one commands that need to be executed when a shape was deleted.
* The default implementation is empty.
* @param shapes The list of shapes that got removed.
* @param command The command that was used to remove the shapes from the document.
*/
virtual void shapesRemoved(const QList<KoShape*> &shapes, KUndo2Command *command);
/**
* Return a pointer to the resource manager associated with the
* shape-set (typically a document). The resource manager contains
* document wide resources * such as variable managers, the image
* collection and others.
*/
virtual KoDocumentResourceManager *resourceManager() const;
+ /**
+ * The size of the document measured in rasterized pixels. This information is needed for loading
+ * SVG documents that use 'px' as the default unit.
+ */
+ virtual QRectF documentRectInPixels() const = 0;
+
+ /**
+ * The size of the document measured in 'pt'
+ */
+ QRectF documentRect() const;
+
+ /**
+ * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly.
+ */
+ virtual qreal pixelsPerInch() const = 0;
+
private:
KoShapeBasedDocumentBasePrivate * const d;
};
#endif
diff --git a/libs/flake/KoShapeContainer.cpp b/libs/flake/KoShapeContainer.cpp
index 411098c702..cc4c75fbdb 100644
--- a/libs/flake/KoShapeContainer.cpp
+++ b/libs/flake/KoShapeContainer.cpp
@@ -1,256 +1,240 @@
/* 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 "KoShapeContainerDefaultModel.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"
KoShapeContainerPrivate::KoShapeContainerPrivate(KoShapeContainer *q)
: KoShapePrivate(q),
- model(0)
+ shapeInterface(q),
+ model(0)
{
}
KoShapeContainerPrivate::~KoShapeContainerPrivate()
{
delete model;
}
+KoShapeContainerPrivate::KoShapeContainerPrivate(const KoShapeContainerPrivate &rhs, KoShapeContainer *q)
+ : KoShapePrivate(rhs, q),
+ shapeInterface(q),
+ model(0)
+{
+}
+
KoShapeContainer::KoShapeContainer(KoShapeContainerModel *model)
- : KoShape(*(new KoShapeContainerPrivate(this)))
+ : KoShape(new KoShapeContainerPrivate(this))
{
Q_D(KoShapeContainer);
d->model = model;
}
-KoShapeContainer::KoShapeContainer(KoShapeContainerPrivate &dd)
+KoShapeContainer::KoShapeContainer(KoShapeContainerPrivate *dd)
: KoShape(dd)
{
+ Q_D(KoShapeContainer);
+
+ // 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()) {
+ shape->setParent(this);
+ }
+ }
}
KoShapeContainer::~KoShapeContainer()
{
Q_D(KoShapeContainer);
if (d->model) {
- Q_FOREACH (KoShape *shape, d->model->shapes()) {
- delete shape;
- }
+ d->model->deleteOwnedShapes();
}
}
void KoShapeContainer::addShape(KoShape *shape)
{
- Q_D(KoShapeContainer);
- Q_ASSERT(shape);
- if (shape->parent() == this && shapes().contains(shape))
- return;
- // TODO add a method to create a default model depending on the shape container
- if (d->model == 0)
- d->model = new KoShapeContainerDefaultModel();
- if (shape->parent() && shape->parent() != this)
- shape->parent()->removeShape(shape);
- d->model->add(shape);
shape->setParent(this);
}
void KoShapeContainer::removeShape(KoShape *shape)
{
- Q_D(KoShapeContainer);
- Q_ASSERT(shape);
- if (d->model == 0)
- return;
- d->model->remove(shape);
shape->setParent(0);
-
- KoShapeContainer * grandparent = parent();
- if (grandparent) {
- grandparent->model()->childChanged(this, KoShape::ChildChanged);
- }
-}
-
-void KoShapeContainer::removeAllShapes()
-{
- Q_D(KoShapeContainer);
- if (d->model == 0)
- return;
- for(int i = d->model->shapes().count() - 1; i >= 0; --i) {
- KoShape *shape = d->model->shapes()[i];
- d->model->remove(shape);
- shape->setParent(0);
- }
-
- KoShapeContainer * grandparent = parent();
- if (grandparent) {
- grandparent->model()->childChanged(this, KoShape::ChildChanged);
- }
}
int KoShapeContainer::shapeCount() const
{
Q_D(const KoShapeContainer);
if (d->model == 0)
return 0;
return d->model->count();
}
bool KoShapeContainer::isChildLocked(const KoShape *child) const
{
Q_D(const KoShapeContainer);
if (d->model == 0)
return false;
return d->model->isChildLocked(child);
}
void KoShapeContainer::setClipped(const KoShape *child, bool clipping)
{
Q_D(KoShapeContainer);
if (d->model == 0)
return;
d->model->setClipped(child, clipping);
}
void KoShapeContainer::setInheritsTransform(const KoShape *shape, bool inherit)
{
Q_D(KoShapeContainer);
if (d->model == 0)
return;
d->model->setInheritsTransform(shape, inherit);
}
bool KoShapeContainer::inheritsTransform(const KoShape *shape) const
{
Q_D(const KoShapeContainer);
if (d->model == 0)
return false;
return d->model->inheritsTransform(shape);
}
void KoShapeContainer::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext)
{
+ // Shape container paints only its internal component part. All the children are rendered
+ // by the shape manager itself
+
Q_D(KoShapeContainer);
painter.save();
paintComponent(painter, converter, paintcontext);
painter.restore();
- if (d->model == 0 || d->model->count() == 0)
- return;
-
- QList<KoShape*> sortedObjects = d->model->shapes();
- qSort(sortedObjects.begin(), sortedObjects.end(), KoShape::compareShapeZIndex);
-
- // Do the following to revert the absolute transformation of the container
- // that is re-applied in shape->absoluteTransformation() later on. The transformation matrix
- // of the container has already been applied once before this function is called.
- QTransform baseMatrix = absoluteTransformation(&converter).inverted() * painter.transform();
-
- // clip the children to the parent outline.
- QTransform m;
- qreal zoomX, zoomY;
- converter.zoom(&zoomX, &zoomY);
- m.scale(zoomX, zoomY);
- painter.setClipPath(m.map(outline()), Qt::IntersectClip);
-
- QRectF toPaintRect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter));
- toPaintRect = transform().mapRect(toPaintRect);
- // We'll use this clipRect to see if our child shapes lie within it.
- // Because shape->boundingRect() uses absoluteTransformation(0) we'll
- // use that as well to have the same (absolute) reference transformation
- // of our and the child's bounding boxes.
- QTransform absTrans = absoluteTransformation(0);
- QRectF clipRect = absTrans.map(outline()).boundingRect();
-
-
- Q_FOREACH (KoShape *shape, sortedObjects) {
- //debugFlake <<"KoShapeContainer::painting shape:" << shape->shapeId() <<"," << shape->boundingRect();
- if (!shape->isVisible())
- continue;
- if (!isClipped(shape)) // the shapeManager will have to draw those, or else we can't do clipRects
- continue;
- // don't try to draw a child shape that is not in the clipping rect of the painter.
- if (!clipRect.intersects(shape->boundingRect()))
-
- continue;
-
- painter.save();
- painter.setTransform(shape->absoluteTransformation(&converter) * baseMatrix);
- shape->paint(painter, converter, paintcontext);
- painter.restore();
- if (shape->stroke()) {
- painter.save();
- painter.setTransform(shape->absoluteTransformation(&converter) * baseMatrix);
- shape->stroke()->paint(shape, painter, converter);
- painter.restore();
- }
- }
}
void KoShapeContainer::shapeChanged(ChangeType type, KoShape* shape)
{
Q_UNUSED(shape);
Q_D(KoShapeContainer);
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
{
Q_D(const KoShapeContainer);
if (d->model == 0) // throw exception??
return false;
return d->model->isClipped(child);
}
void KoShapeContainer::update() const
{
Q_D(const KoShapeContainer);
KoShape::update();
if (d->model)
Q_FOREACH (KoShape *shape, d->model->shapes())
shape->update();
}
QList<KoShape*> KoShapeContainer::shapes() const
{
Q_D(const KoShapeContainer);
if (d->model == 0)
return QList<KoShape*>();
return d->model->shapes();
}
KoShapeContainerModel *KoShapeContainer::model() const
{
Q_D(const KoShapeContainer);
return d->model;
}
+
+KoShapeContainer::ShapeInterface *KoShapeContainer::shapeInterface()
+{
+ Q_D(KoShapeContainer);
+ return &d->shapeInterface;
+}
+
+KoShapeContainer::ShapeInterface::ShapeInterface(KoShapeContainer *_q)
+ : q(_q)
+{
+}
+
+void KoShapeContainer::ShapeInterface::addShape(KoShape *shape)
+{
+ KoShapeContainerPrivate * const d = q->d_func();
+
+ 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)
+{
+ KoShapeContainerPrivate * const d = q->d_func();
+
+ 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 51cfd72e0f..f434b91da3 100644
--- a/libs/flake/KoShapeContainer.h
+++ b/libs/flake/KoShapeContainer.h
@@ -1,232 +1,259 @@
/* 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)
*/
virtual ~KoShapeContainer();
/**
* 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);
- /**
- * Remove all children to be completely separated from the container.
- *
- * All the shapes will only be removed from the container but not be deleted.
- */
- void removeAllShapes();
-
/**
* 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;
/**
* Return whether the child has the effective state of being locked for user modifications.
* This method is deferred to the model, which should call the KoShape::isGeometryProtected() on the child.
* @param child the shape that the user wants to move.
*/
bool isChildLocked(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
virtual void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext);
/**
* @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.
* @see applyConversion()
*/
virtual void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext) = 0;
using KoShape::update;
/// reimplemented
virtual void update() const;
/**
* 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;
+
+ /**
+ * 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:
/**
* 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() { }
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
/// constructor
- KoShapeContainer(KoShapeContainerPrivate &);
+ KoShapeContainer(KoShapeContainerPrivate *);
private:
Q_DECLARE_PRIVATE(KoShapeContainer)
};
#endif
diff --git a/libs/flake/KoShapeContainerDefaultModel.cpp b/libs/flake/KoShapeContainerDefaultModel.cpp
deleted file mode 100644
index 308dc46579..0000000000
--- a/libs/flake/KoShapeContainerDefaultModel.cpp
+++ /dev/null
@@ -1,170 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
- * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
- * 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.
- */
-
-#include "KoShapeContainerDefaultModel.h"
-
-#include "KoShapeContainer.h"
-
-class Q_DECL_HIDDEN KoShapeContainerDefaultModel::Private
-{
-public:
- class Relation
- {
- public:
- explicit Relation(KoShape *child)
- : inside(false),
- inheritsTransform(false),
- m_child(child)
- {}
-
- KoShape* child()
- {
- return m_child;
- }
-
- uint inside : 1; ///< if true, the child will be clipped by the parent.
- uint inheritsTransform : 1;
-
- private:
- KoShape *m_child;
- };
-
- ~Private()
- {
- qDeleteAll(relations);
- }
-
- Relation* findRelation(const KoShape *child) const
- {
- foreach (Relation *relation, relations) {
- if (relation->child() == child) {
- return relation;
- }
- }
- return 0;
- }
-
-
- // TODO use a QMap<KoShape*, bool> instead this should speed things up a bit
- QList<Relation *> relations;
-};
-
-KoShapeContainerDefaultModel::KoShapeContainerDefaultModel()
-: d(new Private())
-{
-}
-
-KoShapeContainerDefaultModel::~KoShapeContainerDefaultModel()
-{
- delete d;
-}
-
-void KoShapeContainerDefaultModel::add(KoShape *child)
-{
- Private::Relation *r = new Private::Relation(child);
- d->relations.append(r);
-}
-
-void KoShapeContainerDefaultModel::proposeMove(KoShape *shape, QPointF &move)
-{
- KoShapeContainer *parent = shape->parent();
- bool allowedToMove = true;
- while (allowedToMove && parent) {
- allowedToMove = parent->isEditable();
- parent = parent->parent();
- }
- if (! allowedToMove) {
- move.setX(0);
- move.setY(0);
- }
-}
-
-
-void KoShapeContainerDefaultModel::setClipped(const KoShape *child, bool clipping)
-{
- Private::Relation *relation = d->findRelation(child);
- if (relation == 0)
- return;
- if (relation->inside == clipping)
- return;
- relation->child()->update(); // mark old canvas-location as in need of repaint (aggregated)
- relation->inside = clipping;
- relation->child()->notifyChanged();
- relation->child()->update(); // mark new area as in need of repaint
-}
-
-bool KoShapeContainerDefaultModel::isClipped(const KoShape *child) const
-{
- Private::Relation *relation = d->findRelation(child);
- return relation ? relation->inside: false;
-}
-
-void KoShapeContainerDefaultModel::remove(KoShape *child)
-{
- Private::Relation *relation = d->findRelation(child);
- if (relation == 0)
- return;
- d->relations.removeAll(relation);
- delete relation;
-}
-
-int KoShapeContainerDefaultModel::count() const
-{
- return d->relations.count();
-}
-
-QList<KoShape*> KoShapeContainerDefaultModel::shapes() const
-{
- QList<KoShape*> answer;
- Q_FOREACH (Private::Relation *relation, d->relations) {
- answer.append(relation->child());
- }
- return answer;
-}
-
-bool KoShapeContainerDefaultModel::isChildLocked(const KoShape *child) const
-{
- return child->isGeometryProtected();
-}
-
-void KoShapeContainerDefaultModel::containerChanged(KoShapeContainer *, KoShape::ChangeType)
-{
-}
-
-void KoShapeContainerDefaultModel::setInheritsTransform(const KoShape *shape, bool inherit)
-{
- Private::Relation *relation = d->findRelation(shape);
- if (relation == 0)
- return;
- if (relation->inheritsTransform == inherit)
- return;
- relation->child()->update(); // mark old canvas-location as in need of repaint (aggregated)
- relation->inheritsTransform = inherit;
- relation->child()->notifyChanged();
- relation->child()->update(); // mark new area as in need of repaint
-}
-
-bool KoShapeContainerDefaultModel::inheritsTransform(const KoShape *shape) const
-{
- Private::Relation *relation = d->findRelation(shape);
- return relation ? relation->inheritsTransform: false;
-}
-
diff --git a/libs/flake/KoShapeContainerDefaultModel.h b/libs/flake/KoShapeContainerDefaultModel.h
deleted file mode 100644
index 9cae6c5975..0000000000
--- a/libs/flake/KoShapeContainerDefaultModel.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
- * 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 KOSHAPECONTAINERDEFAULTMODEL_H
-#define KOSHAPECONTAINERDEFAULTMODEL_H
-
-#include "KoShapeContainerModel.h"
-
-#include "kritaflake_export.h"
-
-/**
- * A default implementation of the KoShapeContainerModel.
- */
-class KRITAFLAKE_EXPORT KoShapeContainerDefaultModel : public KoShapeContainerModel
-{
-public:
- KoShapeContainerDefaultModel();
- virtual ~KoShapeContainerDefaultModel();
-
- virtual void add(KoShape *shape);
-
- virtual void proposeMove(KoShape *shape, QPointF &move);
-
- virtual void setClipped(const KoShape *shape, bool clipping);
-
- virtual bool isClipped(const KoShape *shape) const;
-
- virtual void setInheritsTransform(const KoShape *shape, bool inherit);
-
- virtual bool inheritsTransform(const KoShape *shape) const;
-
- virtual void remove(KoShape *shape);
-
- virtual int count() const;
-
- virtual QList<KoShape*> shapes() const;
-
- virtual bool isChildLocked(const KoShape *child) const;
-
- /// empty implementation.
- virtual void containerChanged(KoShapeContainer *container, KoShape::ChangeType type);
-
-private:
- class Private;
- Private * const d;
-};
-
-#endif
diff --git a/libs/flake/KoShapeContainerModel.cpp b/libs/flake/KoShapeContainerModel.cpp
index a66e887800..d400c84d15 100644
--- a/libs/flake/KoShapeContainerModel.cpp
+++ b/libs/flake/KoShapeContainerModel.cpp
@@ -1,50 +1,85 @@
/* 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);
}
}
}
+
+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/KoShapeContainerModel.h b/libs/flake/KoShapeContainerModel.h
index fbbad30c56..289a7e2d92 100644
--- a/libs/flake/KoShapeContainerModel.h
+++ b/libs/flake/KoShapeContainerModel.h
@@ -1,173 +1,181 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007, 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 KOSHAPECONTAINERMODEL_H
#define KOSHAPECONTAINERMODEL_H
#include "kritaflake_export.h"
#include <KoShape.h>
#include <QList>
#include <QPointF>
class KoShapeContainer;
/**
* The interface for the container model.
* This class has no implementation, but only pure virtual methods. You can find a
* fully implemented model using KoShapeContainerDefaultModel. Extending this
* class and implementing all methods allows you to implement a custom data-backend
* for the KoShapeContainer.
* @see KoShapeContainer, KoShapeContainerDefaultModel
*/
class KRITAFLAKE_EXPORT KoShapeContainerModel
{
public:
/// default constructor
KoShapeContainerModel();
/// destructor
virtual ~KoShapeContainerModel();
+ void deleteOwnedShapes();
+
/**
* Add a shape to this models store.
* @param shape the shape to be managed in the container.
*/
virtual void add(KoShape *shape) = 0;
/**
* Remove a shape to be completely separated from the model.
* @param shape the shape to be removed.
*/
virtual void remove(KoShape *shape) = 0;
/**
* Set the argument shape 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 shape the shape for which the property will be changed.
* @param clipping the new value
*/
virtual void setClipped(const KoShape *shape, bool clipping) = 0;
/**
* Returns if the argument shape 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 shape has its 'clipping' property set.
* @param shape the shape for which the property will be returned.
*/
virtual bool isClipped(const KoShape *shape) const = 0;
/**
* 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
*/
virtual void setInheritsTransform(const KoShape *shape, bool inherit) = 0;
/**
* 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.
*/
virtual bool inheritsTransform(const KoShape *shape) const = 0;
/**
* Return wheather the child has the effective state of being locked for user modifications.
* The model has to call KoShape::isGeometryProtected() and base its return value upon that, it can
* additionally find rules on wheather the child is locked based on the container state.
* @param child the shape that the user wants to move.
*/
virtual bool isChildLocked(const KoShape *child) const = 0;
/**
* Return the current number of children registered.
* @return the current number of children registered.
*/
virtual int count() const = 0;
/**
* Return the list of all shapes of this model
* @return the list of all shapes
*/
virtual QList<KoShape*> shapes() const = 0;
/**
* This method is called as a notification that one of the properties of the
* container changed. This can be one of size, position, rotation and skew.
* Note that clipped children will automatically get all these changes, the model
* does not have to do anything for that.
* @param container the actual container that changed.
* @param type this enum shows which change the container has had.
*/
virtual void containerChanged(KoShapeContainer *container, KoShape::ChangeType type) = 0;
/**
* This method is called when the user tries to move a shape that is a shape of the
* container this model represents.
* The shape itself is not yet moved; it is proposed to be moved over the param move distance.
* You can alter the value of the move to affect the actual distance moved.
* The default implementation does nothing.
* @param shape the shape of this container that the user is trying to move.
* @param move the distance that the user proposes to move shape from the current position.
*/
virtual void proposeMove(KoShape *shape, QPointF &move);
/**
* This method is called when one of the shape shapes has been modified.
* When a shape shape is rotated, moved or scaled/skewed this method will be called
* to inform the container of such a change. The change has already happened at the
* time this method is called.
* The base implementation notifies the grand parent of the shape that there was a
* change in a shape. A reimplentation if this function should call this method when
* overwriding the function.
*
* @param shape the shape that has been changed
* @param type this enum shows which change the shape has had.
*/
virtual void childChanged(KoShape *shape, KoShape::ChangeType type);
+
+ virtual void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree);
+ virtual void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree);
+
+protected:
+ KoShapeContainerModel(const KoShapeContainerModel &rhs);
};
#endif
diff --git a/libs/flake/KoShapeContainer_p.h b/libs/flake/KoShapeContainer_p.h
index 843b0e8af9..2a1b21164e 100644
--- a/libs/flake/KoShapeContainer_p.h
+++ b/libs/flake/KoShapeContainer_p.h
@@ -1,39 +1,43 @@
/* 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 KOSHAPECONTAINERPRIVATE_H
#define KOSHAPECONTAINERPRIVATE_H
#include "KoShape_p.h"
+#include "KoShapeContainer.h"
#include "kritaflake_export.h"
class KoShapeContainerModel;
/**
* \internal used private d-pointer class for the \a KoShapeContainer class.
*/
class KRITAFLAKE_EXPORT KoShapeContainerPrivate : public KoShapePrivate
{
public:
explicit KoShapeContainerPrivate(KoShapeContainer *q);
virtual ~KoShapeContainerPrivate();
+ KoShapeContainerPrivate(const KoShapeContainerPrivate &rhs, KoShapeContainer *q);
+
+ KoShapeContainer::ShapeInterface shapeInterface;
KoShapeContainerModel *model;
};
#endif
diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp
index 840cabd945..d277c9366b 100644
--- a/libs/flake/KoShapeController.cpp
+++ b/libs/flake/KoShapeController.cpp
@@ -1,191 +1,212 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
* 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 "KoShapeController.h"
#include "KoShapeBasedDocumentBase.h"
#include "KoShapeRegistry.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeManager.h"
#include "KoShapeLayer.h"
#include "KoSelection.h"
#include "commands/KoShapeCreateCommand.h"
#include "commands/KoShapeDeleteCommand.h"
#include "commands/KoShapeConnectionChangeCommand.h"
#include "KoCanvasBase.h"
#include "KoShapeConfigWidgetBase.h"
#include "KoShapeFactoryBase.h"
#include "KoShape.h"
#include "KoConnectionShape.h"
#include <KoUnit.h>
#include <QObject>
#include <kpagedialog.h>
#include <klocalizedstring.h>
class KoShapeController::Private
{
public:
Private()
: canvas(0),
shapeBasedDocument(0)
{
}
KoCanvasBase *canvas;
KoShapeBasedDocumentBase *shapeBasedDocument;
KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) {
if (canvas) {
if (showDialog) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId());
Q_ASSERT(factory);
int z = 0;
Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes())
z = qMax(z, sh->zIndex());
shape->setZIndex(z + 1);
// show config dialog.
KPageDialog *dialog = new KPageDialog(canvas->canvasWidget());
dialog->setWindowTitle(i18n("%1 Options", factory->name()));
int pageCount = 0;
QList<KoShapeConfigWidgetBase*> widgets;
Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) {
if (! panel->showOnShapeCreate())
continue;
panel->open(shape);
panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept()));
widgets.append(panel);
panel->setResourceManager(canvas->resourceManager());
panel->setUnit(canvas->unit());
QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle();
dialog->addPage(panel, title);
pageCount ++;
}
if (pageCount > 0) {
if (pageCount > 1)
dialog->setFaceType(KPageDialog::Tabbed);
if (dialog->exec() != KPageDialog::Accepted) {
delete dialog;
return 0;
}
Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets)
widget->save();
}
delete dialog;
}
-
- // set the active layer as parent if there is not yet a parent.
- if (!shape->parent()) {
- shape->setParent(canvas->shapeManager()->selection()->activeLayer());
- }
}
- return new KoShapeCreateCommand(shapeBasedDocument, shape, parent);
+
+ return addShapesDirect({shape}, parent);
+ }
+
+ KUndo2Command* addShapesDirect(const QList<KoShape*> shapes, KUndo2Command *parent)
+ {
+ return new KoShapeCreateCommand(shapeBasedDocument, shapes, parent);
}
void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast<KoConnectionShape*>(dependee);
if (connection) {
if (shape == connection->firstShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle,
shape, connection->firstConnectionId(), 0, -1, parentCmd);
} else if (shape == connection->secondShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle,
shape, connection->secondConnectionId(), 0, -1, parentCmd);
}
}
}
}
};
KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument)
: d(new Private())
{
d->canvas = canvas;
d->shapeBasedDocument = shapeBasedDocument;
if (shapeBasedDocument) {
shapeBasedDocument->resourceManager()->setShapeController(this);
}
}
KoShapeController::~KoShapeController()
{
delete d;
}
void KoShapeController::reset()
{
d->canvas = 0;
d->shapeBasedDocument = 0;
}
KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent)
{
return d->addShape(shape, true, parent);
}
KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent)
{
- return d->addShape(shape, false, parent);
+ return d->addShapesDirect({shape}, parent);
+}
+
+KUndo2Command *KoShapeController::addShapesDirect(const QList<KoShape *> shapes, KUndo2Command *parent)
+{
+ return d->addShapesDirect(shapes, parent);
}
KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent);
QList<KoShape*> shapes;
shapes.append(shape);
d->shapeBasedDocument->shapesRemoved(shapes, cmd);
// detach shape from any attached connection shapes
d->handleAttachedConnections(shape, cmd);
return cmd;
}
KUndo2Command* KoShapeController::removeShapes(const QList<KoShape*> &shapes, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent);
d->shapeBasedDocument->shapesRemoved(shapes, cmd);
foreach (KoShape *shape, shapes) {
d->handleAttachedConnections(shape, cmd);
}
return cmd;
}
void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument)
{
d->shapeBasedDocument = shapeBasedDocument;
}
+QRectF KoShapeController::documentRectInPixels() const
+{
+ return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080);
+}
+
+qreal KoShapeController::pixelsPerInch() const
+{
+ return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0;
+}
+
+QRectF KoShapeController::documentRect() const
+{
+ return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels();
+}
+
KoDocumentResourceManager *KoShapeController::resourceManager() const
{
if (!d->shapeBasedDocument)
return 0;
return d->shapeBasedDocument->resourceManager();
}
KoShapeBasedDocumentBase *KoShapeController::documentBase() const
{
return d->shapeBasedDocument;
}
diff --git a/libs/flake/KoShapeController.h b/libs/flake/KoShapeController.h
index 793fa9ffd0..fbb5356ad0 100644
--- a/libs/flake/KoShapeController.h
+++ b/libs/flake/KoShapeController.h
@@ -1,143 +1,169 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 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 KOSHAPECONTROLLER_H
#define KOSHAPECONTROLLER_H
#include "kritaflake_export.h"
#include <QObject>
#include <QList>
#include <QMetaType>
class KoCanvasBase;
class KoShape;
class KoShapeBasedDocumentBase;
class KUndo2Command;
class KoDocumentResourceManager;
/**
* Class used by tools to maintain the list of shapes.
* All applications have some sort of list of all shapes that belong to the document.
* The applications implement the KoShapeBasedDocumentBase interface (all pure virtuals)
* to add and remove shapes from the document. To ensure that an application can expect
* a certain protocol to be adhered to when adding/removing shapes, all tools use the API
* from this class for maintaining the list of shapes in the document. So no tool gets
* to access the application directly.
*/
class KRITAFLAKE_EXPORT KoShapeController : public QObject
{
Q_OBJECT
public:
/**
* Create a new Controller; typically not called by applications, only
* by the KonCanvasBase constructor.
* @param canvas the canvas this controller works for. The canvas can be 0
* @param shapeBasedDocument the application provided shapeBasedDocument that we can call.
*/
KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument);
/// destructor
~KoShapeController();
/**
* @brief reset sets the canvas and shapebased document to 0.
*/
void reset();
/**
* @brief Add a shape to the document.
* If the shape has no parent, the active layer will become its parent.
*
* @param shape to add to the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will insert the shape into the document or 0 if the
* insertion was cancelled. The command is not yet executed.
*/
KUndo2Command* addShape(KoShape *shape, KUndo2Command *parent = 0);
/**
* @brief Add a shape to the document, skipping any dialogs or other user interaction.
*
* @param shape to add to the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will insert the shape into the document. The command is not yet executed.
*/
KUndo2Command* addShapeDirect(KoShape *shape, KUndo2Command *parent = 0);
+ /**
+ * @brief Add shapes to the document, skipping any dialogs or other user interaction.
+ *
+ * @param shapes to add to the document
+ * @param parent the parent command if the resulting command is a compound undo command.
+ *
+ * @return command which will insert the shapes into the document. The command is not yet executed.
+ */
+ KUndo2Command* addShapesDirect(const QList<KoShape*> shape, KUndo2Command *parent = 0);
+
/**
* @brief Remove a shape from the document.
*
* @param shape to remove from the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will remove the shape from the document.
* The command is not yet executed.
*/
KUndo2Command* removeShape(KoShape *shape, KUndo2Command *parent = 0);
/**
* Remove a shape from the document.
*
* @param shapes the set of shapes to remove from the document
* @param parent the parent command if the resulting command is a compound undo command.
*
* @return command which will remove the shape from the document.
* The command is not yet executed.
*/
KUndo2Command* removeShapes(const QList<KoShape*> &shapes, KUndo2Command *parent = 0);
/**
* @brief Set the KoShapeBasedDocumentBase used to add/remove shapes.
*
* NOTE: only Sheets uses this method. Do not use it in your application. Sheets
* has to also call:
* <code>KoToolManager::instance()->updateShapeControllerBase(shapeBasedDocument, canvas->canvasController());</code>
*
* @param shapeBasedDocument the new shapeBasedDocument.
*/
void setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument);
+ /**
+ * The size of the document measured in rasterized pixels. This information is needed for loading
+ * SVG documents that use 'px' as the default unit.
+ */
+ QRectF documentRectInPixels() const;
+
+ /**
+ * Resolution of the rasterized representaiton of the document. Used to load SVG documents correctly.
+ */
+ qreal pixelsPerInch() const;
+
+ /**
+ * Document rect measured in 'pt'
+ */
+ QRectF documentRect() const;
+
/**
* Return a pointer to the resource manager associated with the
* shape-set (typically a document). The resource manager contains
* document wide resources * such as variable managers, the image
* collection and others.
*/
KoDocumentResourceManager *resourceManager() const;
/**
* @brief Returns the KoShapeBasedDocumentBase used to add/remove shapes.
*
* @return the KoShapeBasedDocumentBase
*/
KoShapeBasedDocumentBase *documentBase() const;
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoShapeController *)
#endif
diff --git a/libs/flake/KoShapeGroup.cpp b/libs/flake/KoShapeGroup.cpp
index 217fa5655d..0d9b11ed24 100644
--- a/libs/flake/KoShapeGroup.cpp
+++ b/libs/flake/KoShapeGroup.cpp
@@ -1,250 +1,286 @@
/* 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 :
m_group->invalidateSizeCache();
break;
default:
break;
}
}
private: // members
KoShapeGroup * m_group;
};
class KoShapeGroupPrivate : public KoShapeContainerPrivate
{
public:
KoShapeGroupPrivate(KoShapeGroup *q)
- : KoShapeContainerPrivate(q)
+ : KoShapeContainerPrivate(q)
{
model = new ShapeGroupContainerModel(q);
}
+ KoShapeGroupPrivate(const KoShapeGroupPrivate &rhs, KoShapeGroup *q)
+ : KoShapeContainerPrivate(rhs, q)
+ {
+ ShapeGroupContainerModel *otherModel = dynamic_cast<ShapeGroupContainerModel*>(rhs.model);
+ KIS_ASSERT_RECOVER_RETURN(otherModel);
+ model = new ShapeGroupContainerModel(*otherModel, q);
+ }
+
~KoShapeGroupPrivate() override
{
}
- mutable bool sizeCached;
+ mutable QRectF savedOutlineRect;
+ mutable bool sizeCached = false;
+
+ void tryUpdateCachedSize() const;
+
+ Q_DECLARE_PUBLIC(KoShapeGroup)
};
KoShapeGroup::KoShapeGroup()
- : KoShapeContainer(*(new KoShapeGroupPrivate(this)))
+ : KoShapeContainer(new KoShapeGroupPrivate(this))
+{
+}
+
+KoShapeGroup::KoShapeGroup(const KoShapeGroup &rhs)
+ : KoShapeContainer(new KoShapeGroupPrivate(*rhs.d_func(), this))
{
- setSize(QSizeF(0, 0));
}
KoShapeGroup::~KoShapeGroup()
{
}
+KoShape *KoShapeGroup::cloneShape() const
+{
+ return new KoShapeGroup(*this);
+}
+
void KoShapeGroup::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
bool KoShapeGroup::hitTest(const QPointF &position) const
{
Q_UNUSED(position);
return false;
}
-QSizeF KoShapeGroup::size() const
+void KoShapeGroupPrivate::tryUpdateCachedSize() const
{
- Q_D(const KoShapeGroup);
- //debugFlake << "size" << d->size;
- if (!d->sizeCached) {
+ Q_Q(const KoShapeGroup);
+
+ if (!sizeCached) {
QRectF bound;
- Q_FOREACH (KoShape *shape, shapes()) {
- if (bound.isEmpty())
- bound = shape->transformation().mapRect(shape->outlineRect());
- else
- bound |= shape->transformation().mapRect(shape->outlineRect());
+ Q_FOREACH (KoShape *shape, q->shapes()) {
+ bound |= shape->transformation().mapRect(shape->outlineRect());
}
- d->size = bound.size();
- d->sizeCached = true;
- debugFlake << "recalculated size" << d->size;
+ savedOutlineRect = bound;
+ size = bound.size();
+ sizeCached = true;
}
+}
+
+QSizeF KoShapeGroup::size() const
+{
+ Q_D(const KoShapeGroup);
+ d->tryUpdateCachedSize();
return d->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
+{
+ Q_D(const KoShapeGroup);
+
+ d->tryUpdateCachedSize();
+ return d->savedOutlineRect;
+}
+
QRectF KoShapeGroup::boundingRect() const
{
- bool first = true;
- QRectF groupBound;
- Q_FOREACH (KoShape* shape, shapes()) {
- if (first) {
- groupBound = shape->boundingRect();
- first = false;
- } else {
- groupBound = groupBound.united(shape->boundingRect());
- }
- }
+ 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().addAttributePt("svg:y", position().y());
QList<KoShape*> shapes = this->shapes();
qSort(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)
{
Q_D(KoShapeGroup);
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:
- {
- KoShapeStrokeModel *str = stroke();
- if (str) {
- if (str->deref())
- delete str;
- setStroke(0);
- }
break;
- }
default:
break;
}
+
+ invalidateSizeCache();
}
void KoShapeGroup::invalidateSizeCache()
{
Q_D(KoShapeGroup);
d->sizeCached = false;
}
-
diff --git a/libs/flake/KoShapeGroup.h b/libs/flake/KoShapeGroup.h
index 0755d7be9e..acd5814669 100644
--- a/libs/flake/KoShapeGroup.h
+++ b/libs/flake/KoShapeGroup.h
@@ -1,83 +1,93 @@
/* 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
virtual ~KoShapeGroup();
+
+ KoShape* cloneShape() const override;
+
/// This implementation is empty since a group is itself not visible.
virtual void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext);
/// always returns false since the group itself can't be selected or hit
virtual bool hitTest(const QPointF &position) const;
- /// a group in flake doesn't have a size, this function just returns QSizeF(0,0)
- virtual QSizeF size() const;
+ QSizeF size() const override;
+ void setSize(const QSizeF &size) override;
+ QRectF outlineRect() const override;
/// a group's boundingRect
- virtual QRectF boundingRect() const;
+ QRectF boundingRect() const override;
/// reimplemented from KoShape
virtual void saveOdf(KoShapeSavingContext &context) const;
// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
+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:
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
Q_DECLARE_PRIVATE(KoShapeGroup)
};
#endif
diff --git a/libs/flake/KoShapeLayer.cpp b/libs/flake/KoShapeLayer.cpp
index d203153314..8a241cea5a 100644
--- a/libs/flake/KoShapeLayer.cpp
+++ b/libs/flake/KoShapeLayer.cpp
@@ -1,90 +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
{
- QRectF bb;
-
- Q_FOREACH (KoShape* shape, shapes()) {
- if (bb.isEmpty())
- bb = shape->boundingRect();
- else
- bb = bb.united(shape->boundingRect());
- }
-
- return bb;
+ return KoShape::boundingRect(shapes());
}
void KoShapeLayer::saveOdf(KoShapeSavingContext & context) const
{
QList<KoShape*> shapes = this->shapes();
qSort(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 &)
{
}
diff --git a/libs/flake/KoShapeLoadingContext.cpp b/libs/flake/KoShapeLoadingContext.cpp
index d4d34d5feb..0b1635219f 100644
--- a/libs/flake/KoShapeLoadingContext.cpp
+++ b/libs/flake/KoShapeLoadingContext.cpp
@@ -1,221 +1,221 @@
/* This file is part of the KDE project
Copyright (C) 2007-2009, 2011 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2014-2015 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 "KoShapeLoadingContext.h"
#include "KoShape.h"
#include "KoShapeContainer.h"
#include "KoSharedLoadingData.h"
#include "KoShapeBasedDocumentBase.h"
#include "KoImageCollection.h"
#include "KoMarkerCollection.h"
#include "KoDocumentResourceManager.h"
#include "KoLoadingShapeUpdater.h"
#include <FlakeDebug.h>
uint qHash(const KoShapeLoadingContext::AdditionalAttributeData & attributeData)
{
return qHash(attributeData.name);
}
static QSet<KoShapeLoadingContext::AdditionalAttributeData> s_additionlAttributes;
class Q_DECL_HIDDEN KoShapeLoadingContext::Private
{
public:
Private(KoOdfLoadingContext &c, KoDocumentResourceManager *resourceManager)
: context(c)
, zIndex(0)
, documentResources(resourceManager)
, documentRdf(0)
, sectionModel(0)
{
}
~Private() {
Q_FOREACH (KoSharedLoadingData * data, sharedData) {
delete data;
}
}
KoOdfLoadingContext &context;
QMap<QString, KoShapeLayer*> layers;
QMap<QString, KoShape*> drawIds;
QMap<QString, QPair<KoShape *, QVariant> > subIds;
QMap<QString, KoSharedLoadingData *> sharedData; //FIXME: use QScopedPointer here to auto delete in destructor
int zIndex;
QMap<QString, KoLoadingShapeUpdater*> updaterById;
QMap<KoShape *, KoLoadingShapeUpdater*> updaterByShape;
KoDocumentResourceManager *documentResources;
QObject *documentRdf;
KoSectionModel *sectionModel;
};
KoShapeLoadingContext::KoShapeLoadingContext(KoOdfLoadingContext & context, KoDocumentResourceManager *documentResources)
: d(new Private(context, documentResources))
{
if (d->documentResources) {
KoMarkerCollection *markerCollection = d->documentResources->resource(KoDocumentResourceManager::MarkerCollection).value<KoMarkerCollection*>();
if (markerCollection) {
- markerCollection->loadOdf(*this);
+ //markerCollection->loadOdf(*this);
}
}
}
KoShapeLoadingContext::~KoShapeLoadingContext()
{
delete d;
}
KoOdfLoadingContext & KoShapeLoadingContext::odfLoadingContext()
{
return d->context;
}
KoShapeLayer * KoShapeLoadingContext::layer(const QString & layerName)
{
return d->layers.value(layerName, 0);
}
void KoShapeLoadingContext::addLayer(KoShapeLayer * layer, const QString & layerName)
{
d->layers[ layerName ] = layer;
}
void KoShapeLoadingContext::clearLayers()
{
d->layers.clear();
}
void KoShapeLoadingContext::addShapeId(KoShape * shape, const QString & id)
{
d->drawIds.insert(id, shape);
QMap<QString, KoLoadingShapeUpdater*>::iterator it(d->updaterById.find(id));
while (it != d->updaterById.end() && it.key() == id) {
d->updaterByShape.insertMulti(shape, it.value());
it = d->updaterById.erase(it);
}
}
KoShape * KoShapeLoadingContext::shapeById(const QString &id)
{
return d->drawIds.value(id, 0);
}
void KoShapeLoadingContext::addShapeSubItemId(KoShape *shape, const QVariant &subItem, const QString &id)
{
d->subIds.insert(id, QPair<KoShape *, QVariant>(shape, subItem));
}
QPair<KoShape *, QVariant> KoShapeLoadingContext::shapeSubItemById(const QString &id)
{
return d->subIds.value(id);
}
// TODO make sure to remove the shape from the loading context when loading for it failed and it was deleted. This can also happen when the parent is deleted
void KoShapeLoadingContext::updateShape(const QString & id, KoLoadingShapeUpdater * shapeUpdater)
{
d->updaterById.insertMulti(id, shapeUpdater);
}
void KoShapeLoadingContext::shapeLoaded(KoShape * shape)
{
QMap<KoShape*, KoLoadingShapeUpdater*>::iterator it(d->updaterByShape.find(shape));
while (it != d->updaterByShape.end() && it.key() == shape) {
it.value()->update(shape);
delete it.value();
it = d->updaterByShape.erase(it);
}
}
KoImageCollection * KoShapeLoadingContext::imageCollection()
{
return d->documentResources ? d->documentResources->imageCollection() : 0;
}
int KoShapeLoadingContext::zIndex()
{
return d->zIndex++;
}
void KoShapeLoadingContext::setZIndex(int index)
{
d->zIndex = index;
}
void KoShapeLoadingContext::addSharedData(const QString & id, KoSharedLoadingData * data)
{
QMap<QString, KoSharedLoadingData*>::iterator it(d->sharedData.find(id));
// data will not be overwritten
if (it == d->sharedData.end()) {
d->sharedData.insert(id, data);
} else {
warnFlake << "The id" << id << "is already registered. Data not inserted";
Q_ASSERT(it == d->sharedData.end());
}
}
KoSharedLoadingData * KoShapeLoadingContext::sharedData(const QString & id) const
{
KoSharedLoadingData * data = 0;
QMap<QString, KoSharedLoadingData*>::const_iterator it(d->sharedData.find(id));
if (it != d->sharedData.constEnd()) {
data = it.value();
}
return data;
}
void KoShapeLoadingContext::addAdditionalAttributeData(const AdditionalAttributeData & attributeData)
{
s_additionlAttributes.insert(attributeData);
}
QSet<KoShapeLoadingContext::AdditionalAttributeData> KoShapeLoadingContext::additionalAttributeData()
{
return s_additionlAttributes;
}
KoDocumentResourceManager *KoShapeLoadingContext::documentResourceManager() const
{
return d->documentResources;
}
QObject *KoShapeLoadingContext::documentRdf() const
{
return d->documentRdf;
}
void KoShapeLoadingContext::setDocumentRdf(QObject *documentRdf)
{
d->documentRdf = documentRdf;
}
KoSectionModel *KoShapeLoadingContext::sectionModel()
{
return d->sectionModel;
}
void KoShapeLoadingContext::setSectionModel(KoSectionModel *sectionModel)
{
d->sectionModel = sectionModel;
}
diff --git a/libs/flake/KoShapeManager.cpp b/libs/flake/KoShapeManager.cpp
index 6f861f15fd..c0553f25e2 100644
--- a/libs/flake/KoShapeManager.cpp
+++ b/libs/flake/KoShapeManager.cpp
@@ -1,594 +1,607 @@
/* 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 "KoShapeManagerPaintingStrategy.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 <QPainter>
#include <QTimer>
#include <FlakeDebug.h>
#include "kis_painting_tweaks.h"
+bool KoShapeManager::Private::shapeUsedInRenderingTree(KoShape *shape)
+{
+ return !dynamic_cast<KoShapeGroup*>(shape) && !dynamic_cast<KoShapeLayer*>(shape);
+}
void KoShapeManager::Private::updateTree()
{
// 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());
- strategy->adapt(shape, br);
tree.insert(br, shape);
}
// do it again to see which shapes we intersect with _after_ moving.
- foreach (KoShape *shape, aggregate4update)
+ foreach (KoShape *shape, aggregate4update) {
detector.detect(tree, shape, shapeIndexesBeforeUpdate[shape]);
+ }
aggregate4update.clear();
shapeIndexesBeforeUpdate.clear();
detector.fireSignals();
if (selectionModified) {
- selection->updateSizeAndPosition();
emit q->selectionContentChanged();
}
if (anyModified) {
emit q->contentChanged();
}
}
void KoShapeManager::Private::paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
QList<KoShape*> shapes = group->shapes();
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
Q_FOREACH (KoShape *child, shapes) {
// we paint recursively here, so we do not have to check recursively for visibility
if (!child->isVisible())
continue;
KoShapeGroup *childGroup = dynamic_cast<KoShapeGroup*>(child);
if (childGroup) {
paintGroup(childGroup, painter, converter, paintContext);
} else {
painter.save();
- strategy->paint(child, painter, converter, paintContext);
+ 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);
}
KoShapeManager::KoShapeManager(KoCanvasBase *canvas)
: d(new Private(this, canvas))
{
Q_ASSERT(d->canvas); // not optional.
connect(d->selection, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
}
KoShapeManager::~KoShapeManager()
{
Q_FOREACH (KoShape *shape, d->shapes) {
shape->priv()->removeShapeManager(this);
}
Q_FOREACH (KoShape *shape, d->additionalShapes) {
shape->priv()->removeShapeManager(this);
}
delete d;
}
-
void KoShapeManager::setShapes(const QList<KoShape *> &shapes, Repaint repaint)
{
//clear selection
d->selection->deselectAll();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->priv()->removeShapeManager(this);
}
d->aggregate4update.clear();
d->tree.clear();
d->shapes.clear();
+
Q_FOREACH (KoShape *shape, shapes) {
addShape(shape, repaint);
}
}
void KoShapeManager::addShape(KoShape *shape, Repaint repaint)
{
if (d->shapes.contains(shape))
return;
shape->priv()->addShapeManager(this);
d->shapes.append(shape);
- if (! dynamic_cast<KoShapeGroup*>(shape) && ! dynamic_cast<KoShapeLayer*>(shape)) {
+
+ if (d->shapeUsedInRenderingTree(shape)) {
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);
}
}
Private::DetectCollision detector;
detector.detect(d->tree, shape, shape->zIndex());
detector.fireSignals();
}
-void KoShapeManager::addAdditional(KoShape *shape)
-{
- if (shape) {
- if (d->additionalShapes.contains(shape)) {
- return;
- }
- shape->priv()->addShapeManager(this);
- d->additionalShapes.append(shape);
- }
-}
-
void KoShapeManager::remove(KoShape *shape)
{
Private::DetectCollision detector;
detector.detect(d->tree, shape, shape->zIndex());
detector.fireSignals();
shape->update();
shape->priv()->removeShapeManager(this);
d->selection->deselect(shape);
d->aggregate4update.remove(shape);
- d->tree.remove(shape);
+
+ if (d->shapeUsedInRenderingTree(shape)) {
+ d->tree.remove(shape);
+ }
d->shapes.removeAll(shape);
// remove the children of a KoShapeContainer
KoShapeContainer *container = dynamic_cast<KoShapeContainer*>(shape);
if (container) {
foreach (KoShape *containerShape, container->shapes()) {
remove(containerShape);
}
}
+}
- // This signal is used in the annotation shape.
- // FIXME: Is this really what we want? (and shouldn't it be called shapeDeleted()?)
- shapeRemoved(shape);
+KoShapeManager::ShapeInterface::ShapeInterface(KoShapeManager *_q)
+ : q(_q)
+{
}
-void KoShapeManager::removeAdditional(KoShape *shape)
+void KoShapeManager::ShapeInterface::notifyShapeDestructed(KoShape *shape)
{
- if (shape) {
- shape->priv()->removeShapeManager(this);
- d->additionalShapes.removeAll(shape);
+ q->d->selection->deselect(shape);
+ q->d->aggregate4update.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)
{
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()) {
QRectF rect = converter.viewToDocument(KisPaintingTweaks::safeClipBoundingRect(painter));
unsortedShapes = d->tree.intersects(rect);
} else {
unsortedShapes = 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(true))
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();
}
if (addShapeToList) {
sortedShapes.append(shape);
} else if (parent) {
sortedShapes.append(parent);
}
}
qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
- foreach (KoShape *shape, sortedShapes) {
- if (shape->parent() != 0 && shape->parent()->isClipped(shape))
- continue;
+ KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
- painter.save();
-
- // apply shape clipping
- KoClipPath::applyClipping(shape, painter, converter);
-
- // let the painting strategy paint the shape
- KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
- d->strategy->paint(shape, painter, converter, paintContext);
-
- painter.restore();
+ foreach (KoShape *shape, sortedShapes) {
+ renderSingleShape(shape, painter, converter, paintContext);
}
#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
if (! forPrint) {
KoShapePaintingContext paintContext(d->canvas, forPrint); //FIXME
d->selection->paint(painter, converter, paintContext);
}
}
+void KoShapeManager::renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+{
+ KisQPainterStateSaver saver(&painter);
+
+ // apply shape clipping
+ KoClipPath::applyClipping(shape, painter, converter);
+
+ // apply transformation
+ painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform());
+
+ // paint the shape
+ paintShape(shape, painter, converter, paintContext);
+}
+
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);
}
if (shape->shadow()) {
painter.save();
shape->shadow()->paint(shape, painter, converter);
painter.restore();
}
if (!shape->filterEffectStack() || shape->filterEffectStack()->isEmpty()) {
- painter.save();
- shape->paint(painter, converter, paintContext);
- painter.restore();
+
+ QScopedPointer<KoClipMaskPainter> clipMaskPainter;
+ QPainter *shapePainter = &painter;
+
+ KoClipMask *clipMask = shape->clipMask();
+ if (clipMask) {
+ clipMaskPainter.reset(new KoClipMaskPainter(&painter, shape->boundingRect()));
+ shapePainter = clipMaskPainter->shapePainter();
+ }
+
+ shapePainter->save();
+ shape->paint(*shapePainter, converter, paintContext);
+ shapePainter->restore();
if (shape->stroke()) {
- painter.save();
- shape->stroke()->paint(shape, painter, converter);
- painter.restore();
+ shapePainter->save();
+ shape->stroke()->paint(shape, *shapePainter, converter);
+ shapePainter->restore();
+ }
+
+ if (clipMask) {
+ shape->clipMask()->drawMask(clipMaskPainter->maskPainter(), shape);
+ clipMaskPainter->renderOnGlobalPainter();
}
+
} 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);
- d->paintGroup(group, imagePainter, converter, paintContext);
+ Private::paintGroup(group, imagePainter, converter, paintContext);
} else {
imagePainter.save();
shape->paint(imagePainter, converter, paintContext);
imagePainter.restore();
if (shape->stroke()) {
imagePainter.save();
shape->stroke()->paint(shape, imagePainter, converter);
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);
}
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);
}
KoFilterEffect *lastEffect = filterEffects.last();
// Paint the result
painter.save();
painter.drawImage(clippingOffset, imageBuffers.value(lastEffect->output()));
painter.restore();
}
}
KoShape *KoShapeManager::shapeAt(const QPointF &position, KoFlake::ShapeSelection selection, bool omitHiddenShapes)
{
d->updateTree();
QList<KoShape*> sortedShapes(d->tree.contains(position));
qSort(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(true))
continue;
if (! shape->hitTest(position))
continue;
switch (selection) {
case KoFlake::ShapeOnTop:
if (shape->isSelectable())
return shape;
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)
+QList<KoShape *> KoShapeManager::shapesAt(const QRectF &rect, bool omitHiddenShapes, bool containedMode)
{
d->updateTree();
- QList<KoShape*> intersectedShapes(d->tree.intersects(rect));
-
- for (int count = intersectedShapes.count() - 1; count >= 0; count--) {
+ QList<KoShape*> shapes(containedMode ? d->tree.contained(rect) : d->tree.intersects(rect));
+
+ for (int count = shapes.count() - 1; count >= 0; count--) {
- KoShape *shape = intersectedShapes.at(count);
+ KoShape *shape = shapes.at(count);
- if (omitHiddenShapes && ! shape->isVisible(true)) {
- intersectedShapes.removeAt(count);
+ if (omitHiddenShapes && !shape->isVisible(true)) {
+ shapes.removeAt(count);
} else {
const QPainterPath outline = shape->absoluteTransformation(0).map(shape->outline());
- if (! outline.intersects(rect) && ! outline.contains(rect)) {
- intersectedShapes.removeAt(count);
+
+ 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 intersectedShapes;
+
+ return shapes;
}
void KoShapeManager::update(QRectF &rect, const KoShape *shape, bool selectionHandles)
{
d->canvas->updateCanvas(rect);
if (selectionHandles && d->selection->isSelected(shape)) {
if (d->canvas->toolProxy())
d->canvas->toolProxy()->repaintDecorations();
}
}
void KoShapeManager::notifyShapeChanged(KoShape *shape)
{
Q_ASSERT(shape);
if (d->aggregate4update.contains(shape) || d->additionalShapes.contains(shape)) {
return;
}
const bool wasEmpty = d->aggregate4update.isEmpty();
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);
}
if (wasEmpty && !d->aggregate4update.isEmpty())
QTimer::singleShot(100, this, SLOT(updateTree()));
emit shapeChanged(shape);
}
QList<KoShape*> KoShapeManager::shapes() const
{
return d->shapes;
}
QList<KoShape*> KoShapeManager::topLevelShapes() const
{
QList<KoShape*> shapes;
// get all toplevel shapes
Q_FOREACH (KoShape *shape, d->shapes) {
if (shape->parent() == 0) {
shapes.append(shape);
}
}
return shapes;
}
KoSelection *KoShapeManager::selection() const
{
return d->selection;
}
-void KoShapeManager::suggestChangeTool(KoPointerEvent *event)
-{
- QList<KoShape*> shapes;
-
- KoShape *clicked = shapeAt(event->point);
- if (clicked) {
- if (! selection()->isSelected(clicked)) {
- selection()->deselectAll();
- selection()->select(clicked);
- }
- shapes.append(clicked);
- }
-
- QList<KoShape*> shapes2;
- foreach (KoShape *shape, shapes) {
- QSet<KoShape*> delegates = shape->toolDelegates();
- if (delegates.isEmpty()) {
- shapes2.append(shape);
- } else {
- foreach (KoShape *delegatedShape, delegates) {
- shapes2.append(delegatedShape);
- }
- }
- }
- KoToolManager::instance()->switchToolRequested(
- KoToolManager::instance()->preferredToolForSelection(shapes2));
-}
-
-void KoShapeManager::setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy)
-{
- delete d->strategy;
- d->strategy = strategy;
-}
-
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 c5f3128f42..20bdc199e0 100644
--- a/libs/flake/KoShapeManager.h
+++ b/libs/flake/KoShapeManager.h
@@ -1,224 +1,215 @@
/* 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 "KoFlake.h"
#include "kritaflake_export.h"
class KoShape;
class KoSelection;
class KoViewConverter;
class KoCanvasBase;
class KoPointerEvent;
-class KoShapeManagerPaintingStrategy;
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);
virtual ~KoShapeManager();
/**
* 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);
- /**
- * Add an additional shape to the manager.
- *
- * For additional shapes only updates are handled
- */
- void addAdditional(KoShape *shape);
/**
* Remove a KoShape from this manager
* @param shape the shape to remove
*/
void remove(KoShape *shape);
- /**
- * Remove an additional shape
- *
- * For additional shapes only updates are handled
- */
- void removeAdditional(KoShape *shape);
-
public:
/// return the selection shapes for this shapeManager
KoSelection *selection() const;
/**
* 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);
/**
* 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 true, only visible shapes are considered
*/
- QList<KoShape *> shapesAt(const QRectF &rect, bool omitHiddenShapes = true);
+ 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(QRectF &rect, const KoShape *shape = 0, bool selectionHandles = false);
/**
* 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);
- /**
- * Switch to editing the shape that is at the position of the event.
- * This method will check select a shape at the event position and switch to the default tool
- * for that shape, or switch to the default tool if there is no shape at the position.
- * @param event the event that holds the point where to look for a shape.
- */
- void suggestChangeTool(KoPointerEvent *event);
-
/**
* 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.
*/
- void paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
+ static void paintShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
/**
- * Set the strategy of the KoShapeManager
- *
- * This can be used to change the behaviour of the painting of the shapes.
- * @param strategy the new strategy. The ownership of the argument \p
- * strategy will be taken by the shape manager.
+ * @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.
*/
- void setPaintingStrategy(KoShapeManagerPaintingStrategy *strategy);
+ static void renderSingleShape(KoShape *shape, QPainter &painter, const KoViewConverter &converter, 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();
- /// emitted when a shape is removed.
- void shapeRemoved(KoShape *);
/// emitted when any shape changed.
void shapeChanged(KoShape *);
private:
-
- friend class KoShapeManagerPaintingStrategy;
KoCanvasBase *canvas();
class Private;
Private * const d;
Q_PRIVATE_SLOT(d, void updateTree())
};
#endif
diff --git a/libs/flake/KoShapeManagerPaintingStrategy.cpp b/libs/flake/KoShapeManagerPaintingStrategy.cpp
deleted file mode 100644
index 72e8f37a4f..0000000000
--- a/libs/flake/KoShapeManagerPaintingStrategy.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/* This file is part of the KDE project
-
- Copyright (C) 2007,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.
-*/
-
-#include "KoShapeManagerPaintingStrategy.h"
-
-#include "KoShape.h"
-#include "KoShapeManager.h"
-#include <QPainter>
-
-class Q_DECL_HIDDEN KoShapeManagerPaintingStrategy::Private
-{
-public:
- Private(KoShapeManager * manager)
- : shapeManager(manager)
- {}
-
- KoShapeManager * shapeManager;
-};
-
-KoShapeManagerPaintingStrategy::KoShapeManagerPaintingStrategy(KoShapeManager * shapeManager)
-: d(new KoShapeManagerPaintingStrategy::Private(shapeManager))
-{
-}
-
-KoShapeManagerPaintingStrategy::~KoShapeManagerPaintingStrategy()
-{
- delete d;
-}
-
-void KoShapeManagerPaintingStrategy::paint(KoShape * shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
-{
- if (d->shapeManager) {
- painter.save();
- painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform());
- d->shapeManager->paintShape(shape, painter, converter, paintContext);
- painter.restore(); // for the matrix
- }
-}
-
-void KoShapeManagerPaintingStrategy::adapt(KoShape * shape, QRectF & rect)
-{
- Q_UNUSED(shape);
- Q_UNUSED(rect);
-}
-
-void KoShapeManagerPaintingStrategy::setShapeManager(KoShapeManager * shapeManager)
-{
- d->shapeManager = shapeManager;
-}
-
-KoShapeManager * KoShapeManagerPaintingStrategy::shapeManager()
-{
- return d->shapeManager;
-}
diff --git a/libs/flake/KoShapeManagerPaintingStrategy.h b/libs/flake/KoShapeManagerPaintingStrategy.h
deleted file mode 100644
index 804eb51ada..0000000000
--- a/libs/flake/KoShapeManagerPaintingStrategy.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* This file is part of the KDE project
-
- Copyright (C) 2007,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 KOSHAPEMANAGERPAINTINGSTRATEGY_H
-#define KOSHAPEMANAGERPAINTINGSTRATEGY_H
-
-#include "kritaflake_export.h"
-
-class KoShapeManager;
-class KoShape;
-class KoViewConverter;
-class KoShapePaintingContext;
-class QPainter;
-class QRectF;
-
-/**
- * This implements the painting strategy for the KoShapeManager
- *
- * This is done to make it possible to have e.g. animations in kpresenter.
- *
- * This class implements the default strategy which is normally used.
- */
-class KRITAFLAKE_EXPORT KoShapeManagerPaintingStrategy
-{
-public:
- explicit KoShapeManagerPaintingStrategy(KoShapeManager *shapeManager);
- virtual ~KoShapeManagerPaintingStrategy();
-
- /**
- * Paint the shape
- *
- * @param shape the shape to paint
- * @param painter the painter to paint to.
- * @param converter to convert between document and view coordinates.
- * @param forPrint if true, make sure only actual content is drawn and no decorations.
- */
- virtual void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
-
- /**
- * Adapt the rect the shape occupies
- *
- * @param rect rect which will be updated to give the rect the shape occupies.
- */
- virtual void adapt(KoShape *shape, QRectF &rect);
-
- /**
- * Set the shape manager
- *
- * This is needed in case you cannot set the shape manager when creating the paiting strategy.
- * It needs to be set before you paint otherwise nothing will be painted.
- *
- * @param shapeManager The shape manager to use in the painting startegy
- */
- void setShapeManager(KoShapeManager *shapeManager);
-
-protected:
- KoShapeManager *shapeManager();
-
-private:
- class Private;
- Private * const d;
-};
-
-#endif /* KOSHAPEMANAGERPAINTINGSTRATEGY_H */
diff --git a/libs/flake/KoShapeManager_p.h b/libs/flake/KoShapeManager_p.h
index e9db0df8ba..8a73f8bc31 100644
--- a/libs/flake/KoShapeManager_p.h
+++ b/libs/flake/KoShapeManager_p.h
@@ -1,111 +1,115 @@
/* 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 "KoShapeManagerPaintingStrategy.h"
+#include "KoShapeManager.h"
#include <KoRTree.h>
-class KoShapeManager;
class KoCanvasBase;
class KoShapeGroup;
class KoShapePaintingContext;
class QPainter;
class Q_DECL_HIDDEN KoShapeManager::Private
{
public:
Private(KoShapeManager *shapeManager, KoCanvasBase *c)
: selection(new KoSelection()),
canvas(c),
tree(4, 2),
- strategy(new KoShapeManagerPaintingStrategy(shapeManager)),
- q(shapeManager)
+ q(shapeManager),
+ shapeInterface(shapeManager)
{
}
~Private() {
delete selection;
- delete strategy;
}
/**
* 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();
+ /**
+ * Returns whether the shape should be added to the RTree for collision and ROI
+ * detection.
+ */
+ bool shapeUsedInRenderingTree(KoShape *shape);
+
/**
* 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
*/
- void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
+ 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->priv()->shapeChanged(KoShape::CollisionDetected);
}
private:
QList<KoShape*> shapesWithCollisionDetection;
};
QList<KoShape *> shapes;
QList<KoShape *> additionalShapes; // these are shapes that are only handled for updates
KoSelection *selection;
KoCanvasBase *canvas;
KoRTree<KoShape *> tree;
QSet<KoShape *> aggregate4update;
QHash<KoShape*, int> shapeIndexesBeforeUpdate;
- KoShapeManagerPaintingStrategy *strategy;
KoShapeManager *q;
+ KoShapeManager::ShapeInterface shapeInterface;
};
#endif
diff --git a/libs/flake/KoShapePaste.cpp b/libs/flake/KoShapePaste.cpp
deleted file mode 100644
index cddd646390..0000000000
--- a/libs/flake/KoShapePaste.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
- Copyright (C) 2009 Thomas Zander <zander@kde.org>
- Copyright (C) 2010-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 "KoShapePaste.h"
-
-#include <FlakeDebug.h>
-#include <klocalizedstring.h>
-
-#include <KoOdfLoadingContext.h>
-#include <KoOdfReadStore.h>
-#include <KoXmlReader.h>
-
-#include "KoCanvasBase.h"
-#include "KoShapeController.h"
-#include "KoShape.h"
-#include "KoShapeLayer.h"
-#include "KoShapeLoadingContext.h"
-#include "KoShapeManager.h"
-#include "KoShapeBasedDocumentBase.h"
-#include "KoShapeRegistry.h"
-#include "KoCanvasController.h"
-#include "KoDocumentResourceManager.h"
-#include "commands/KoShapeCreateCommand.h"
-#include "KoViewConverter.h"
-
-class Q_DECL_HIDDEN KoShapePaste::Private
-{
-public:
- Private(KoCanvasBase *cb, KoShapeLayer *l) : canvas(cb), layer(l) {}
-
- KoCanvasBase *canvas;
- KoShapeLayer *layer;
- QList<KoShape*> pastedShapes;
-};
-
-KoShapePaste::KoShapePaste(KoCanvasBase *canvas, KoShapeLayer *layer)
- : d(new Private(canvas, layer))
-{
-}
-
-KoShapePaste::~KoShapePaste()
-{
- delete d;
-}
-
-bool KoShapePaste::process(const KoXmlElement & body, KoOdfReadStore & odfStore)
-{
- d->pastedShapes.clear();
- KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
- KoShapeLoadingContext context(loadingContext, d->canvas->shapeController()->resourceManager());
-
- QList<KoShape*> shapes(d->layer ? d->layer->shapes(): d->canvas->shapeManager()->topLevelShapes());
-
- int zIndex = 0;
- if (!shapes.isEmpty()) {
- zIndex = shapes.first()->zIndex();
- foreach (KoShape * shape, shapes) {
- zIndex = qMax(zIndex, shape->zIndex());
- }
- ++zIndex;
- }
- context.setZIndex(zIndex);
-
- KoDocumentResourceManager *rm = d->canvas->shapeController()->resourceManager();
- Q_ASSERT(rm);
-
- QPointF pasteOffset(rm->pasteOffset(), rm->pasteOffset());
- const bool pasteAtCursor = rm->pasteAtCursor();
-
- // get hold of the canvas' shape manager
- KoShapeManager *sm = d->canvas->shapeManager();
- Q_ASSERT(sm);
-
- // TODO if this is a text create a text shape and load the text inside the new shape.
- // create the shape from the clipboard
- KoXmlElement element;
- forEachElement(element, body) {
- debugFlake << "loading shape" << element.localName();
-
- KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(element, context);
- if (shape) {
- d->pastedShapes << shape;
- }
- }
-
- if (d->pastedShapes.isEmpty())
- return true;
-
- // position shapes
- if (pasteAtCursor) {
- QRectF bbox;
- // determine bounding rect of all pasted shapes
- foreach (KoShape *shape, d->pastedShapes) {
- if (bbox.isEmpty())
- bbox = shape->boundingRect();
- else
- bbox |= shape->boundingRect();
- }
- // where is the cursor now?
- QWidget *canvasWidget = d->canvas->canvasWidget();
- KoCanvasController *cc = d->canvas->canvasController();
- // map mouse screen position to the canvas widget coordinates
- QPointF mouseCanvasPos = canvasWidget->mapFromGlobal(QCursor::pos());
- // apply the canvas offset
- mouseCanvasPos -= QPoint(cc->canvasOffsetX(), cc->canvasOffsetY());
- // apply offset of document origin
- mouseCanvasPos -= d->canvas->documentOrigin();
- // convert to document coordinates
- QPointF mouseDocumentPos = d->canvas->viewConverter()->viewToDocument(mouseCanvasPos);
- // now we can determine the offset to apply, with the center of the pasted shapes
- // bounding rect at the current mouse position
- QPointF pasteOffset = mouseDocumentPos - bbox.center();
- foreach (KoShape *shape, d->pastedShapes) {
- QPointF move(pasteOffset);
- d->canvas->clipToDocument(shape, move);
- if (move.x() != 0 || move.y() != 0) {
- shape->setPosition(shape->position() + move);
- }
- }
- } else {
- foreach (KoShape *shape, d->pastedShapes) {
- bool done = true;
- do {
- // find a nice place for our shape.
- done = true;
- foreach (const KoShape *s, sm->shapesAt(shape->boundingRect()) + d->pastedShapes) {
- if (d->layer && s->parent() != d->layer)
- continue;
- if (s->name() != shape->name())
- continue;
- if (qAbs(s->position().x() - shape->position().x()) > 0.001)
- continue;
- if (qAbs(s->position().y() - shape->position().y()) > 0.001)
- continue;
- if (qAbs(s->size().width() - shape->size().width()) > 0.001)
- continue;
- if (qAbs(s->size().height() - shape->size().height()) > 0.001)
- continue;
- // move it and redo our iteration.
- QPointF move(pasteOffset);
- d->canvas->clipToDocument(shape, move);
- if (move.x() != 0 || move.y() != 0) {
- shape->setPosition(shape->position() + move);
- done = false;
- break;
- }
- }
- } while (!done);
- }
- }
-
- KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Shapes"));
- if (!cmd) {
- qDeleteAll(d->pastedShapes);
- d->pastedShapes.clear();
- return false;
- }
-
- // add shapes to the document
- foreach (KoShape *shape, d->pastedShapes) {
- if (!shape->parent()) {
- shape->setParent(d->layer);
- }
- d->canvas->shapeController()->addShapeDirect(shape, cmd);
- }
-
- d->canvas->addCommand(cmd);
-
- return true;
-}
-
-QList<KoShape*> KoShapePaste::pastedShapes() const
-{
- return d->pastedShapes;
-}
diff --git a/libs/flake/KoShapePaste.h b/libs/flake/KoShapePaste.h
deleted file mode 100644
index be8376d766..0000000000
--- a/libs/flake/KoShapePaste.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 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.
-*/
-
-#ifndef KOSHAPEPASTE_H
-#define KOSHAPEPASTE_H
-
-#include <KoOdfPaste.h>
-#include "kritaflake_export.h"
-
-#include <QList>
-
-class KoCanvasBase;
-class KoShapeLayer;
-class KoShape;
-
-/**
- * Class for pasting shapes to the document
- */
-class KRITAFLAKE_EXPORT KoShapePaste : public KoOdfPaste
-{
-public:
- /**
- * Contructor
- *
- * @param canvas The canvas on which the paste is done
- * @param parentLayer The layer on which the shapes will be pasted
- */
- KoShapePaste(KoCanvasBase *canvas, KoShapeLayer *parentLayer);
- virtual ~KoShapePaste();
-
- QList<KoShape*> pastedShapes() const;
-
-protected:
- /// reimplemented
- virtual bool process(const KoXmlElement & body, KoOdfReadStore &odfStore);
-
- class Private;
- Private * const d;
-};
-
-#endif /* KOSHAPEPASTE_H */
diff --git a/libs/flake/KoShapeSavingContext.cpp b/libs/flake/KoShapeSavingContext.cpp
index 613b11bdf8..fa8a8f4f3b 100644
--- a/libs/flake/KoShapeSavingContext.cpp
+++ b/libs/flake/KoShapeSavingContext.cpp
@@ -1,345 +1,346 @@
/* This file is part of the KDE project
Copyright (C) 2004-2006 David Faure <faure@kde.org>
Copyright (C) 2007-2009, 2011 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
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 "KoShapeSavingContext.h"
#include "KoDataCenterBase.h"
#include "KoShapeLayer.h"
#include "KoImageData.h"
#include "KoMarker.h"
#include <KoXmlWriter.h>
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <KoSharedSavingData.h>
#include <KoElementReference.h>
#include <FlakeDebug.h>
#include <QUuid>
#include <QImage>
#include <KisMimeDatabase.h>
class KoShapeSavingContextPrivate {
public:
KoShapeSavingContextPrivate(KoXmlWriter&, KoGenStyles&, KoEmbeddedDocumentSaver&);
~KoShapeSavingContextPrivate();
KoXmlWriter *xmlWriter;
KoShapeSavingContext::ShapeSavingOptions savingOptions;
QList<const KoShapeLayer*> layers;
QSet<KoDataCenterBase *> dataCenters;
QMap<QString, KoSharedSavingData*> sharedData;
QMap<qint64, QString> imageNames;
int imageId;
QMap<QString, QImage> images;
QHash<const KoShape *, QTransform> shapeOffsets;
QMap<const KoMarker *, QString> markerRefs;
KoGenStyles& mainStyles;
KoEmbeddedDocumentSaver& embeddedSaver;
QMap<const void*, KoElementReference> references;
QMap<QString, int> referenceCounters;
QMap<QString, QList<const void*> > prefixedReferences;
};
KoShapeSavingContextPrivate::KoShapeSavingContextPrivate(KoXmlWriter &w,
KoGenStyles &s, KoEmbeddedDocumentSaver &e)
: xmlWriter(&w),
savingOptions(0),
imageId(0),
mainStyles(s),
embeddedSaver(e)
{
}
KoShapeSavingContextPrivate::~KoShapeSavingContextPrivate()
{
Q_FOREACH (KoSharedSavingData * data, sharedData) {
delete data;
}
}
KoShapeSavingContext::KoShapeSavingContext(KoXmlWriter &xmlWriter, KoGenStyles &mainStyles,
KoEmbeddedDocumentSaver &embeddedSaver)
: d(new KoShapeSavingContextPrivate(xmlWriter, mainStyles, embeddedSaver))
{
// by default allow saving of draw:id + xml:id
addOption(KoShapeSavingContext::DrawId);
}
KoShapeSavingContext::~KoShapeSavingContext()
{
delete d;
}
KoXmlWriter & KoShapeSavingContext::xmlWriter()
{
return *d->xmlWriter;
}
void KoShapeSavingContext::setXmlWriter(KoXmlWriter &xmlWriter)
{
d->xmlWriter = &xmlWriter;
}
KoGenStyles & KoShapeSavingContext::mainStyles()
{
return d->mainStyles;
}
KoEmbeddedDocumentSaver &KoShapeSavingContext::embeddedSaver()
{
return d->embeddedSaver;
}
bool KoShapeSavingContext::isSet(ShapeSavingOption option) const
{
return d->savingOptions & option;
}
void KoShapeSavingContext::setOptions(ShapeSavingOptions options)
{
d->savingOptions = options;
}
KoShapeSavingContext::ShapeSavingOptions KoShapeSavingContext::options() const
{
return d->savingOptions;
}
void KoShapeSavingContext::addOption(ShapeSavingOption option)
{
d->savingOptions = d->savingOptions | option;
}
void KoShapeSavingContext::removeOption(ShapeSavingOption option)
{
if (isSet(option))
d->savingOptions = d->savingOptions ^ option; // xor to remove it.
}
KoElementReference KoShapeSavingContext::xmlid(const void *referent, const QString& prefix, KoElementReference::GenerationOption counter)
{
Q_ASSERT(counter == KoElementReference::UUID || (counter == KoElementReference::Counter && !prefix.isEmpty()));
if (d->references.contains(referent)) {
return d->references[referent];
}
KoElementReference ref;
if (counter == KoElementReference::Counter) {
int referenceCounter = d->referenceCounters[prefix];
referenceCounter++;
ref = KoElementReference(prefix, referenceCounter);
d->references.insert(referent, ref);
d->referenceCounters[prefix] = referenceCounter;
}
else {
if (!prefix.isEmpty()) {
ref = KoElementReference(prefix);
d->references.insert(referent, ref);
}
else {
d->references.insert(referent, ref);
}
}
if (!prefix.isNull()) {
d->prefixedReferences[prefix].append(referent);
}
return ref;
}
KoElementReference KoShapeSavingContext::existingXmlid(const void *referent)
{
if (d->references.contains(referent)) {
return d->references[referent];
}
else {
KoElementReference ref;
ref.invalidate();
return ref;
}
}
void KoShapeSavingContext::clearXmlIds(const QString &prefix)
{
if (d->prefixedReferences.contains(prefix)) {
Q_FOREACH (const void* ptr, d->prefixedReferences[prefix]) {
d->references.remove(ptr);
}
d->prefixedReferences.remove(prefix);
}
if (d->referenceCounters.contains(prefix)) {
d->referenceCounters[prefix] = 0;
}
}
void KoShapeSavingContext::addLayerForSaving(const KoShapeLayer *layer)
{
if (layer && ! d->layers.contains(layer))
d->layers.append(layer);
}
void KoShapeSavingContext::saveLayerSet(KoXmlWriter &xmlWriter) const
{
xmlWriter.startElement("draw:layer-set");
Q_FOREACH (const KoShapeLayer * layer, d->layers) {
xmlWriter.startElement("draw:layer");
xmlWriter.addAttribute("draw:name", layer->name());
if (layer->isGeometryProtected())
xmlWriter.addAttribute("draw:protected", "true");
if (! layer->isVisible())
xmlWriter.addAttribute("draw:display", "none");
xmlWriter.endElement(); // draw:layer
}
xmlWriter.endElement(); // draw:layer-set
}
void KoShapeSavingContext::clearLayers()
{
d->layers.clear();
}
QString KoShapeSavingContext::imageHref(const KoImageData *image)
{
QMap<qint64, QString>::iterator it(d->imageNames.find(image->key()));
if (it == d->imageNames.end()) {
QString suffix = image->suffix();
if (suffix.isEmpty()) {
it = d->imageNames.insert(image->key(), QString("Pictures/image%1").arg(++d->imageId));
}
else {
it = d->imageNames.insert(image->key(), QString("Pictures/image%1.%2").arg(++d->imageId).arg(suffix));
}
}
return it.value();
}
QString KoShapeSavingContext::imageHref(const QImage &image)
{
// TODO this can be optimized to recognize images which have the same content
// Also this can use quite a lot of memeory as the qimage are all kept until
// they are saved to the store in memory
QString href = QString("Pictures/image%1.png").arg(++d->imageId);
d->images.insert(href, image);
return href;
}
QMap<qint64, QString> KoShapeSavingContext::imagesToSave()
{
return d->imageNames;
}
QString KoShapeSavingContext::markerRef(const KoMarker *marker)
{
- QMap<const KoMarker *, QString>::iterator it = d->markerRefs.find(marker);
- if (it == d->markerRefs.end()) {
- it = d->markerRefs.insert(marker, marker->saveOdf(*this));
- }
+// QMap<const KoMarker *, QString>::iterator it = d->markerRefs.find(marker);
+// if (it == d->markerRefs.end()) {
+// it = d->markerRefs.insert(marker, marker->saveOdf(*this));
+// }
+// return it.value();
- return it.value();
+ return QString();
}
void KoShapeSavingContext::addDataCenter(KoDataCenterBase * dataCenter)
{
if (dataCenter) {
d->dataCenters.insert(dataCenter);
}
}
bool KoShapeSavingContext::saveDataCenter(KoStore *store, KoXmlWriter* manifestWriter)
{
bool ok = true;
Q_FOREACH (KoDataCenterBase *dataCenter, d->dataCenters) {
ok = ok && dataCenter->completeSaving(store, manifestWriter, this);
//debugFlake << "ok" << ok;
}
// Save images
for (QMap<QString, QImage>::iterator it(d->images.begin()); it != d->images.end(); ++it) {
if (store->open(it.key())) {
KoStoreDevice device(store);
ok = ok && it.value().save(&device, "PNG");
store->close();
// TODO error handling
if (ok) {
const QString mimetype = KisMimeDatabase::mimeTypeForFile(it.key());
manifestWriter->addManifestEntry(it.key(), mimetype);
}
else {
warnFlake << "saving image failed";
}
}
else {
ok = false;
warnFlake << "saving image failed: open store failed";
}
}
return ok;
}
void KoShapeSavingContext::addSharedData(const QString &id, KoSharedSavingData * data)
{
QMap<QString, KoSharedSavingData*>::iterator it(d->sharedData.find(id));
// data will not be overwritten
if (it == d->sharedData.end()) {
d->sharedData.insert(id, data);
} else {
warnFlake << "The id" << id << "is already registered. Data not inserted";
Q_ASSERT(it == d->sharedData.end());
}
}
KoSharedSavingData * KoShapeSavingContext::sharedData(const QString &id) const
{
KoSharedSavingData * data = 0;
QMap<QString, KoSharedSavingData*>::const_iterator it(d->sharedData.constFind(id));
if (it != d->sharedData.constEnd()) {
data = it.value();
}
return data;
}
void KoShapeSavingContext::addShapeOffset(const KoShape *shape, const QTransform &m)
{
d->shapeOffsets.insert(shape, m);
}
void KoShapeSavingContext::removeShapeOffset(const KoShape *shape)
{
d->shapeOffsets.remove(shape);
}
QTransform KoShapeSavingContext::shapeOffset(const KoShape *shape) const
{
return d->shapeOffsets.value(shape, QTransform());
}
diff --git a/libs/flake/KoShapeStroke.cpp b/libs/flake/KoShapeStroke.cpp
index dea3784441..b7c5c096b8 100644
--- a/libs/flake/KoShapeStroke.cpp
+++ b/libs/flake/KoShapeStroke.cpp
@@ -1,262 +1,412 @@
/* 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 "KoMarkerData.h"
+#include "KoMarker.h"
#include "KoInsets.h"
+#include <KoPathSegment.h>
+#include <KoPathPoint.h>
+#include <cmath>
+#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;
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
{
if (!pen.isCosmetic() && pen.style() != Qt::NoPen) {
KoPathShape *pathShape = dynamic_cast<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())
+ : 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())
+ : 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())
+ : 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);
qreal lineWidth = d->pen.widthF();
if (lineWidth < 0) {
lineWidth = 1;
}
lineWidth *= 0.5; // since we draw a line half inside, and half outside the object.
// if we have square cap, we need a little more space
// -> sqrt((0.5*penWidth)^2 + (0.5*penWidth)^2)
if (capStyle() == Qt::SquareCap) {
lineWidth *= M_SQRT2;
}
if (joinStyle() == Qt::MiterJoin) {
lineWidth = qMax(lineWidth, miterLimit());
}
insets.top = lineWidth;
insets.bottom = lineWidth;
insets.left = lineWidth;
insets.right = lineWidth;
}
+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;
}
void KoShapeStroke::paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter)
{
KoShape::applyConversion(painter, converter);
QPen pen = d->pen;
if (d->brush.gradient())
pen.setBrush(d->brush);
else
pen.setColor(d->color);
d->paintBorder(shape, painter, pen);
}
-void KoShapeStroke::paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter, const QColor &color)
+bool KoShapeStroke::compareFillTo(const KoShapeStrokeModel *other)
{
- KoShape::applyConversion(painter, converter);
+ if (!other) return false;
- QPen pen = d->pen;
- pen.setColor(color);
+ const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other);
+ if (!stroke) return false;
- d->paintBorder(shape, painter, pen);
+ 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)
+ if (style < Qt::CustomDashLine) {
d->pen.setStyle(style);
- else
+ } 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 8d8407fc81..56f2945f22 100644
--- a/libs/flake/KoShapeStroke.h
+++ b/libs/flake/KoShapeStroke.h
@@ -1,115 +1,120 @@
/* 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 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);
virtual ~KoShapeStroke();
/// 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.
virtual void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) const;
virtual void strokeInsets(const KoShape *shape, KoInsets &insets) const;
+ virtual qreal strokeMaxMarkersInset(const KoShape *shape) const;
virtual bool hasTransparency() const;
virtual void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter);
- virtual void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter, const QColor &color);
+
+ virtual bool compareFillTo(const KoShapeStrokeModel *other);
+ virtual bool compareStyleTo(const KoShapeStrokeModel *other);
+ virtual bool isVisible() const;
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 7e5161bbcd..d4a05b4bdd 100644
--- a/libs/flake/KoShapeStrokeModel.h
+++ b/libs/flake/KoShapeStrokeModel.h
@@ -1,111 +1,111 @@
/* 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:
KoShapeStrokeModel();
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;
- /**
- * Paint the stroke in the given color
- *
- * This method should paint the stroke around the shape in the given color.
- *
- * @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.
- * @param color to use to paint the stroke.
- */
- virtual void paint(KoShape *shape, QPainter &painter, const KoViewConverter &converter, const QColor &color) = 0;
+ virtual bool compareFillTo(const KoShapeStrokeModel *other) = 0;
+ virtual bool compareStyleTo(const KoShapeStrokeModel *other) = 0;
+ virtual bool isVisible() const = 0;
/**
* 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
diff --git a/libs/flake/KoShapeUserData.cpp b/libs/flake/KoShapeUserData.cpp
index b35464ae82..2d3a922ce9 100644
--- a/libs/flake/KoShapeUserData.cpp
+++ b/libs/flake/KoShapeUserData.cpp
@@ -1,29 +1,35 @@
/* 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.
*/
#include "KoShapeUserData.h"
KoShapeUserData::KoShapeUserData(QObject *parent)
: QObject(parent)
{
}
KoShapeUserData::~KoShapeUserData()
{
}
+
+KoShapeUserData::KoShapeUserData(const KoShapeUserData &rhs)
+ : QObject()
+{
+ Q_UNUSED(rhs);
+}
diff --git a/libs/flake/KoShapeUserData.h b/libs/flake/KoShapeUserData.h
index d4b4656594..edde53f9ea 100644
--- a/libs/flake/KoShapeUserData.h
+++ b/libs/flake/KoShapeUserData.h
@@ -1,54 +1,59 @@
/* 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 KOSHAPEUSERDATA_H
#define KOSHAPEUSERDATA_H
#include <QObject>
#include "kritaflake_export.h"
/**
* The KoShapeUserData class is used to associate custom data with a shape.
*
* KoShapeUserData provides an abstract interface for container classes
* that are used to associate application-specific user data with shapes in KoShape
* Generally, subclasses of this class provide functions to allow data to
* be stored and retrieved, and instances are attached to KoShape using
* KoShape::setUserData(). This makes it possible to store additional data per
* shape in a way that allows applications to not know the implementation of a
* specific KoShape extending class.
*
* Each subclass should provide a reimplementation of the destructor to ensure that
* any private data is automatically cleaned up when user data objects are deleted.
*
* Please note that this object is a QObject to allow a
* <code>qobject_cast<MyData*> (shape->userData())</code> to work which is useful in an environment
* where classes from plugins may not be castable using a static_cast or a dynamic_cast
*/
class KRITAFLAKE_EXPORT KoShapeUserData : public QObject
{
Q_OBJECT
public:
/// Constructor
explicit KoShapeUserData(QObject *parent = 0);
virtual ~KoShapeUserData();
+
+ virtual KoShapeUserData* clone() const = 0;
+
+protected:
+ KoShapeUserData(const KoShapeUserData &rhs);
};
#endif
diff --git a/libs/flake/KoShape_p.h b/libs/flake/KoShape_p.h
index aa26ef464f..6e5b0cb047 100644
--- a/libs/flake/KoShape_p.h
+++ b/libs/flake/KoShape_p.h
@@ -1,120 +1,130 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Boudewijn Rempt <boud@kogmbh.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOSHAPEPRIVATE_H
#define KOSHAPEPRIVATE_H
#include "KoShape.h"
#include <QPoint>
#include <QPaintDevice>
#include <QTransform>
+#include <QScopedPointer>
+
+#include <KoClipMask.h>
class KoBorder;
class KoShapeManager;
class KoShapePrivate
{
public:
explicit KoShapePrivate(KoShape *shape);
virtual ~KoShapePrivate();
+
+ explicit KoShapePrivate(const KoShapePrivate &rhs, KoShape *q);
+
/**
* Notify the shape that a change was done. To be used by inheriting shapes.
* @param type the change type
*/
void shapeChanged(KoShape::ChangeType type);
void addShapeManager(KoShapeManager *manager);
void removeShapeManager(KoShapeManager *manager);
/**
* 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;
/// calls update on the shape where the stroke is.
void updateStroke();
+public:
// Members
KoShape *q_ptr; // Points the shape that owns this class.
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;
- KoShapeUserData *userData;
- KoShapeApplicationData *appData;
- KoShapeStrokeModel *stroke; ///< points to a stroke, or 0 if there is no stroke
+ 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.
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
- KoClipPath * clipPath; ///< the current clip path
+ 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
static const int MaxZIndex = 32767;
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;
- KoShapeAnchor *anchor;
- qreal minimumHeight;
+
+
+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;
Q_DECLARE_PUBLIC(KoShape)
};
#endif
diff --git a/libs/flake/KoSnapGuide.cpp b/libs/flake/KoSnapGuide.cpp
index 353f3e464e..da7f7709a5 100644
--- a/libs/flake/KoSnapGuide.cpp
+++ b/libs/flake/KoSnapGuide.cpp
@@ -1,281 +1,281 @@
/* 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 "KoSnapGuide.h"
#include "KoSnapProxy.h"
#include "KoSnapStrategy.h"
#include <KoPathShape.h>
#include <KoPathPoint.h>
#include <KoViewConverter.h>
#include <KoCanvasBase.h>
#include <QPainter>
#include <math.h>
template <class T>
inline QSharedPointer<T> toQShared(T* ptr) {
return QSharedPointer<T>(ptr);
}
class Q_DECL_HIDDEN KoSnapGuide::Private
{
public:
Private(KoCanvasBase *parentCanvas)
- : canvas(parentCanvas), editedShape(0), currentStrategy(0),
+ : canvas(parentCanvas), additionalEditedShape(0), currentStrategy(0),
active(true),
snapDistance(10)
{
}
~Private()
{
strategies.clear();
}
KoCanvasBase *canvas;
- KoShape *editedShape;
+ KoShape *additionalEditedShape;
typedef QSharedPointer<KoSnapStrategy> KoSnapStrategySP;
typedef QList<KoSnapStrategySP> StrategiesList;
StrategiesList strategies;
KoSnapStrategySP currentStrategy;
KoSnapGuide::Strategies usedStrategies;
bool active;
int snapDistance;
QList<KoPathPoint*> ignoredPoints;
QList<KoShape*> ignoredShapes;
};
KoSnapGuide::KoSnapGuide(KoCanvasBase *canvas)
: d(new Private(canvas))
{
d->strategies.append(toQShared(new GridSnapStrategy()));
d->strategies.append(toQShared(new NodeSnapStrategy()));
d->strategies.append(toQShared(new OrthogonalSnapStrategy()));
d->strategies.append(toQShared(new ExtensionSnapStrategy()));
d->strategies.append(toQShared(new IntersectionSnapStrategy()));
d->strategies.append(toQShared(new BoundingBoxSnapStrategy()));
}
KoSnapGuide::~KoSnapGuide()
{
}
-void KoSnapGuide::setEditedShape(KoShape *shape)
+void KoSnapGuide::setAdditionalEditedShape(KoShape *shape)
{
- d->editedShape = shape;
+ d->additionalEditedShape = shape;
}
-KoShape *KoSnapGuide::editedShape() const
+KoShape *KoSnapGuide::additionalEditedShape() const
{
- return d->editedShape;
+ return d->additionalEditedShape;
}
void KoSnapGuide::enableSnapStrategy(Strategy type, bool value)
{
if (value) {
d->usedStrategies |= type;
} else {
d->usedStrategies &= ~type;
}
}
bool KoSnapGuide::isStrategyEnabled(Strategy type) const
{
return d->usedStrategies & type;
}
void KoSnapGuide::enableSnapStrategies(Strategies strategies)
{
d->usedStrategies = strategies;
}
KoSnapGuide::Strategies KoSnapGuide::enabledSnapStrategies() const
{
return d->usedStrategies;
}
bool KoSnapGuide::addCustomSnapStrategy(KoSnapStrategy *customStrategy)
{
if (!customStrategy || customStrategy->type() != CustomSnapping)
return false;
d->strategies.append(toQShared(customStrategy));
return true;
}
void KoSnapGuide::overrideSnapStrategy(Strategy type, KoSnapStrategy *strategy)
{
for (auto it = d->strategies.begin(); it != d->strategies.end(); /*noop*/) {
if ((*it)->type() == type) {
if (strategy) {
*it = toQShared(strategy);
} else {
it = d->strategies.erase(it);
}
return;
} else {
++it;
}
}
if (strategy) {
d->strategies.append(toQShared(strategy));
}
}
void KoSnapGuide::enableSnapping(bool on)
{
d->active = on;
}
bool KoSnapGuide::isSnapping() const
{
return d->active;
}
void KoSnapGuide::setSnapDistance(int distance)
{
d->snapDistance = qAbs(distance);
}
int KoSnapGuide::snapDistance() const
{
return d->snapDistance;
}
QPointF KoSnapGuide::snap(const QPointF &mousePosition, const QPointF &dragOffset, Qt::KeyboardModifiers modifiers)
{
QPointF pos = mousePosition + dragOffset;
pos = snap(pos, modifiers);
return pos - dragOffset;
}
QPointF KoSnapGuide::snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers)
{
d->currentStrategy.clear();
if (! d->active || (modifiers & Qt::ShiftModifier))
return mousePosition;
KoSnapProxy proxy(this);
qreal minDistance = HUGE_VAL;
qreal maxSnapDistance = d->canvas->viewConverter()->viewToDocument(QSizeF(d->snapDistance,
d->snapDistance)).width();
foreach (Private::KoSnapStrategySP strategy, d->strategies) {
if (d->usedStrategies & strategy->type() ||
strategy->type() == GridSnapping ||
strategy->type() == CustomSnapping) {
if (! strategy->snap(mousePosition, &proxy, maxSnapDistance))
continue;
QPointF snapCandidate = strategy->snappedPosition();
qreal distance = KoSnapStrategy::squareDistance(snapCandidate, mousePosition);
if (distance < minDistance) {
d->currentStrategy = strategy;
minDistance = distance;
}
}
}
if (! d->currentStrategy)
return mousePosition;
return d->currentStrategy->snappedPosition();
}
QRectF KoSnapGuide::boundingRect()
{
QRectF rect;
if (d->currentStrategy) {
rect = d->currentStrategy->decoration(*d->canvas->viewConverter()).boundingRect();
return rect.adjusted(-2, -2, 2, 2);
} else {
return rect;
}
}
void KoSnapGuide::paint(QPainter &painter, const KoViewConverter &converter)
{
if (! d->currentStrategy || ! d->active)
return;
QPainterPath decoration = d->currentStrategy->decoration(converter);
painter.setBrush(Qt::NoBrush);
QPen whitePen(Qt::white, 0);
whitePen.setStyle(Qt::SolidLine);
painter.setPen(whitePen);
painter.drawPath(decoration);
QPen redPen(Qt::red, 0);
redPen.setStyle(Qt::DotLine);
painter.setPen(redPen);
painter.drawPath(decoration);
}
KoCanvasBase *KoSnapGuide::canvas() const
{
return d->canvas;
}
void KoSnapGuide::setIgnoredPathPoints(const QList<KoPathPoint*> &ignoredPoints)
{
d->ignoredPoints = ignoredPoints;
}
QList<KoPathPoint*> KoSnapGuide::ignoredPathPoints() const
{
return d->ignoredPoints;
}
void KoSnapGuide::setIgnoredShapes(const QList<KoShape*> &ignoredShapes)
{
d->ignoredShapes = ignoredShapes;
}
QList<KoShape*> KoSnapGuide::ignoredShapes() const
{
return d->ignoredShapes;
}
void KoSnapGuide::reset()
{
d->currentStrategy.clear();
- d->editedShape = 0;
+ d->additionalEditedShape = 0;
d->ignoredPoints.clear();
d->ignoredShapes.clear();
// remove all custom strategies
int strategyCount = d->strategies.count();
for (int i = strategyCount-1; i >= 0; --i) {
if (d->strategies[i]->type() == CustomSnapping) {
d->strategies.removeAt(i);
}
}
}
diff --git a/libs/flake/KoSnapGuide.h b/libs/flake/KoSnapGuide.h
index 91438b2c7a..f13f2fb651 100644
--- a/libs/flake/KoSnapGuide.h
+++ b/libs/flake/KoSnapGuide.h
@@ -1,159 +1,159 @@
/* 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.
*/
#ifndef KOSNAPGUIDE_H
#define KOSNAPGUIDE_H
#include "kritaflake_export.h"
#include <QScopedPointer>
#include <QList>
#include <Qt>
class KoSnapStrategy;
class KoShape;
class KoPathPoint;
class KoViewConverter;
class KoCanvasBase;
class QPainter;
class QPointF;
class QRectF;
/**
* This class is the place where all the snapping (i.e. snap to grid) is handled.
*
* What this class does is snapping a given position (i.e. mouse position) to various
* snapping targets like grid, boundbox etc.
* The snap guide does not know anything about the specific snapping target. This
* is handled by the different snapping strategies which are derived from KoSnapStrategy.
* Snapping strategies can be enabled/disabled by passing a mask of corresponding
* snapping ids to KoSnapGuide::enableSnapStrategies. There can be one or more snapping
* strategies enabled at the same time. The best result (with the nearest distance to the
* original position) is then returned to the caller of KoSnapGuide::snap.
*
* The snap guide is part of the KoCanvasBase class and thus can be accessed by any tool
* or application via the canvas pointer.
* For letting the user manage which snap stratgies to enable, there is a snap guide config
* widget in guiutils.
*
*/
class KRITAFLAKE_EXPORT KoSnapGuide
{
public:
/// the different possible snap Strategies
enum Strategy
{
OrthogonalSnapping = 1,
NodeSnapping = 2,
ExtensionSnapping = 4,
IntersectionSnapping = 8,
GridSnapping = 0x10,
BoundingBoxSnapping = 0x20,
GuideLineSnapping = 0x40,
DocumentBoundsSnapping = 0x80,
DocumentCenterSnapping = 0x100,
CustomSnapping = 0x200
};
Q_DECLARE_FLAGS(Strategies, Strategy)
/// Creates the snap guide to work on the given canvas
explicit KoSnapGuide(KoCanvasBase *canvas);
virtual ~KoSnapGuide();
/// snaps the mouse position, returns if mouse was snapped
QPointF snap(const QPointF &mousePosition, Qt::KeyboardModifiers modifiers);
QPointF snap(const QPointF &mousePosition, const QPointF &dragOffset, Qt::KeyboardModifiers modifiers);
/// paints the guide
void paint(QPainter &painter, const KoViewConverter &converter);
/// returns the bounding rect of the guide
QRectF boundingRect();
/// Adds an additional shape to snap to (useful when creating a path)
- void setEditedShape(KoShape *shape);
+ void setAdditionalEditedShape(KoShape *shape);
/// returns the extra shapes to use
- KoShape *editedShape() const;
+ KoShape *additionalEditedShape() const;
void enableSnapStrategy(Strategy type, bool value);
bool isStrategyEnabled(Strategy type) const;
/// enables the strategies used for snapping
void enableSnapStrategies(Strategies strategies);
/// returns the enabled snap strategies
KoSnapGuide::Strategies enabledSnapStrategies() const;
/**
* Adds a custom snap strategy
*
* The snap guide take ownership of the strategy. All custom strategies
* are destroyed when calling reset().
*/
bool addCustomSnapStrategy(KoSnapStrategy *customStrategy);
/**
* Overrides the first entry of a strategy \p type with a strategy
* \p strategy. Note that basically strategy->type() may not be equal
* to type and that is ok. \p strategy may also be null.
*/
void overrideSnapStrategy(Strategy type, KoSnapStrategy *strategy);
/// enables the snapping guides
void enableSnapping(bool on);
/// returns if snapping is enabled
bool isSnapping() const;
/// sets the snap distances in pixels
void setSnapDistance(int distance);
/// returns the snap distance in pixels
int snapDistance() const;
/// returns the canvas the snap guide is working on
KoCanvasBase *canvas() const;
/// Sets a list of path points to ignore
void setIgnoredPathPoints(const QList<KoPathPoint*> &ignoredPoints);
/// Returns list of ignored points
QList<KoPathPoint*> ignoredPathPoints() const;
/// Sets list of ignored shapes
void setIgnoredShapes(const QList<KoShape*> &ignoredShapes);
/// Returns list of ignored shapes
QList<KoShape*> ignoredShapes() const;
/// Resets the snap guide
void reset();
private:
class Private;
const QScopedPointer<Private> d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KoSnapGuide::Strategies)
#endif // KOSNAPGUIDE_H
diff --git a/libs/flake/KoSnapProxy.cpp b/libs/flake/KoSnapProxy.cpp
index e377e2c037..71c26d79a9 100644
--- a/libs/flake/KoSnapProxy.cpp
+++ b/libs/flake/KoSnapProxy.cpp
@@ -1,166 +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)
+QList<QPointF> KoSnapProxy::pointsInRect(const QRectF &rect, bool omitEditedShape)
{
QList<QPointF> points;
- QList<KoShape*> shapes = shapesInRect(rect);
+ 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()) {
- int index = shapes.indexOf(shape);
- if (index >= 0)
+ const int index = shapes.indexOf(shape);
+ if (index >= 0) {
shapes.removeAt(index);
+ }
}
- if (! omitEditedShape && m_snapGuide->editedShape()) {
- QRectF bound = m_snapGuide->editedShape()->boundingRect();
+
+
+ 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->editedShape());
+ shapes.append(m_snapGuide->additionalEditedShape());
}
return shapes;
}
QList<QPointF> KoSnapProxy::pointsFromShape(KoShape * shape)
{
QList<QPointF> snapPoints;
// no snapping to hidden shapes
if (! shape->isVisible(true))
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);
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)
+QList<KoPathSegment> KoSnapProxy::segmentsInRect(const QRectF &rect, bool omitEditedShape)
{
- QList<KoShape*> shapes = shapesInRect(rect, true);
+ 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);
// 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(true))
- continue;
- if (ignoredShapes.contains(shape))
- continue;
+ if (shape->isVisible(true) &&
+ !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);
+ }
+ }
+ }
- filteredShapes.append(shape);
+ if (!omitEditedShape && m_snapGuide->additionalEditedShape()) {
+ filteredShapes.append(m_snapGuide->additionalEditedShape());
}
- if (! omitEditedShape && m_snapGuide->editedShape())
- filteredShapes.append(m_snapGuide->editedShape());
return filteredShapes;
}
KoCanvasBase * KoSnapProxy::canvas()
{
return m_snapGuide->canvas();
}
diff --git a/libs/flake/KoSnapProxy.h b/libs/flake/KoSnapProxy.h
index 387814e2d0..76be581b27 100644
--- a/libs/flake/KoSnapProxy.h
+++ b/libs/flake/KoSnapProxy.h
@@ -1,62 +1,62 @@
/* 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.
*/
#ifndef KOSNAPPROXY_H
#define KOSNAPPROXY_H
class KoSnapGuide;
class KoShape;
class KoPathSegment;
class KoCanvasBase;
#include <QList>
#include "kritaflake_export.h"
class QPointF;
class QRectF;
/**
* This class provides access to different shape related snap targets to snap strategies.
*/
class KRITAFLAKE_EXPORT KoSnapProxy
{
public:
KoSnapProxy(KoSnapGuide *snapGuide);
/// returns list of points in given rectangle in document coordinates
- QList<QPointF> pointsInRect(const QRectF &rect);
+ QList<QPointF> pointsInRect(const QRectF &rect, bool omitEditedShape);
/// returns list of shape in given rectangle in document coordinates
QList<KoShape*> shapesInRect(const QRectF &rect, bool omitEditedShape = false);
/// returns list of points from given shape
QList<QPointF> pointsFromShape(KoShape *shape);
/// returns list of points in given rectangle in document coordinates
- QList<KoPathSegment> segmentsInRect(const QRectF &rect);
+ QList<KoPathSegment> segmentsInRect(const QRectF &rect, bool omitEditedShape);
/// returns list of all shapes
QList<KoShape*> shapes(bool omitEditedShape = false);
/// returns canvas we are working on
KoCanvasBase *canvas();
private:
KoSnapGuide *m_snapGuide;
};
#endif
diff --git a/libs/flake/KoSnapStrategy.cpp b/libs/flake/KoSnapStrategy.cpp
index 7935b4454f..f18c1dd23d 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();
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);
+ 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);
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);
+ 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::Position pointId[5] = {
- KoFlake::TopLeftCorner,
- KoFlake::TopRightCorner,
- KoFlake::BottomRightCorner,
- KoFlake::BottomLeftCorner,
- KoFlake::CenteredPosition
+ 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/KoSvgPaste.cpp b/libs/flake/KoSvgPaste.cpp
new file mode 100644
index 0000000000..205cca0285
--- /dev/null
+++ b/libs/flake/KoSvgPaste.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KoSvgPaste.h"
+
+#include <QApplication>
+#include <QClipboard>
+#include <QMimeData>
+
+#include <SvgParser.h>
+#include <KoDocumentResourceManager.h>
+#include <KoXmlReader.h>
+#include <FlakeDebug.h>
+#include <QRectF>
+
+KoSvgPaste::KoSvgPaste()
+{
+}
+
+bool KoSvgPaste::hasShapes() const
+{
+ const QMimeData *mimeData = QApplication::clipboard()->mimeData();
+ return mimeData && mimeData->hasFormat("image/svg+xml");
+}
+
+QList<KoShape*> KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) const
+{
+ QList<KoShape*> shapes;
+
+ const QMimeData *mimeData = QApplication::clipboard()->mimeData();
+ if (!mimeData) return shapes;
+
+ QByteArray data = mimeData->data("image/svg+xml");
+ if (data.isEmpty()) return shapes;
+
+ KoXmlDocument doc;
+
+ QString errorMsg;
+ int errorLine = 0;
+ int errorColumn = 0;
+
+ const bool documentValid = doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn);
+
+ if (!documentValid) {
+ errorFlake << "Failed to process an SVG file at"
+ << errorLine << ":" << errorColumn << "->" << errorMsg;
+ return shapes;
+ }
+
+ KoDocumentResourceManager resourceManager;
+ SvgParser parser(&resourceManager);
+ parser.setResolution(viewportInPx, resolutionPPI);
+
+ shapes = parser.parseSvg(doc.documentElement(), fragmentSize);
+
+ return shapes;
+}
diff --git a/libs/flake/KoSvgPaste.h b/libs/flake/KoSvgPaste.h
new file mode 100644
index 0000000000..98400cf6e1
--- /dev/null
+++ b/libs/flake/KoSvgPaste.h
@@ -0,0 +1,38 @@
+/*
+ * 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 KOSVGPASTE_H
+#define KOSVGPASTE_H
+
+#include "kritaflake_export.h"
+#include <QList>
+
+class KoShape;
+class QRectF;
+class QSizeF;
+
+class KRITAFLAKE_EXPORT KoSvgPaste
+{
+public:
+ KoSvgPaste();
+
+ bool hasShapes() const;
+ QList<KoShape*> fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const;
+};
+
+#endif // KOSVGPASTE_H
diff --git a/libs/flake/KoTextShapeDataBase.cpp b/libs/flake/KoTextShapeDataBase.cpp
index 4077b05dad..115f72f4e4 100644
--- a/libs/flake/KoTextShapeDataBase.cpp
+++ b/libs/flake/KoTextShapeDataBase.cpp
@@ -1,87 +1,96 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2009-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 "KoTextShapeDataBase.h"
#include "KoTextShapeDataBase_p.h"
+#include <QTextDocument>
+
KoTextShapeDataBasePrivate::KoTextShapeDataBasePrivate()
- : document(0)
- , textAlignment(Qt::AlignLeft | Qt::AlignTop)
+ : textAlignment(Qt::AlignLeft | Qt::AlignTop)
, resizeMethod(KoTextShapeDataBase::NoResize)
{
}
+KoTextShapeDataBasePrivate::KoTextShapeDataBasePrivate(const KoTextShapeDataBasePrivate &rhs)
+ : document(rhs.document->clone()),
+ margins(rhs.margins),
+ textAlignment(rhs.textAlignment),
+ resizeMethod(rhs.resizeMethod)
+{
+}
+
KoTextShapeDataBasePrivate::~KoTextShapeDataBasePrivate()
{
}
-KoTextShapeDataBase::KoTextShapeDataBase(KoTextShapeDataBasePrivate &dd)
- : d_ptr(&dd)
+KoTextShapeDataBase::KoTextShapeDataBase(KoTextShapeDataBasePrivate *dd)
+ : d_ptr(dd)
{
}
KoTextShapeDataBase::~KoTextShapeDataBase()
{
delete d_ptr;
}
QTextDocument *KoTextShapeDataBase::document() const
{
Q_D(const KoTextShapeDataBase);
- return d->document;
+ return d->document.data();
}
void KoTextShapeDataBase::setShapeMargins(const KoInsets &margins)
{
Q_D(KoTextShapeDataBase);
d->margins = margins;
}
KoInsets KoTextShapeDataBase::shapeMargins() const
{
Q_D(const KoTextShapeDataBase);
return d->margins;
}
void KoTextShapeDataBase::setVerticalAlignment(Qt::Alignment alignment)
{
Q_D(KoTextShapeDataBase);
d->textAlignment = (d->textAlignment & Qt::AlignHorizontal_Mask)
| (alignment & Qt::AlignVertical_Mask);
}
Qt::Alignment KoTextShapeDataBase::verticalAlignment() const
{
Q_D(const KoTextShapeDataBase);
return d->textAlignment & Qt::AlignVertical_Mask;
}
void KoTextShapeDataBase::setResizeMethod(KoTextShapeDataBase::ResizeMethod method)
{
Q_D(KoTextShapeDataBase);
if (d->resizeMethod == method)
return;
d->resizeMethod = method;
}
KoTextShapeDataBase::ResizeMethod KoTextShapeDataBase::resizeMethod() const
{
Q_D(const KoTextShapeDataBase);
return d->resizeMethod;
}
diff --git a/libs/flake/KoTextShapeDataBase.h b/libs/flake/KoTextShapeDataBase.h
index a7cadf53ae..e217ff7875 100644
--- a/libs/flake/KoTextShapeDataBase.h
+++ b/libs/flake/KoTextShapeDataBase.h
@@ -1,145 +1,145 @@
/* 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 KOTEXTSHAPEDATABASE_H
#define KOTEXTSHAPEDATABASE_H
#include "kritaflake_export.h"
#include "KoShapeUserData.h"
class KoTextShapeDataBasePrivate;
class KoXmlElement;
class KoShapeLoadingContext;
class KoShapeSavingContext;
class KoGenStyle;
struct KoInsets;
class QTextDocument;
/**
* \internal
*/
class KRITAFLAKE_EXPORT KoTextShapeDataBase : public KoShapeUserData
{
Q_OBJECT
public:
/// constructor
KoTextShapeDataBase();
virtual ~KoTextShapeDataBase();
/// return the document
QTextDocument *document() const;
/**
* Set the margins that will make the shapes text area smaller.
* The shape that owns this textShapeData object will layout text in an area
* confined by the shape size made smaller by the margins set here.
* @param margins the margins that shrink the text area.
*/
void setShapeMargins(const KoInsets &margins);
/**
* returns the currently set margins for the shape.
*/
KoInsets shapeMargins() const;
/**
* Load the text from ODF.
*/
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
/**
* Save the text to ODF.
*/
virtual void saveOdf(KoShapeSavingContext &context, int from = 0, int to = -1) const = 0;
/**
* Load the style of the element
*
* This method is used to load the style in case the TextShape is used as TOS. In this case
* the paragraph style of the shape e.g. a custom-shape needs to be applied before we load the
* text so all looks as it should look.
*/
virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
/**
* Save the style of the element
*
* This method save the style in case the TextShape is used as TOS. In this case the paragraph
* style of the shape e.g. a custom-shape needs to be saved with the style of the shape.
*/
virtual void saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0;
/** Sets the vertical alignment of all the text inside the shape. */
void setVerticalAlignment(Qt::Alignment alignment);
/** Returns the vertical alignment of all the text in the shape */
Qt::Alignment verticalAlignment() const;
/**
* Enum to describe the text document's automatic resizing behaviour.
*/
enum ResizeMethod {
/// Resize the shape to fit the content. This makes sure that the text shape takes op
/// only as much space as absolutely necessary to fit the entire text into its boundaries.
AutoResize,
/// Specifies whether or not to automatically increase the width of the drawing object
/// if text is added to fit the entire width of the text into its boundaries.
/// Compared to AutoResize above this only applied to the width whereas the height is
/// not resized. Also this only grows but does not shrink again if text is removed again.
AutoGrowWidth,
/// Specifies whether or not to automatically increase the height of the drawing object
/// if text is added to fit the entire height of the text into its boundaries.
AutoGrowHeight,
/// This combines the AutoGrowWidth and AutoGrowHeight and automatically increase width
/// and height to fit the entire text into its boundaries.
AutoGrowWidthAndHeight,
/// Shrink the content displayed within the shape to match into the shape's boundaries. This
/// will scale the content down as needed to display the whole document.
ShrinkToFitResize,
/// Deactivates auto-resizing. This is the default resizing method.
NoResize
};
/**
* Specifies how the document should be resized upon a change in the document.
*
* If auto-resizing is turned on, text will not be wrapped unless enforced by e.g. a newline.
*
* By default, NoResize is set.
*/
void setResizeMethod(ResizeMethod method);
/**
* Returns the auto-resizing mode. By default, this is NoResize.
*
* @see setResizeMethod
*/
ResizeMethod resizeMethod() const;
protected:
/// constructor
- KoTextShapeDataBase(KoTextShapeDataBasePrivate &);
+ KoTextShapeDataBase(KoTextShapeDataBasePrivate *);
KoTextShapeDataBasePrivate *d_ptr;
private:
Q_DECLARE_PRIVATE(KoTextShapeDataBase)
};
#endif
diff --git a/libs/flake/KoTextShapeDataBase_p.h b/libs/flake/KoTextShapeDataBase_p.h
index 90541f33ce..fc9b813a76 100644
--- a/libs/flake/KoTextShapeDataBase_p.h
+++ b/libs/flake/KoTextShapeDataBase_p.h
@@ -1,41 +1,43 @@
/* 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 KOTEXTSHAPEDATABASE_P_H
#define KOTEXTSHAPEDATABASE_P_H
#include "KoInsets.h"
class QTextDocument;
/// \internal
class KRITAFLAKE_EXPORT KoTextShapeDataBasePrivate
{
public:
KoTextShapeDataBasePrivate();
virtual ~KoTextShapeDataBasePrivate();
- QTextDocument *document;
+ KoTextShapeDataBasePrivate(const KoTextShapeDataBasePrivate &rhs);
+
+ QScopedPointer<QTextDocument> document;
KoInsets margins;
Qt::Alignment textAlignment;
KoTextShapeDataBase::ResizeMethod resizeMethod;
};
#endif
diff --git a/libs/flake/KoToolBase.cpp b/libs/flake/KoToolBase.cpp
index 55dd22c3ea..58cf5f6e5c 100644
--- a/libs/flake/KoToolBase.cpp
+++ b/libs/flake/KoToolBase.cpp
@@ -1,428 +1,437 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2010 Thomas Zander <zander@kde.org>
* 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 <QDebug>
#include <QAction>
#include "KoToolBase.h"
#include "KoToolBase_p.h"
#include "KoCanvasBase.h"
#include "KoPointerEvent.h"
#include "KoDocumentResourceManager.h"
#include "KoCanvasResourceManager.h"
#include "KoViewConverter.h"
#include "KoShapeController.h"
#include "KoShapeBasedDocumentBase.h"
#include "KoToolSelection.h"
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <QWidget>
#include <QFile>
#include <QDomDocument>
#include <QDomElement>
KoToolBase::KoToolBase(KoCanvasBase *canvas)
: d_ptr(new KoToolBasePrivate(this, canvas))
{
Q_D(KoToolBase);
d->connectSignals();
}
KoToolBase::KoToolBase(KoToolBasePrivate &dd)
: d_ptr(&dd)
{
Q_D(KoToolBase);
d->connectSignals();
}
KoToolBase::~KoToolBase()
{
// Enable this to easily generate action files for tools
// if (actions().size() > 0) {
// QDomDocument doc;
// QDomElement e = doc.createElement("Actions");
// e.setAttribute("name", toolId());
// e.setAttribute("version", "2");
// doc.appendChild(e);
// Q_FOREACH (QAction *action, actions().values()) {
// QDomElement a = doc.createElement("Action");
// a.setAttribute("name", action->objectName());
// // But seriously, XML is the worst format ever designed
// auto addElement = [&](QString title, QString content) {
// QDomElement newNode = doc.createElement(title);
// QDomText newText = doc.createTextNode(content);
// newNode.appendChild(newText);
// a.appendChild(newNode);
// };
// addElement("icon", action->icon().name());
// addElement("text", action->text());
// addElement("whatsThis" , action->whatsThis());
// addElement("toolTip" , action->toolTip());
// addElement("iconText" , action->iconText());
// addElement("shortcut" , action->shortcut().toString());
// addElement("isCheckable" , QString((action->isChecked() ? "true" : "false")));
// addElement("statusTip", action->statusTip());
// e.appendChild(a);
// }
// QFile f(toolId() + ".action");
// f.open(QFile::WriteOnly);
// f.write(doc.toString().toUtf8());
// f.close();
// }
// else {
// qDebug() << "Tool" << toolId() << "has no actions";
// }
qDeleteAll(d_ptr->optionWidgets);
delete d_ptr;
}
/// Ultimately only called from Calligra Sheets
void KoToolBase::updateShapeController(KoShapeBasedDocumentBase *shapeController)
{
if (shapeController) {
KoDocumentResourceManager *scrm = shapeController->resourceManager();
if (scrm) {
connect(scrm, SIGNAL(resourceChanged(int, const QVariant &)),
this, SLOT(documentResourceChanged(int, const QVariant &)));
}
}
}
+
+bool KoToolBase::isActivated() const
+{
+ Q_D(const KoToolBase);
+ return d->isActivated;
+}
+
+
+void KoToolBase::activate(KoToolBase::ToolActivation toolActivation, const QSet<KoShape *> &shapes)
+{
+ Q_D(KoToolBase);
+ d->isActivated = true;
+}
+
void KoToolBase::deactivate()
{
+ Q_D(KoToolBase);
+ d->isActivated = false;
}
void KoToolBase::canvasResourceChanged(int key, const QVariant & res)
{
Q_UNUSED(key);
Q_UNUSED(res);
}
void KoToolBase::documentResourceChanged(int key, const QVariant &res)
{
Q_UNUSED(key);
Q_UNUSED(res);
}
bool KoToolBase::wantsAutoScroll() const
{
return true;
}
void KoToolBase::mouseDoubleClickEvent(KoPointerEvent *event)
{
event->ignore();
}
void KoToolBase::mouseTripleClickEvent(KoPointerEvent *event)
{
event->ignore();
}
void KoToolBase::keyPressEvent(QKeyEvent *e)
{
e->ignore();
}
void KoToolBase::keyReleaseEvent(QKeyEvent *e)
{
e->ignore();
}
void KoToolBase::wheelEvent(KoPointerEvent * e)
{
e->ignore();
}
void KoToolBase::touchEvent(QTouchEvent *event)
{
event->ignore();
}
+void KoToolBase::explicitUserStrokeEndRequest()
+{
+}
+
QVariant KoToolBase::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &) const
{
Q_D(const KoToolBase);
if (d->canvas->canvasWidget() == 0)
return QVariant();
switch (query) {
case Qt::ImMicroFocus:
return QRect(d->canvas->canvasWidget()->width() / 2, 0, 1, d->canvas->canvasWidget()->height());
case Qt::ImFont:
return d->canvas->canvasWidget()->font();
default:
return QVariant();
}
}
void KoToolBase::inputMethodEvent(QInputMethodEvent * event)
{
if (! event->commitString().isEmpty()) {
QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString());
keyPressEvent(&ke);
}
event->accept();
}
-void KoToolBase::customPressEvent(KoPointerEvent * event)
-{
- event->ignore();
-}
-
-void KoToolBase::customReleaseEvent(KoPointerEvent * event)
-{
- event->ignore();
-}
-
-void KoToolBase::customMoveEvent(KoPointerEvent * event)
-{
- event->ignore();
-}
-
bool KoToolBase::wantsTouch() const
{
return false;
}
void KoToolBase::useCursor(const QCursor &cursor)
{
Q_D(KoToolBase);
d->currentCursor = cursor;
emit cursorChanged(d->currentCursor);
}
QList<QPointer<QWidget> > KoToolBase::optionWidgets()
{
Q_D(KoToolBase);
if (d->optionWidgets.empty()) {
d->optionWidgets = createOptionWidgets();
}
return d->optionWidgets;
}
void KoToolBase::addAction(const QString &name, QAction *action)
{
Q_D(KoToolBase);
if (action->objectName().isEmpty()) {
action->setObjectName(name);
}
d->actions.insert(name, action);
}
QHash<QString, QAction *> KoToolBase::actions() const
{
Q_D(const KoToolBase);
return d->actions;
}
QAction *KoToolBase::action(const QString &name) const
{
Q_D(const KoToolBase);
return d->actions.value(name);
}
QWidget * KoToolBase::createOptionWidget()
{
return 0;
}
QList<QPointer<QWidget> > KoToolBase::createOptionWidgets()
{
QList<QPointer<QWidget> > ow;
if (QWidget *widget = createOptionWidget()) {
if (widget->objectName().isEmpty()) {
widget->setObjectName(toolId());
}
ow.append(widget);
}
return ow;
}
void KoToolBase::setToolId(const QString &id)
{
Q_D(KoToolBase);
d->toolId = id;
}
QString KoToolBase::toolId() const
{
Q_D(const KoToolBase);
return d->toolId;
}
QCursor KoToolBase::cursor() const
{
Q_D(const KoToolBase);
return d->currentCursor;
}
void KoToolBase::deleteSelection()
{
}
void KoToolBase::cut()
{
copy();
deleteSelection();
}
-QList<QAction*> KoToolBase::popupActionList() const
+QMenu *KoToolBase::popupActionsMenu()
{
- Q_D(const KoToolBase);
- return d->popupActionList;
-}
-
-void KoToolBase::setPopupActionList(const QList<QAction*> &list)
-{
- Q_D(KoToolBase);
- d->popupActionList = list;
+ return 0;
}
KoCanvasBase * KoToolBase::canvas() const
{
Q_D(const KoToolBase);
return d->canvas;
}
void KoToolBase::setStatusText(const QString &statusText)
{
emit statusTextChanged(statusText);
}
uint KoToolBase::handleRadius() const
{
Q_D(const KoToolBase);
if(d->canvas->shapeController()->resourceManager())
{
return d->canvas->shapeController()->resourceManager()->handleRadius();
} else {
return 3;
}
}
uint KoToolBase::grabSensitivity() const
{
Q_D(const KoToolBase);
if(d->canvas->shapeController()->resourceManager())
{
return d->canvas->shapeController()->resourceManager()->grabSensitivity();
} else {
return 3;
}
}
QRectF KoToolBase::handleGrabRect(const QPointF &position) const
{
Q_D(const KoToolBase);
const KoViewConverter * converter = d->canvas->viewConverter();
uint handleSize = 2*grabSensitivity();
QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize));
r.moveCenter(position);
return r;
}
QRectF KoToolBase::handlePaintRect(const QPointF &position) const
{
Q_D(const KoToolBase);
const KoViewConverter * converter = d->canvas->viewConverter();
uint handleSize = 2*handleRadius();
QRectF r = converter->viewToDocument(QRectF(0, 0, handleSize, handleSize));
r.moveCenter(position);
return r;
}
void KoToolBase::setTextMode(bool value)
{
Q_D(KoToolBase);
d->isInTextMode=value;
}
-QStringList KoToolBase::supportedPasteMimeTypes() const
-{
- return QStringList();
-}
-
bool KoToolBase::paste()
{
return false;
}
void KoToolBase::copy() const
{
}
void KoToolBase::dragMoveEvent(QDragMoveEvent *event, const QPointF &point)
{
Q_UNUSED(event);
Q_UNUSED(point);
}
void KoToolBase::dragLeaveEvent(QDragLeaveEvent *event)
{
Q_UNUSED(event);
}
void KoToolBase::dropEvent(QDropEvent *event, const QPointF &point)
{
Q_UNUSED(event);
Q_UNUSED(point);
}
bool KoToolBase::hasSelection()
{
KoToolSelection *sel = selection();
return (sel && sel->hasSelection());
}
KoToolSelection *KoToolBase::selection()
{
return 0;
}
void KoToolBase::repaintDecorations()
{
}
bool KoToolBase::isInTextMode() const
{
Q_D(const KoToolBase);
return d->isInTextMode;
}
+void KoToolBase::requestUndoDuringStroke()
+{
+ /**
+ * Default implementation just cancells the stroke
+ */
+ requestStrokeCancellation();
+}
+
+void KoToolBase::requestStrokeCancellation()
+{
+}
+
+void KoToolBase::requestStrokeEnd()
+{
+}
+
bool KoToolBase::maskSyntheticEvents() const
{
Q_D(const KoToolBase);
return d->maskSyntheticEvents;
}
void KoToolBase::setMaskSyntheticEvents(bool value)
{
Q_D(KoToolBase);
d->maskSyntheticEvents = value;
}
diff --git a/libs/flake/KoToolBase.h b/libs/flake/KoToolBase.h
index 0e8d111294..b19b815840 100644
--- a/libs/flake/KoToolBase.h
+++ b/libs/flake/KoToolBase.h
@@ -1,537 +1,539 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* 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 KOTOOLBASE_H
#define KOTOOLBASE_H
#include <QObject>
#include <QPointer>
#include <QSet>
#include <QList>
#include <QHash>
#include "kritaflake_export.h"
class KoShape;
class KoCanvasBase;
class KoPointerEvent;
class KoViewConverter;
class KoToolSelection;
class KoToolBasePrivate;
class KoShapeBasedDocumentBase;
class QAction;
class QKeyEvent;
class QWidget;
class QCursor;
class QPainter;
class QString;
class QStringList;
class QRectF;
class QPointF;
class QInputMethodEvent;
class QDragMoveEvent;
class QDragLeaveEvent;
class QDropEvent;
class QTouchEvent;
+class QMenu;
/**
* Abstract base class for all tools. Tools can create or manipulate
* flake shapes, canvas state or any other thing that a user may wish
* to do to his document or his view on a document with a pointing
* device.
*
* There exists an instance of every tool for every pointer device.
* These instances are managed by the toolmanager..
*/
class KRITAFLAKE_EXPORT KoToolBase : public QObject
{
Q_OBJECT
public:
/// Option for activate()
enum ToolActivation {
TemporaryActivation, ///< The tool is activated temporarily and works 'in-place' of another one.
DefaultActivation ///< The tool is activated normally and emitting 'done' goes to the defaultTool
};
/**
* Constructor, normally only called by the factory (see KoToolFactoryBase)
* @param canvas the canvas interface this tool will work for.
*/
explicit KoToolBase(KoCanvasBase *canvas);
virtual ~KoToolBase();
/**
* connect the tool to the new shapecontroller. Old connections are removed.
*
* @param shapeController the new shape controller
*/
void updateShapeController(KoShapeBasedDocumentBase *shapeController);
/**
* request a repaint of the decorations to be made. This triggers
* an update call on the canvas, but does not paint directly.
*/
virtual void repaintDecorations();
/**
* Return if dragging (moving with the mouse down) to the edge of a canvas should scroll the
* canvas (default is true).
* @return if this tool wants mouse events to cause scrolling of canvas.
*/
virtual bool wantsAutoScroll() const;
/**
* Called by the canvas to paint any decorations that the tool deems needed.
* The painter has the top left of the canvas as its origin.
* @param painter used for painting the shape
* @param converter to convert between internal and view coordinates.
*/
virtual void paint(QPainter &painter, const KoViewConverter &converter) = 0;
/**
* Return the option widgets for this tool. Create them if they
* do not exist yet. If the tool does not have an option widget,
* this method return an empty list. (After discussion with Thomas, who prefers
* the toolmanager to handle that case.)
*
* @see m_optionWidgets
*/
QList<QPointer<QWidget> > optionWidgets();
/**
* Retrieves the entire collection of actions for the tool.
*/
QHash<QString, QAction *> actions() const;
/**
* Retrieve an action by name.
*/
QAction *action(const QString &name) const;
/**
* Called when (one of) the mouse or stylus buttons is pressed.
* Implementors should call event->ignore() if they do not actually use the event.
* @param event state and reason of this mouse or stylus press
*/
virtual void mousePressEvent(KoPointerEvent *event) = 0;
/**
* Called when (one of) the mouse or stylus buttons is double clicked.
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this mouse or stylus press
*/
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/**
* Called when (one of) the mouse or stylus buttons is triple clicked.
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this mouse or stylus press
*/
virtual void mouseTripleClickEvent(KoPointerEvent *event);
/**
* Called when the mouse or stylus moved over the canvas.
* Implementors should call event->ignore() if they do not actually use the event.
* @param event state and reason of this mouse or stylus move
*/
virtual void mouseMoveEvent(KoPointerEvent *event) = 0;
/**
* Called when (one of) the mouse or stylus buttons is released.
* Implementors should call event->ignore() if they do not actually use the event.
* @param event state and reason of this mouse or stylus release
*/
virtual void mouseReleaseEvent(KoPointerEvent *event) = 0;
/**
* Called when a key is pressed.
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this key press
*/
virtual void keyPressEvent(QKeyEvent *event);
/**
* Called when a key is released
* Implementors should call event->ignore() if they do not actually use the event.
* Default implementation ignores this event.
* @param event state and reason of this key release
*/
virtual void keyReleaseEvent(QKeyEvent *event);
/**
* Called when the scrollwheel is used
* Implementors should call event->ignore() if they do not actually use the event
* @param event state of this wheel event
*/
virtual void wheelEvent(KoPointerEvent *event);
virtual void touchEvent(QTouchEvent *event);
+ /**
+ * @brief explicitUserStrokeEndRequest is called by the input manager
+ * when the user presses Enter key or any equivalent. This callback
+ * comes before requestStrokeEnd(), which comes from a different source.
+ */
+ virtual void explicitUserStrokeEndRequest();
+
/**
* This method is used to query a set of properties of the tool to be
* able to support complex input method operations as support for surrounding
* text and reconversions.
* Default implementation returns simple defaults, for tools that want to provide
* a more responsive text entry experience for CJK languages it would be good to reimplemnt.
* @param query specifies which property is queried.
* @param converter the view converter for the current canvas.
*/
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const;
/**
* Text entry of complex text, like CJK, can be made more interactive if a tool
* implements this and the InputMethodQuery() methods.
* Reimplementing this only provides the user with a more responsive text experience, since the
* default implementation forwards the typed text as key pressed events.
* @param event the input method event.
*/
virtual void inputMethodEvent(QInputMethodEvent *event);
- /**
- * Called when (one of) a custom device buttons is pressed.
- * Implementors should call event->ignore() if they do not actually use the event.
- * @param event state and reason of this custom device press
- */
- virtual void customPressEvent(KoPointerEvent *event);
-
- /**
- * Called when (one of) a custom device buttons is released.
- * Implementors should call event->ignore() if they do not actually use the event.
- * @param event state and reason of this custom device release
- */
- virtual void customReleaseEvent(KoPointerEvent *event);
-
- /**
- * Called when a custom device moved over the canvas.
- * Implementors should call event->ignore() if they do not actually use the event.
- * @param event state and reason of this custom device move
- */
- virtual void customMoveEvent(KoPointerEvent *event);
-
-
-
/**
* @return true if synthetic mouse events on the canvas should be eaten.
*
* For example, the guides tool should allow click and drag with touch,
* while the same touch events should be rejected by the freehand tool.
*
* These events are sent by the OS in Windows
*/
bool maskSyntheticEvents() const;
/**
* @return true if the tool will accept raw QTouchEvents.
*/
virtual bool wantsTouch() const;
/**
* Set the identifier code from the KoToolFactoryBase that created this tool.
* @param id the identifier code
* @see KoToolFactoryBase::id()
*/
void setToolId(const QString &id);
/**
* get the identifier code from the KoToolFactoryBase that created this tool.
* @return the toolId.
* @see KoToolFactoryBase::id()
*/
Q_INVOKABLE QString toolId() const;
/// return the last emitted cursor
QCursor cursor() const;
/**
* Returns the internal selection object of this tool.
* Each tool can have a selection which is private to that tool and the specified shape that it comes with.
* The default returns 0.
*/
virtual KoToolSelection *selection();
/**
* @returns true if the tool has selected data.
*/
virtual bool hasSelection();
/**
* copies the tools selection to the clipboard.
* The default implementation is empty to aid tools that don't have any selection.
* @see selection()
*/
virtual void copy() const;
/**
* Delete the tools selection.
* The default implementation is empty to aid tools that don't have any selection.
* @see selection()
*/
virtual void deleteSelection();
/**
* Cut the tools selection and copy it to the clipboard.
* The default implementation calls copy() and then deleteSelection()
* @see copy()
* @see deleteSelection()
*/
virtual void cut();
/**
* Paste the clipboard selection.
* A tool typically has one or more shapes selected and pasting should do something meaningful
* for this specific shape and tool combination. Inserting text in a text tool, for example.
- * If you reimplement this function make sure to also reimplement supportedPasteMimeTypes().
* @return will return true if pasting succeeded. False if nothing happened.
*/
virtual bool paste();
- /**
- * Returns the mimetypes that this tool's paste() function can handle
- * @return QStringList containing the mimetypes that's supported by paste()
- */
- virtual QStringList supportedPasteMimeTypes() const;
-
/**
* Handle the dragMoveEvent
* A tool typically has one or more shapes selected and dropping into should do
* something meaningful for this specific shape and tool combination. For example
* dropping text in a text tool.
* The tool should Accept the event if it is meaningful; Default implementation does not.
*/
virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point);
/**
* Handle the dragLeaveEvent
* Basically just a noticification that the drag is no long relevant
* The tool should Accept the event if it is meaningful; Default implementation does not.
*/
virtual void dragLeaveEvent(QDragLeaveEvent *event);
/**
* Handle the dropEvent
* A tool typically has one or more shapes selected and dropping into should do
* something meaningful for this specific shape and tool combination. For example
* dropping text in a text tool.
* The tool should Accept the event if it is meaningful; Default implementation does not.
*/
virtual void dropEvent(QDropEvent *event, const QPointF &point);
/**
- * @return A list of actions to be used for a popup.
+ * @return a menu with context-aware actions for the currect selection. If
+ * the returned value is null, no context menu is shown.
*/
- QList<QAction*> popupActionList() const;
+ virtual QMenu* popupActionsMenu();
/// Returns the canvas the tool is working on
KoCanvasBase *canvas() const;
/**
* This method can be reimplemented in a subclass.
* @return returns true, if the tool is in text mode. that means, that there is
* any kind of textual input and all single key shortcuts should be eaten.
*/
bool isInTextMode() const;
+ /**
+ * Called when the user requested undo while the stroke is
+ * active. If you tool supports undo of the part of its actions,
+ * override this method and do the needed work there.
+ *
+ * NOTE: Default implementation forwards this request to
+ * requestStrokeCancellation() method, so that the stroke
+ * would be cancelled.
+ */
+ virtual void requestUndoDuringStroke();
+
+ /**
+ * Called when the user requested the cancellation of the current
+ * stroke. If you tool supports cancelling, override this method
+ * and do the needed work there
+ */
+ virtual void requestStrokeCancellation();
+
+ /**
+ * Called when the image decided that the stroke should better be
+ * ended. If you tool supports long strokes, override this method
+ * and do the needed work there
+ */
+ virtual void requestStrokeEnd();
+
public Q_SLOTS:
/**
* This method is called when this tool instance is activated.
* For any main window there is only one tool active at a time, which then gets all
* user input. Switching between tools will call deactivate on one and activate on the
* new tool allowing the tool to flush items (like a selection)
* when it is not in use.
*
* <p>There is one case where two tools are activated at the same. This is the case
* where one tool delegates work to another temporarily. For example, while shift is
* being held down. The second tool will get activated with temporary=true and
* it should emit done() when the state that activated it is ended.
* <p>One of the important tasks of activate is to call useCursor()
*
* @param shapes the set of shapes that are selected or suggested for editing by a
* selected shape for the tool to work on. Not all shapes will be meant for this
* tool.
* @param toolActivation if TemporaryActivation, this tool is only temporarily actived
* and should emit done when it is done.
* @see deactivate()
*/
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes) = 0;
+ virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
/**
* This method is called whenever this tool is no longer the
* active tool
* @see activate()
*/
virtual void deactivate();
/**
* This method is called whenever a property in the resource
* provider associated with the canvas this tool belongs to
* changes. An example is currently selected foreground color.
*/
virtual void canvasResourceChanged(int key, const QVariant &res);
/**
* This method is called whenever a property in the resource
* provider associated with the document this tool belongs to
* changes. An example is the handle radius
*/
virtual void documentResourceChanged(int key, const QVariant &res);
/**
* This method just relays the given text via the tools statusTextChanged signal.
* @param statusText the new status text
*/
void setStatusText(const QString &statusText);
Q_SIGNALS:
/**
* Emitted when this tool wants itself to be replaced by another tool.
*
* @param id the identification of the desired tool
* @see toolId(), KoToolFactoryBase::id()
*/
void activateTool(const QString &id);
/**
* Emitted when this tool wants itself to temporarily be replaced by another tool.
* For instance, a paint tool could desire to be
* temporarily replaced by a pan tool which could be temporarily
* replaced by a colorpicker.
* @param id the identification of the desired tool
*/
void activateTemporary(const QString &id);
/**
* Emitted when the tool has been temporarily activated and wants
* to notify the world that it's done.
*/
void done();
/**
* Emitted by useCursor() when the cursor to display on the canvas is changed.
* The KoToolManager should connect to this signal to handle cursors further.
*/
void cursorChanged(const QCursor &cursor);
/**
* A tool can have a selection that is copy-able, this signal is emitted when that status changes.
* @param hasSelection is true when the tool holds selected data.
*/
void selectionChanged(bool hasSelection);
/**
* Emitted when the tool wants to display a different status text
* @param statusText the new status text
*/
void statusTextChanged(const QString &statusText);
protected:
/**
* Classes inheriting from this one can call this method to signify which cursor
* the tool wants to display at this time. Logical place to call it is after an
* incoming event has been handled.
* @param cursor the new cursor.
*/
void useCursor(const QCursor &cursor);
/**
* Reimplement this if your tool actually has an option widget.
* Sets the option widget to 0 by default.
*/
virtual QWidget *createOptionWidget();
virtual QList<QPointer<QWidget> > createOptionWidgets();
/**
* Add an action under the given name to the collection.
*
* Inserting an action under a name that is already used for another action will replace
* the other action in the collection.
*
* @param name The name by which the action be retrieved again from the collection.
* @param action The action to add.
* @param readWrite set this to ReadOnlyAction to keep the action available on
* read-only documents
*/
void addAction(const QString &name, QAction *action);
- /**
- * Set the list of actions to be used as popup menu.
- * @param list the list of actions.
- * @see popupActionList
- */
- void setPopupActionList(const QList<QAction*> &list);
-
/// Convenience function to get the current handle radius
uint handleRadius() const;
/// Convencience function to get the current grab sensitivity
uint grabSensitivity() const;
/**
* Returns a handle grab rect at the given position.
*
* The position is expected to be in document coordinates. The grab sensitivity
* canvas resource is used for the dimension of the rectangle.
*
* @return the handle rectangle in document coordinates
*/
QRectF handleGrabRect(const QPointF &position) const;
/**
* Returns a handle paint rect at the given position.
*
* The position is expected to be in document coordinates. The handle radius
* canvas resource is used for the dimension of the rectangle.
*
* @return the handle rectangle in document coordinates
*/
QRectF handlePaintRect(const QPointF &position) const;
/**
* You should set the text mode to true in subclasses, if this tool is in text input mode, eg if the users
* are able to type. If you don't set it, then single key shortcuts will get the key event and not this tool.
*/
void setTextMode(bool value);
/**
* Allows subclasses to specify whether synthetic mouse events should be accepted.
*/
void setMaskSyntheticEvents(bool value);
+ /**
+ * Returns true if activate() has been called (more times than deactivate :) )
+ */
+ bool isActivated() const;
+
protected:
KoToolBase(KoToolBasePrivate &dd);
KoToolBasePrivate *d_ptr;
private:
KoToolBase();
KoToolBase(const KoToolBase&);
KoToolBase& operator=(const KoToolBase&);
Q_DECLARE_PRIVATE(KoToolBase)
};
#endif /* KOTOOL_H */
diff --git a/libs/flake/KoToolBase_p.h b/libs/flake/KoToolBase_p.h
index be79a9497f..e9c6e075b2 100644
--- a/libs/flake/KoToolBase_p.h
+++ b/libs/flake/KoToolBase_p.h
@@ -1,88 +1,89 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 KO GmbH <boud@kogmbh.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOTOOLBASE_P_H
#define KOTOOLBASE_P_H
#include "KoDocumentResourceManager.h"
#include "KoCanvasResourceManager.h"
#include "KoCanvasBase.h"
#include "KoShapeController.h"
#include <QHash>
#include <QWidget>
#include <QString>
#include <QPointer>
#include <string.h> // for the qt version check
class QAction;
class KoToolBase;
class KoToolBasePrivate
{
public:
KoToolBasePrivate(KoToolBase *qq, KoCanvasBase *canvas_)
: currentCursor(Qt::ArrowCursor),
q(qq),
canvas(canvas_),
- isInTextMode(false)
+ isInTextMode(false),
+ isActivated(false)
{
}
~KoToolBasePrivate()
{
Q_FOREACH (QPointer<QWidget> optionWidget, optionWidgets) {
if (optionWidget) {
optionWidget->setParent(0);
delete optionWidget;
}
}
optionWidgets.clear();
}
void connectSignals()
{
if (canvas) { // in the case of KoToolManagers dummytool it can be zero :(
KoCanvasResourceManager * crp = canvas->resourceManager();
Q_ASSERT_X(crp, "KoToolBase::KoToolBase", "No Canvas KoResourceManager");
if (crp)
q->connect(crp, SIGNAL(canvasResourceChanged(int, const QVariant &)),
SLOT(canvasResourceChanged(int, const QVariant &)));
// can be 0 in the case of Calligra Sheets
KoDocumentResourceManager *scrm = canvas->shapeController()->resourceManager();
if (scrm) {
q->connect(scrm, SIGNAL(resourceChanged(int, const QVariant &)),
SLOT(documentResourceChanged(int, const QVariant &)));
}
}
}
QList<QPointer<QWidget> > optionWidgets; ///< the optionwidgets associated with this tool
QCursor currentCursor;
QHash<QString, QAction *> actions;
QString toolId;
- QList<QAction*> popupActionList;
KoToolBase *q;
KoCanvasBase *canvas; ///< the canvas interface this tool will work for.
bool isInTextMode;
bool maskSyntheticEvents{false}; ///< Whether this tool masks synthetic events
+ bool isActivated;
};
#endif
diff --git a/libs/flake/KoToolManager.cpp b/libs/flake/KoToolManager.cpp
index 4cddf63fda..0a1156a286 100644
--- a/libs/flake/KoToolManager.cpp
+++ b/libs/flake/KoToolManager.cpp
@@ -1,1083 +1,1060 @@
/* This file is part of the KDE project
*
* Copyright (c) 2005-2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2006-2008 Thomas Zander <zander@kde.org>
* Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
* 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.
*/
// flake
#include "KoToolManager.h"
#include "KoToolManager_p.h"
#include "KoToolRegistry.h"
#include "KoToolProxy.h"
#include "KoToolProxy_p.h"
#include "KoSelection.h"
#include "KoCanvasController.h"
#include "KoCanvasControllerWidget.h"
#include "KoShape.h"
#include "KoShapeLayer.h"
#include "KoShapeRegistry.h"
#include "KoShapeManager.h"
+#include "KoSelectedShapesProxy.h"
#include "KoCanvasBase.h"
#include "KoInputDeviceHandlerRegistry.h"
#include "KoInputDeviceHandlerEvent.h"
#include "KoPointerEvent.h"
#include "tools/KoCreateShapesTool.h"
#include "tools/KoZoomTool.h"
#include "tools/KoPanTool.h"
#include "kis_action_registry.h"
#include "KoToolFactoryBase.h"
+#include <krita_container_utils.h>
+
// Qt + kde
#include <QWidget>
#include <QEvent>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QTabletEvent>
#include <QKeyEvent>
#include <QVBoxLayout>
#include <QStringList>
#include <QApplication>
#include <kactioncollection.h>
#include <kactioncategory.h>
#include <FlakeDebug.h>
#include <QAction>
#include <klocalizedstring.h>
#include <QKeySequence>
#include <QStack>
#include <QLabel>
#include <QGlobalStatic>
Q_GLOBAL_STATIC(KoToolManager, s_instance)
class CanvasData
{
public:
CanvasData(KoCanvasController *cc, const KoInputDevice &id)
: activeTool(0),
canvas(cc),
inputDevice(id),
dummyToolWidget(0),
dummyToolLabel(0)
{
}
~CanvasData()
{
// the dummy tool widget does not necessarily have a parent and we create it, so we delete it.
delete dummyToolWidget;
}
void activateToolActions()
{
disabledDisabledActions.clear();
disabledActions.clear();
disabledCanvasShortcuts.clear();
// we do several things here
// 1. enable the actions of the active tool
// 2. disable conflicting actions
// 3. replace conflicting actions in the action collection
KActionCollection *canvasActionCollection = canvas->actionCollection();
QHash<QString, QAction *> toolActions = activeTool->actions();
QHash<QString, QAction *>::const_iterator it(toolActions.constBegin());
for (; it != toolActions.constEnd(); ++it) {
if (canvasActionCollection) {
QString toolActionID = it.key();
QAction *toolAction = it.value();
QAction * action = qobject_cast<QAction*>(canvasActionCollection->action(it.key()));
if (action) {
canvasActionCollection->takeAction(action);
if (action != it.value()) {
if (action->isEnabled()) {
action->setEnabled(false);
disabledActions.append(action);
} else {
disabledDisabledActions.append(action);
}
}
}
Q_FOREACH (QAction *a, canvasActionCollection->actions()) {
QAction *canvasAction = dynamic_cast<QAction*>(a);
if (canvasAction && canvasAction->shortcut().toString() != "" && canvasAction->shortcut() == toolAction->shortcut()) {
warnFlake << activeToolId << ": action" << toolActionID << "conflicts with canvas action" << canvasAction->objectName() << "shortcut:" << canvasAction->shortcut().toString();
disabledCanvasShortcuts[canvasAction] = canvasAction->shortcut().toString();
canvasAction->setShortcut(QKeySequence());
}
}
canvasActionCollection->addAction(toolActionID, toolAction);
}
it.value()->setEnabled(true);
}
canvasActionCollection->readSettings(); // The shortcuts might have been configured in the meantime.
}
void deactivateToolActions()
{
if (!activeTool)
return;
// disable actions of active tool
Q_FOREACH (QAction *action, activeTool->actions()) {
action->setEnabled(false);
}
// enable actions which where disabled on activating the active tool
// and re-add them to the action collection
KActionCollection *ac = canvas->actionCollection();
Q_FOREACH (QPointer<QAction> action, disabledDisabledActions) {
if (action) {
if (ac) {
ac->addAction(action->objectName(), action);
}
}
}
disabledDisabledActions.clear();
Q_FOREACH (QPointer<QAction> action, disabledActions) {
if (action) {
action->setEnabled(true);
if(ac) {
ac->addAction(action->objectName(), action);
}
}
}
disabledActions.clear();
QMap<QPointer<QAction>, QString>::const_iterator it(disabledCanvasShortcuts.constBegin());
for (; it != disabledCanvasShortcuts.constEnd(); ++it) {
QAction *action = it.key();
QString shortcut = it.value();
action->setShortcut(shortcut);
}
disabledCanvasShortcuts.clear();
}
KoToolBase *activeTool; // active Tool
QString activeToolId; // the id of the active Tool
QString activationShapeId; // the shape-type (KoShape::shapeId()) the activeTool 'belongs' to.
QHash<QString, KoToolBase*> allTools; // all the tools that are created for this canvas.
QStack<QString> stack; // stack of temporary tools
KoCanvasController *const canvas;
const KoInputDevice inputDevice;
QWidget *dummyToolWidget; // the widget shown in the toolDocker.
QLabel *dummyToolLabel;
QList<QPointer<QAction> > disabledActions; ///< disabled conflicting actions
QList<QPointer<QAction> > disabledDisabledActions; ///< disabled conflicting actions that were already disabled
QMap<QPointer<QAction>, QString> disabledCanvasShortcuts; ///< Shortcuts that were temporarily removed from canvas actions because the tool overrides
};
// ******** KoToolManager **********
KoToolManager::KoToolManager()
: QObject(),
d(new Private(this))
{
connect(QApplication::instance(), SIGNAL(focusChanged(QWidget*, QWidget*)),
this, SLOT(movedFocus(QWidget*, QWidget*)));
}
KoToolManager::~KoToolManager()
{
delete d;
}
QList<KoToolAction*> KoToolManager::toolActionList() const
{
QList<KoToolAction*> answer;
answer.reserve(d->tools.count());
Q_FOREACH (ToolHelper *tool, d->tools) {
if (tool->id() == KoCreateShapesTool_ID)
continue; // don't show this one.
answer.append(tool->toolAction());
}
return answer;
}
void KoToolManager::requestToolActivation(KoCanvasController * controller)
{
if (d->canvasses.contains(controller)) {
QString activeToolId = d->canvasses.value(controller).first()->activeToolId;
Q_FOREACH (ToolHelper * th, d->tools) {
if (th->id() == activeToolId) {
d->toolActivated(th);
break;
}
}
}
}
KoInputDevice KoToolManager::currentInputDevice() const
{
return d->inputDevice;
}
void KoToolManager::registerToolActions(KActionCollection *ac, KoCanvasController *controller)
{
Q_ASSERT(controller);
Q_ASSERT(ac);
d->setup();
if (!d->canvasses.contains(controller)) {
return;
}
// Actions available during the use of individual tools
CanvasData *cd = d->canvasses.value(controller).first();
Q_FOREACH (KoToolBase *tool, cd->allTools) {
QHash<QString, QAction*> actions = tool->actions();
QHash<QString, QAction*>::const_iterator action(actions.constBegin());
for (; action != actions.constEnd(); ++action) {
if (!ac->action(action.key()))
ac->addAction(action.key(), action.value());
}
}
// Actions used to switch tools via shortcuts
Q_FOREACH (ToolHelper * th, d->tools) {
if (ac->action(th->id())) {
continue;
}
ShortcutToolAction* action = th->createShortcutToolAction(ac);
ac->addCategorizedAction(th->id(), action, "tool-shortcuts");
}
}
void KoToolManager::addController(KoCanvasController *controller)
{
Q_ASSERT(controller);
if (d->canvasses.contains(controller))
return;
d->setup();
d->attachCanvas(controller);
connect(controller->proxyObject, SIGNAL(destroyed(QObject*)), this, SLOT(attemptCanvasControllerRemoval(QObject*)));
connect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
connect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
}
void KoToolManager::removeCanvasController(KoCanvasController *controller)
{
Q_ASSERT(controller);
disconnect(controller->proxyObject, SIGNAL(canvasRemoved(KoCanvasController*)), this, SLOT(detachCanvas(KoCanvasController*)));
disconnect(controller->proxyObject, SIGNAL(canvasSet(KoCanvasController*)), this, SLOT(attachCanvas(KoCanvasController*)));
d->detachCanvas(controller);
}
void KoToolManager::attemptCanvasControllerRemoval(QObject* controller)
{
KoCanvasControllerProxyObject* controllerActual = qobject_cast<KoCanvasControllerProxyObject*>(controller);
if (controllerActual) {
removeCanvasController(controllerActual->canvasController());
}
}
void KoToolManager::updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController)
{
if (!d->canvasses.contains(canvasController))
return;
QList<CanvasData *> canvasses = d->canvasses[canvasController];
Q_FOREACH (CanvasData *canvas, canvasses) {
Q_FOREACH (KoToolBase *tool, canvas->allTools.values()) {
tool->updateShapeController(shapeController);
}
}
}
void KoToolManager::switchToolRequested(const QString & id)
{
Q_ASSERT(d->canvasData);
if (!d->canvasData) return;
while (!d->canvasData->stack.isEmpty()) // switching means to flush the stack
d->canvasData->stack.pop();
d->switchTool(id, false);
}
void KoToolManager::switchInputDeviceRequested(const KoInputDevice &id)
{
if (!d->canvasData) return;
d->switchInputDevice(id);
}
void KoToolManager::switchToolTemporaryRequested(const QString &id)
{
d->switchTool(id, true);
}
void KoToolManager::switchBackRequested()
{
if (!d->canvasData) return;
if (d->canvasData->stack.isEmpty()) {
// default to changing to the interactionTool
d->switchTool(KoInteractionTool_ID, false);
return;
}
d->switchTool(d->canvasData->stack.pop(), false);
}
KoCreateShapesTool * KoToolManager::shapeCreatorTool(KoCanvasBase *canvas) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas) {
KoCreateShapesTool *createTool = dynamic_cast<KoCreateShapesTool*>
(d->canvasData->allTools.value(KoCreateShapesTool_ID));
Q_ASSERT(createTool /* ID changed? */);
return createTool;
}
}
Q_ASSERT(0); // this should not happen
return 0;
}
KoToolBase *KoToolManager::toolById(KoCanvasBase *canvas, const QString &id) const
{
Q_ASSERT(canvas);
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
if (controller->canvas() == canvas)
return d->canvasData->allTools.value(id);
}
return 0;
}
KoCanvasController *KoToolManager::activeCanvasController() const
{
if (! d->canvasData) return 0;
return d->canvasData->canvas;
}
QString KoToolManager::preferredToolForSelection(const QList<KoShape*> &shapes)
{
QList<QString> types;
- Q_FOREACH (KoShape *shape, shapes)
- if (! types.contains(shape->shapeId()))
- types.append(shape->shapeId());
+ Q_FOREACH (KoShape *shape, shapes) {
+ types << shape->shapeId();
+ }
+
+ KritaUtils::makeContainerUnique(types);
QString toolType = KoInteractionTool_ID;
int prio = INT_MAX;
Q_FOREACH (ToolHelper *helper, d->tools) {
+ if (helper->id() == KoCreateShapesTool_ID) continue;
+
if (helper->priority() >= prio)
continue;
- if (helper->section() == KoToolFactoryBase::mainToolType())
- continue;
bool toolWillWork = false;
foreach (const QString &type, types) {
if (helper->activationShapeId().split(',').contains(type)) {
toolWillWork = true;
break;
}
}
+
if (toolWillWork) {
toolType = helper->id();
prio = helper->priority();
}
}
return toolType;
}
-void KoToolManager::injectDeviceEvent(KoInputDeviceHandlerEvent * event)
-{
- if (d->canvasData && d->canvasData->canvas->canvas()) {
- if (static_cast<KoInputDeviceHandlerEvent::Type>(event->type()) == KoInputDeviceHandlerEvent::ButtonPressed)
- d->canvasData->activeTool->customPressEvent(event->pointerEvent());
- else if (static_cast<KoInputDeviceHandlerEvent::Type>(event->type()) == KoInputDeviceHandlerEvent::ButtonReleased)
- d->canvasData->activeTool->customReleaseEvent(event->pointerEvent());
- else if (static_cast<KoInputDeviceHandlerEvent::Type>(event->type()) == KoInputDeviceHandlerEvent::PositionChanged)
- d->canvasData->activeTool->customMoveEvent(event->pointerEvent());
- }
-}
-
void KoToolManager::addDeferredToolFactory(KoToolFactoryBase *toolFactory)
{
ToolHelper *tool = new ToolHelper(toolFactory);
// make sure all plugins are loaded as otherwise we will not load them
d->setup();
d->tools.append(tool);
// connect to all tools so we can hear their button-clicks
connect(tool, SIGNAL(toolActivated(ToolHelper*)), this, SLOT(toolActivated(ToolHelper*)));
// now create tools for all existing canvases
Q_FOREACH (KoCanvasController *controller, d->canvasses.keys()) {
// this canvascontroller is unknown, which is weird
if (!d->canvasses.contains(controller)) {
continue;
}
// create a tool for all canvasdata objects (i.e., all input devices on this canvas)
foreach (CanvasData *cd, d->canvasses[controller]) {
QPair<QString, KoToolBase*> toolPair = createTools(controller, tool);
if (toolPair.second) {
cd->allTools.insert(toolPair.first, toolPair.second);
}
}
// Then create a button for the toolbox for this canvas
if (tool->id() == KoCreateShapesTool_ID) {
continue;
}
emit addedTool(tool->toolAction(), controller);
}
}
QPair<QString, KoToolBase*> KoToolManager::createTools(KoCanvasController *controller, ToolHelper *tool)
{
// XXX: maybe this method should go into the private class?
QHash<QString, KoToolBase*> origHash;
if (d->canvasses.contains(controller)) {
origHash = d->canvasses.value(controller).first()->allTools;
}
if (origHash.contains(tool->id())) {
return QPair<QString, KoToolBase*>(tool->id(), origHash.value(tool->id()));
}
debugFlake << "Creating tool" << tool->id() << ". Activated on:" << tool->activationShapeId() << ", prio:" << tool->priority();
KoToolBase *tl = tool->createTool(controller->canvas());
if (tl) {
d->uniqueToolIds.insert(tl, tool->uniqueId());
tl->setObjectName(tool->id());
Q_FOREACH (QAction *action, tl->actions()) {
action->setEnabled(false);
}
}
KoZoomTool *zoomTool = dynamic_cast<KoZoomTool*>(tl);
if (zoomTool) {
zoomTool->setCanvasController(controller);
}
KoPanTool *panTool = dynamic_cast<KoPanTool*>(tl);
if (panTool) {
panTool->setCanvasController(controller);
}
return QPair<QString, KoToolBase*>(tool->id(), tl);
}
// NOT IMPLEMENTED
void KoToolManager::updateToolShortcuts()
{
// auto actionRegistry = KisActionRegistry::instance();
// foreach (KoToolBase *t, allTools) {
// for (auto it = t->actions().constBegin();
// it != t->actions().constEnd();
// ++it;) {
// actionRegistry->updateShortcut(it.key(), it.value());
// }
// }
}
void KoToolManager::initializeCurrentToolForCanvas()
{
d->postSwitchTool(false);
}
KoToolManager* KoToolManager::instance()
{
return s_instance;
}
QString KoToolManager::activeToolId() const
{
if (!d->canvasData) return QString();
return d->canvasData->activeToolId;
}
KoToolManager::Private *KoToolManager::priv()
{
return d;
}
/**** KoToolManager::Private ****/
KoToolManager::Private::Private(KoToolManager *qq)
: q(qq),
canvasData(0),
layerExplicitlyDisabled(false)
{
}
KoToolManager::Private::~Private()
{
qDeleteAll(tools);
}
// helper method.
CanvasData *KoToolManager::Private::createCanvasData(KoCanvasController *controller, const KoInputDevice &device)
{
QHash<QString, KoToolBase*> toolsHash;
Q_FOREACH (ToolHelper *tool, tools) {
QPair<QString, KoToolBase*> toolPair = q->createTools(controller, tool);
if (toolPair.second) { // only if a real tool was created
toolsHash.insert(toolPair.first, toolPair.second);
}
}
KoCreateShapesTool *createShapesTool = dynamic_cast<KoCreateShapesTool*>(toolsHash.value(KoCreateShapesTool_ID));
Q_ASSERT(createShapesTool);
QString id = KoShapeRegistry::instance()->keys()[0];
createShapesTool->setShapeId(id);
CanvasData *cd = new CanvasData(controller, device);
cd->allTools = toolsHash;
return cd;
}
void KoToolManager::Private::setup()
{
if (tools.size() > 0)
return;
KoShapeRegistry::instance();
KoToolRegistry *registry = KoToolRegistry::instance();
Q_FOREACH (const QString & id, registry->keys()) {
ToolHelper *t = new ToolHelper(registry->value(id));
tools.append(t);
}
// connect to all tools so we can hear their button-clicks
Q_FOREACH (ToolHelper *tool, tools)
connect(tool, SIGNAL(toolActivated(ToolHelper*)), q, SLOT(toolActivated(ToolHelper*)));
// load pluggable input devices
KoInputDeviceHandlerRegistry::instance();
}
void KoToolManager::Private::connectActiveTool()
{
if (canvasData->activeTool) {
connect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor &)),
q, SLOT(updateCursor(const QCursor &)));
connect(canvasData->activeTool, SIGNAL(activateTool(const QString &)),
q, SLOT(switchToolRequested(const QString &)));
connect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)),
q, SLOT(switchToolTemporaryRequested(const QString &)));
connect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
connect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)),
q, SIGNAL(changedStatusText(const QString &)));
}
// we expect the tool to emit a cursor on activation.
updateCursor(Qt::ForbiddenCursor);
}
void KoToolManager::Private::disconnectActiveTool()
{
if (canvasData->activeTool) {
canvasData->deactivateToolActions();
// repaint the decorations before we deactivate the tool as it might deleted
// data needed for the repaint
+ emit q->aboutToChangeTool(canvasData->canvas);
canvasData->activeTool->deactivate();
disconnect(canvasData->activeTool, SIGNAL(cursorChanged(const QCursor&)),
q, SLOT(updateCursor(const QCursor&)));
disconnect(canvasData->activeTool, SIGNAL(activateTool(const QString &)),
q, SLOT(switchToolRequested(const QString &)));
disconnect(canvasData->activeTool, SIGNAL(activateTemporary(const QString &)),
q, SLOT(switchToolTemporaryRequested(const QString &)));
disconnect(canvasData->activeTool, SIGNAL(done()), q, SLOT(switchBackRequested()));
disconnect(canvasData->activeTool, SIGNAL(statusTextChanged(const QString &)),
q, SIGNAL(changedStatusText(const QString &)));
}
// emit a empty status text to clear status text from last active tool
emit q->changedStatusText(QString());
}
void KoToolManager::Private::switchTool(KoToolBase *tool, bool temporary)
{
Q_ASSERT(tool);
if (canvasData == 0)
return;
if (canvasData->activeTool == tool && tool->toolId() != KoInteractionTool_ID)
return;
disconnectActiveTool();
canvasData->activeTool = tool;
connectActiveTool();
postSwitchTool(temporary);
}
void KoToolManager::Private::switchTool(const QString &id, bool temporary)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (canvasData->activeTool && temporary)
canvasData->stack.push(canvasData->activeToolId);
canvasData->activeToolId = id;
KoToolBase *tool = canvasData->allTools.value(id);
if (! tool) {
return;
}
Q_FOREACH (ToolHelper *th, tools) {
if (th->id() == id) {
canvasData->activationShapeId = th->activationShapeId();
break;
}
}
switchTool(tool, temporary);
}
void KoToolManager::Private::postSwitchTool(bool temporary)
{
#ifndef NDEBUG
int canvasCount = 1;
Q_FOREACH (QList<CanvasData*> list, canvasses) {
bool first = true;
Q_FOREACH (CanvasData *data, list) {
if (first) {
debugFlake << "Canvas" << canvasCount++;
}
debugFlake << " +- Tool:" << data->activeToolId << (data == canvasData ? " *" : "");
first = false;
}
}
#endif
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase::ToolActivation toolActivation;
if (temporary)
toolActivation = KoToolBase::TemporaryActivation;
else
toolActivation = KoToolBase::DefaultActivation;
QSet<KoShape*> shapesToOperateOn;
if (canvasData->activeTool
&& canvasData->activeTool->canvas()
&& canvasData->activeTool->canvas()->shapeManager()) {
KoSelection *selection = canvasData->activeTool->canvas()->shapeManager()->selection();
Q_ASSERT(selection);
- Q_FOREACH (KoShape *shape, selection->selectedShapes()) {
- QSet<KoShape*> delegates = shape->toolDelegates();
- if (delegates.isEmpty()) { // no delegates, just the orig shape
- shapesToOperateOn << shape;
- } else {
- shapesToOperateOn += delegates;
- }
- }
+ shapesToOperateOn = QSet<KoShape*>::fromList(selection->selectedEditableShapesAndDelegates());
}
if (canvasData->canvas->canvas()) {
// Caller of postSwitchTool expect this to be called to update the selected tool
updateToolForProxy();
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
KoCanvasBase *canvas = canvasData->canvas->canvas();
canvas->updateInputMethodInfo();
} else {
canvasData->activeTool->activate(toolActivation, shapesToOperateOn);
}
QList<QPointer<QWidget> > optionWidgetList = canvasData->activeTool->optionWidgets();
if (optionWidgetList.empty()) { // no option widget.
QWidget *toolWidget;
QString title;
Q_FOREACH (ToolHelper *tool, tools) {
if (tool->id() == canvasData->activeTool->toolId()) {
title = tool->toolTip();
break;
}
}
toolWidget = canvasData->dummyToolWidget;
if (toolWidget == 0) {
toolWidget = new QWidget();
toolWidget->setObjectName("DummyToolWidget");
QVBoxLayout *layout = new QVBoxLayout(toolWidget);
layout->setMargin(3);
canvasData->dummyToolLabel = new QLabel(toolWidget);
layout->addWidget(canvasData->dummyToolLabel);
layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
toolWidget->setLayout(layout);
canvasData->dummyToolWidget = toolWidget;
}
canvasData->dummyToolLabel->setText(i18n("Active tool: %1", title));
optionWidgetList.append(toolWidget);
}
// Activate the actions for the currently active tool
canvasData->activateToolActions();
emit q->changedTool(canvasData->canvas, uniqueToolIds.value(canvasData->activeTool));
emit q->toolOptionWidgetsChanged(canvasData->canvas, optionWidgetList);
}
void KoToolManager::Private::switchCanvasData(CanvasData *cd)
{
Q_ASSERT(cd);
KoCanvasBase *oldCanvas = 0;
KoInputDevice oldInputDevice;
if (canvasData) {
oldCanvas = canvasData->canvas->canvas();
oldInputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
disconnectActiveTool();
}
KoToolProxy *proxy = proxies.value(oldCanvas);
Q_ASSERT(proxy);
proxy->setActiveTool(0);
}
canvasData = cd;
inputDevice = canvasData->inputDevice;
if (canvasData->activeTool) {
connectActiveTool();
postSwitchTool(false);
}
if (oldInputDevice != canvasData->inputDevice) {
emit q->inputDeviceChanged(canvasData->inputDevice);
}
if (oldCanvas != canvasData->canvas->canvas()) {
emit q->changedCanvas(canvasData->canvas->canvas());
}
}
void KoToolManager::Private::toolActivated(ToolHelper *tool)
{
Q_ASSERT(tool);
Q_ASSERT(canvasData);
if (!canvasData) return;
KoToolBase *t = canvasData->allTools.value(tool->id());
Q_ASSERT(t);
canvasData->activeToolId = tool->id();
canvasData->activationShapeId = tool->activationShapeId();
switchTool(t, false);
}
void KoToolManager::Private::detachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
// check if we are removing the active canvas controller
if (canvasData && canvasData->canvas == controller) {
KoCanvasController *newCanvas = 0;
// try to find another canvas controller beside the one we are removing
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas != controller) {
// yay found one
newCanvas = canvas;
break;
}
}
if (newCanvas) {
switchCanvasData(canvasses.value(newCanvas).first());
} else {
emit q->toolOptionWidgetsChanged(controller, QList<QPointer<QWidget> >());
// as a last resort just set a blank one
canvasData = 0;
}
}
KoToolProxy *proxy = proxies.value(controller->canvas());
if (proxy)
proxy->setActiveTool(0);
QList<KoToolBase *> tools;
Q_FOREACH (CanvasData *canvasData, canvasses.value(controller)) {
Q_FOREACH (KoToolBase *tool, canvasData->allTools) {
if (! tools.contains(tool)) {
tools.append(tool);
}
}
delete canvasData;
}
Q_FOREACH (KoToolBase *tool, tools) {
uniqueToolIds.remove(tool);
delete tool;
}
canvasses.remove(controller);
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::attachCanvas(KoCanvasController *controller)
{
Q_ASSERT(controller);
CanvasData *cd = createCanvasData(controller, KoInputDevice::mouse());
// switch to new canvas as the active one.
switchCanvasData(cd);
inputDevice = cd->inputDevice;
QList<CanvasData*> canvasses_;
canvasses_.append(cd);
canvasses[controller] = canvasses_;
KoToolProxy *tp = proxies[controller->canvas()];
if (tp)
tp->priv()->setCanvasController(controller);
if (cd->activeTool == 0) {
// no active tool, so we activate the highest priority main tool
int highestPriority = INT_MAX;
ToolHelper * helper = 0;
Q_FOREACH (ToolHelper * th, tools) {
if (th->section() == KoToolFactoryBase::mainToolType()) {
if (th->priority() < highestPriority) {
highestPriority = qMin(highestPriority, th->priority());
helper = th;
}
}
}
if (helper)
toolActivated(helper);
}
Connector *connector = new Connector(controller->canvas()->shapeManager());
connect(connector, SIGNAL(selectionChanged(QList<KoShape*>)), q,
SLOT(selectionChanged(QList<KoShape*>)));
- connect(controller->canvas()->shapeManager()->selection(),
+ connect(controller->canvas()->selectedShapesProxy(),
SIGNAL(currentLayerChanged(const KoShapeLayer*)),
q, SLOT(currentLayerChanged(const KoShapeLayer*)));
emit q->changedCanvas(canvasData ? canvasData->canvas->canvas() : 0);
}
void KoToolManager::Private::movedFocus(QWidget *from, QWidget *to)
{
Q_UNUSED(from);
// no canvas anyway or no focus set anyway?
if (!canvasData || to == 0) {
return;
}
// Check if this app is about QWidget-based KoCanvasControllerWidget canvasses
// XXX: Focus handling for non-qwidget based canvases!
KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(canvasData->canvas);
if (!canvasControllerWidget) {
return;
}
// canvasWidget is set as focusproxy for KoCanvasControllerWidget,
// so all focus checks are to be done against canvasWidget objects
// focus returned to current canvas?
if (to == canvasData->canvas->canvas()->canvasWidget()) {
// nothing to do
return;
}
// if the 'to' is one of our canvasWidgets, then switch.
// for code simplicity the current canvas will be checked again,
// but would have been catched already in the lines above, so no issue
KoCanvasController *newCanvas = 0;
Q_FOREACH (KoCanvasController* canvas, canvasses.keys()) {
if (canvas->canvas()->canvasWidget() == to) {
newCanvas = canvas;
break;
}
}
// none of our canvasWidgets got focus?
if (newCanvas == 0) {
return;
}
// switch to canvasdata matching inputdevice used last with this app instance
Q_FOREACH (CanvasData *data, canvasses.value(newCanvas)) {
if (data->inputDevice == inputDevice) {
switchCanvasData(data);
return;
}
}
// if no such inputDevice for this canvas, then simply fallback to first one
switchCanvasData(canvasses.value(newCanvas).first());
}
void KoToolManager::Private::updateCursor(const QCursor &cursor)
{
Q_ASSERT(canvasData);
Q_ASSERT(canvasData->canvas);
Q_ASSERT(canvasData->canvas->canvas());
canvasData->canvas->canvas()->setCursor(cursor);
}
void KoToolManager::Private::selectionChanged(const QList<KoShape*> &shapes)
{
QList<QString> types;
Q_FOREACH (KoShape *shape, shapes) {
QSet<KoShape*> delegates = shape->toolDelegates();
if (delegates.isEmpty()) { // no delegates, just the orig shape
delegates << shape;
}
foreach (KoShape *shape2, delegates) {
Q_ASSERT(shape2);
if (! types.contains(shape2->shapeId())) {
types.append(shape2->shapeId());
}
}
}
// check if there is still a shape selected the active tool can work on
// there needs to be at least one shape for a tool without an activationShapeId
// to work
// if not change the current tool to the default tool
if (!(canvasData->activationShapeId.isNull() && shapes.size() > 0)
&& canvasData->activationShapeId != "flake/always"
&& canvasData->activationShapeId != "flake/edit") {
bool currentToolWorks = false;
foreach (const QString &type, types) {
if (canvasData->activationShapeId.split(',').contains(type)) {
currentToolWorks = true;
break;
}
}
if (!currentToolWorks) {
switchTool(KoInteractionTool_ID, false);
}
}
emit q->toolCodesSelected(types);
}
void KoToolManager::Private::currentLayerChanged(const KoShapeLayer *layer)
{
emit q->currentLayerChanged(canvasData->canvas, layer);
layerExplicitlyDisabled = layer && !layer->isEditable();
updateToolForProxy();
debugFlake << "Layer changed to" << layer << "explicitly disabled:" << layerExplicitlyDisabled;
}
void KoToolManager::Private::updateToolForProxy()
{
KoToolProxy *proxy = proxies.value(canvasData->canvas->canvas());
if(!proxy) return;
bool canUseTool = !layerExplicitlyDisabled || canvasData->activationShapeId.endsWith(QLatin1String("/always"));
proxy->setActiveTool(canUseTool ? canvasData->activeTool : 0);
}
void KoToolManager::Private::switchInputDevice(const KoInputDevice &device)
{
Q_ASSERT(canvasData);
if (!canvasData) return;
if (inputDevice == device) return;
if (inputDevice.isMouse() && device.isMouse()) return;
if (device.isMouse() && !inputDevice.isMouse()) {
// we never switch back to mouse from a tablet input device, so the user can use the
// mouse to edit the settings for a tool activated by a tablet. See bugs
// https://bugs.kde.org/show_bug.cgi?id=283130 and https://bugs.kde.org/show_bug.cgi?id=285501.
// We do continue to switch between tablet devices, thought.
return;
}
QList<CanvasData*> items = canvasses[canvasData->canvas];
// disable all actions for all tools in the all canvasdata objects for this canvas.
Q_FOREACH (CanvasData *cd, items) {
Q_FOREACH (KoToolBase* tool, cd->allTools) {
Q_FOREACH (QAction * action, tool->actions()) {
action->setEnabled(false);
}
}
}
// search for a canvasdata object for the current input device
Q_FOREACH (CanvasData *cd, items) {
if (cd->inputDevice == device) {
switchCanvasData(cd);
if (!canvasData->activeTool) {
switchTool(KoInteractionTool_ID, false);
}
return;
}
}
// still here? That means we need to create a new CanvasData instance with the current InputDevice.
CanvasData *cd = createCanvasData(canvasData->canvas, device);
// switch to new canvas as the active one.
QString oldTool = canvasData->activeToolId;
items.append(cd);
canvasses[cd->canvas] = items;
switchCanvasData(cd);
q->switchToolRequested(oldTool);
}
void KoToolManager::Private::registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas)
{
proxies.insert(canvas, proxy);
Q_FOREACH (KoCanvasController *controller, canvasses.keys()) {
if (controller->canvas() == canvas) {
proxy->priv()->setCanvasController(controller);
break;
}
}
}
-void KoToolManager::Private::switchToolByShortcut(QKeyEvent *event)
-{
- QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers()));
-
- if (event->key() == Qt::Key_Space && event->modifiers() == 0) {
- switchTool(KoPanTool_ID, true);
- } else if (event->key() == Qt::Key_Escape && event->modifiers() == 0) {
- switchTool(KoInteractionTool_ID, false);
- }
-}
-
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoToolManager.cpp"
diff --git a/libs/flake/KoToolManager.h b/libs/flake/KoToolManager.h
index b4cbc879a9..261982df80 100644
--- a/libs/flake/KoToolManager.h
+++ b/libs/flake/KoToolManager.h
@@ -1,348 +1,351 @@
/* This file is part of the KDE project
* Copyright (c) 2005-2006 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2006, 2008 Thomas Zander <zander@kde.org>
* 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.
*/
#ifndef KO_TOOL_MANAGER
#define KO_TOOL_MANAGER
#include "KoInputDevice.h"
#include "kritaflake_export.h"
#include <QObject>
#include <QList>
class KoCanvasController;
class KoShapeBasedDocumentBase;
class KoToolFactoryBase;
class KoCanvasBase;
class KoToolBase;
class KoCreateShapesTool;
class KActionCollection;
class KoShape;
class KoInputDeviceHandlerEvent;
class KoShapeLayer;
class ToolHelper;
class QKeySequence;
class QCursor;
/**
* This class serves as a QAction-like control object for activation of a tool.
*
* It allows to implement a custom UI to control the activation of tools.
* See KoToolBox & KoModeBox in the kowidgets library.
*
* KoToolAction objects are indirectly owned by the KoToolManager singleton
* and live until the end of its lifetime.
*/
class KRITAFLAKE_EXPORT KoToolAction : public QObject
{
Q_OBJECT
public:
// toolHelper takes over ownership, and those live till the end of KoToolManager.
explicit KoToolAction(ToolHelper *toolHelper);
~KoToolAction();
public:
QString id() const; ///< The id of the tool
QString iconText() const; ///< The icontext of the tool
QString toolTip() const; ///< The tooltip of the tool
QString iconName() const; ///< The icon name of the tool
QKeySequence shortcut() const; ///< The shortcut to activate the tool
QString section() const; ///< The section the tool wants to be in.
int priority() const; ///< Lower number (higher priority) means coming first in the section.
int buttonGroupId() const; ///< A unique ID for this tool as passed by changedTool(), >= 0
QString visibilityCode() const; ///< This tool should become visible when we emit this string in toolCodesSelected()
public Q_SLOTS:
void trigger(); ///< Request the activation of the tool
Q_SIGNALS:
void changed(); ///< Emitted when a property changes (shortcut ATM)
private:
friend class ToolHelper;
class Private;
Private *const d;
};
/**
* This class manages the activation and deactivation of tools for
* each input device.
*
* Managing the active tool and switching tool based on various variables.
*
* The state of the toolbox will be the same for all views in the process so practically
* you can say we have one toolbox per application instance (process). Implementation
* does not allow one widget to be in more then one view, so we just make sure the toolbox
* is hidden in not-in-focus views.
*
* The ToolManager is a singleton and will manage all views in all applications that
* are loaded in this process. This means you will have to register and unregister your view.
* When creating your new view you should use a KoCanvasController() and register that
* with the ToolManager like this:
@code
MyGuiWidget::MyGuiWidget() {
m_canvasController = new KoCanvasController(this);
m_canvasController->setCanvas(m_canvas);
KoToolManager::instance()->addControllers(m_canvasController));
}
MyGuiWidget::~MyGuiWidget() {
KoToolManager::instance()->removeCanvasController(m_canvasController);
}
@endcode
*
* For a new view that extends KoView all you need to do is implement KoView::createToolBox()
*
* KoToolManager also keeps track of the current tool based on a
complex set of conditions and heuristics:
- there is one active tool per KoCanvasController (and there is one KoCanvasController
per view, because this is a class with scrollbars and a zoomlevel and so on)
- for every pointing device (determined by the unique id of tablet,
or 0 for mice -- you may have more than one mouse attached, but
Qt cannot distinquish between them, there is an associated tool.
- depending on things like tablet leave/enter proximity, incoming
mouse or tablet events and a little timer (that gets stopped when
we know what is what), the active pointing device is determined,
and the active tool is set accordingly.
Nota bene: if you use KoToolManager and register your canvases with
it you no longer have to manually implement methods to route mouse,
tablet, key or wheel events to the active tool. In fact, it's no
longer interesting to you which tool is active; you can safely
route the paint event through KoToolProxy::paint().
(The reason the input events are handled completely by the
toolmanager and the paint events not is that, generally speaking,
it's okay if the tools get the input events first, but you want to
paint your shapes or other canvas stuff first and only then paint
the tool stuff.)
*/
class KRITAFLAKE_EXPORT KoToolManager : public QObject
{
Q_OBJECT
public:
KoToolManager();
/// Return the toolmanager singleton
static KoToolManager* instance();
~KoToolManager();
/**
* Register actions for switching to tools at the actionCollection parameter.
* The actions will have the text / shortcut as stated by the toolFactory.
* If the application calls this in their KoView extending class they will have all the benefits
* from allowing this in the menus and to allow the use to configure the shortcuts used.
* @param ac the actionCollection that will be the parent of the actions.
* @param controller tools registered with this controller will have all their actions added as well.
*/
void registerToolActions(KActionCollection *ac, KoCanvasController *controller);
/**
* Register a new canvas controller
* @param controller the view controller that this toolmanager will manage the tools for
*/
void addController(KoCanvasController *controller);
/**
* Remove a set of controllers
* When the controller is no longer used it should be removed so all tools can be
* deleted and stop eating memory.
* @param controller the controller that is removed
*/
void removeCanvasController(KoCanvasController *controller);
/**
* Attempt to remove a controller.
* This is automatically called when a controller's proxy object is deleted, and
* it ensures that the controller is, in fact, removed, even if the creator forgot
* to do so.
* @param controller the proxy object of the controller to be removed
*/
Q_SLOT void attemptCanvasControllerRemoval(QObject *controller);
/// @return the active canvas controller
KoCanvasController *activeCanvasController() const;
/**
* Connect all the tools for the given canvas to the new shape controller.
*
* @param shapecontroller the new shape controller
* @param canvasController the canvas
*/
void updateShapeControllerBase(KoShapeBasedDocumentBase *shapeController, KoCanvasController *canvasController);
/**
* Return the tool that is able to create shapes for this param canvas.
* This is typically used by the KoShapeSelector to set which shape to create next.
* @param canvas the canvas that is a child of a previously registered controller
* who's tool you want.
* @see addController()
*/
KoCreateShapesTool *shapeCreatorTool(KoCanvasBase *canvas) const;
/**
* Returns the tool for the given tool id.
* @param canvas the canvas that is a child of a previously registered controller
* who's tool you want.
* @see addController()
*/
KoToolBase *toolById(KoCanvasBase *canvas, const QString &id) const;
/// @return the currently active pointing device
KoInputDevice currentInputDevice() const;
/**
* For the list of shapes find out which tool is the highest priorty tool that can handle it.
* @returns the toolId for the shapes.
* @param shapes a list of shapes, a selection for example, that is used to look for the tool.
*/
QString preferredToolForSelection(const QList<KoShape*> &shapes);
/**
* Returns the list of toolActions for the current tools.
* @returns lists of toolActions for the current tools.
*/
QList<KoToolAction*> toolActionList() const;
/// Update the internal shortcuts of each tool. (Activation shortcuts are exposed already.)
void updateToolShortcuts();
/// Request tool activation for the given canvas controller
void requestToolActivation(KoCanvasController *controller);
- /// Injects an input event from a plugin based input device
- void injectDeviceEvent(KoInputDeviceHandlerEvent *event);
-
/// Returns the toolId of the currently active tool
QString activeToolId() const;
void initializeCurrentToolForCanvas();
class Private;
/**
* \internal return the private object for the toolmanager.
*/
KoToolManager::Private *priv();
public Q_SLOTS:
/**
* Request switching tool
* @param id the id of the tool
*/
void switchToolRequested(const QString &id);
/**
* Request change input device
* @param id the id of the input device
*/
void switchInputDeviceRequested(const KoInputDevice &id);
/**
* a new tool has become known to mankind
*/
void addDeferredToolFactory(KoToolFactoryBase *toolFactory);
/**
* Request for temporary switching the tools.
* This switch can be later reverted with switchBackRequested().
* @param id the id of the tool
*
* @see switchBackRequested()
*/
void switchToolTemporaryRequested(const QString &id);
/**
* Switches back to the original tool after the temporary switch
* has been done. It the user changed the tool manually on the way,
* then it switches to the interaction tool
*/
void switchBackRequested();
Q_SIGNALS:
+ /**
+ * Emitted when a new tool is going to override the current tool
+ * @param canvas the currently active canvas.
+ */
+ void aboutToChangeTool(KoCanvasController *canvas);
+
/**
* Emitted when a new tool was selected or became active.
* @param canvas the currently active canvas.
* @param uniqueToolId a random but unique code for the new tool.
*/
void changedTool(KoCanvasController *canvas, int uniqueToolId);
/**
* Emitted after the selection changed to state which unique shape-types are now
* in the selection.
* @param canvas the currently active canvas.
* @param types a list of string that are the shape types of the selected objects.
*/
void toolCodesSelected(const QList<QString> &types);
/**
* Emitted after the current layer changed either its properties or to a new layer.
* @param canvas the currently active canvas.
* @param layer the layer that is selected.
*/
void currentLayerChanged(const KoCanvasController *canvas, const KoShapeLayer *layer);
/**
* Every time a new input device gets used by a tool, this event is emitted.
* @param device the new input device that the user picked up.
*/
void inputDeviceChanged(const KoInputDevice &device);
/**
* Emitted whenever the active canvas changed.
* @param canvas the new activated canvas (might be 0)
*/
void changedCanvas(const KoCanvasBase *canvas);
/**
* Emitted whenever the active tool changes the status text.
* @param statusText the new status text
*/
void changedStatusText(const QString &statusText);
/**
* emitted whenever a new tool is dynamically added for the given canvas
*/
void addedTool(KoToolAction *toolAction, KoCanvasController *canvas);
/**
* Emit the new tool option widgets to be used with this canvas.
*/
void toolOptionWidgetsChanged(KoCanvasController *controller, const QList<QPointer<QWidget> > &widgets);
private:
KoToolManager(const KoToolManager&);
KoToolManager operator=(const KoToolManager&);
Q_PRIVATE_SLOT(d, void toolActivated(ToolHelper *tool))
Q_PRIVATE_SLOT(d, void detachCanvas(KoCanvasController *controller))
Q_PRIVATE_SLOT(d, void attachCanvas(KoCanvasController *controller))
Q_PRIVATE_SLOT(d, void movedFocus(QWidget *from, QWidget *to))
Q_PRIVATE_SLOT(d, void updateCursor(const QCursor &cursor))
Q_PRIVATE_SLOT(d, void selectionChanged(const QList<KoShape*> &shapes))
Q_PRIVATE_SLOT(d, void currentLayerChanged(const KoShapeLayer *layer))
QPair<QString, KoToolBase*> createTools(KoCanvasController *controller, ToolHelper *tool);
Private *const d;
};
#endif
diff --git a/libs/flake/KoToolManager_p.h b/libs/flake/KoToolManager_p.h
index d9f0bdd3fb..0d8c52c86d 100644
--- a/libs/flake/KoToolManager_p.h
+++ b/libs/flake/KoToolManager_p.h
@@ -1,193 +1,190 @@
/* 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 KO_TOOL_MANAGER_P
#define KO_TOOL_MANAGER_P
#include <QList>
#include <QObject>
#include <QString>
#include <QHash>
#include <QKeySequence>
#include <QAction>
#include "KoInputDevice.h"
#include "KoToolManager.h"
class KoToolFactoryBase;
class KoShapeManager;
class KoCanvasBase;
class KoToolBase;
class KoShape;
class KoToolManager;
class KoCanvasController;
class KoShapeLayer;
class ToolHelper;
class CanvasData;
class KoToolProxy;
class Q_DECL_HIDDEN KoToolManager::Private
{
public:
Private(KoToolManager *qq);
~Private();
void setup();
void connectActiveTool();
void disconnectActiveTool();
void switchTool(KoToolBase *tool, bool temporary);
void switchTool(const QString &id, bool temporary);
void postSwitchTool(bool temporary);
void switchCanvasData(CanvasData *cd);
bool eventFilter(QObject *object, QEvent *event);
void toolActivated(ToolHelper *tool);
void detachCanvas(KoCanvasController *controller);
void attachCanvas(KoCanvasController *controller);
void movedFocus(QWidget *from, QWidget *to);
void updateCursor(const QCursor &cursor);
void switchBackRequested();
void selectionChanged(const QList<KoShape*> &shapes);
void currentLayerChanged(const KoShapeLayer *layer);
void updateToolForProxy();
void switchToolTemporaryRequested(const QString &id);
CanvasData *createCanvasData(KoCanvasController *controller, const KoInputDevice &device);
/**
* Request a switch from to the param input device.
* This will cause the tool for that device to be selected.
*/
void switchInputDevice(const KoInputDevice &device);
/**
* Whenever a new tool proxy class is instantiated, it will use this method to register itself
* so the toolManager can update it to the latest active tool.
* @param proxy the proxy to register.
* @param canvas which canvas the proxy is associated with; whenever a new tool is selected for that canvas,
* the proxy gets an update.
*/
void registerToolProxy(KoToolProxy *proxy, KoCanvasBase *canvas);
- void switchToolByShortcut(QKeyEvent *event);
-
-
KoToolManager *q;
QList<ToolHelper*> tools; // list of all available tools via their factories.
QHash<KoToolBase*, int> uniqueToolIds; // for the changedTool signal
QHash<KoCanvasController*, QList<CanvasData*> > canvasses;
QHash<KoCanvasBase*, KoToolProxy*> proxies;
CanvasData *canvasData; // data about the active canvas.
KoInputDevice inputDevice;
bool layerExplicitlyDisabled;
};
class ShortcutToolAction;
/// \internal
class ToolHelper : public QObject
{
Q_OBJECT
public:
explicit ToolHelper(KoToolFactoryBase *tool);
KoToolAction *toolAction();
/// wrapper around KoToolFactoryBase::id();
QString id() const;
/// wrapper around KoToolFactoryBase::iconName();
QString iconName() const;
/// descriptive text, as ;
QString text() const;
/// descriptive icon text, e.g. use on a button next to an icon or without one;
QString iconText() const;
/// tooltip of the tool, e.g. for tooltip of a button;
QString toolTip() const;
/// wrapper around KoToolFactoryBase::toolType();
QString section() const;
/// wrapper around KoToolFactoryBase::activationShapeId();
QString activationShapeId() const;
/// wrapper around KoToolFactoryBase::priority();
int priority() const;
KoToolBase *createTool(KoCanvasBase *canvas) const;
ShortcutToolAction *createShortcutToolAction(QObject *parent);
/// unique id, >= 0
int uniqueId() const {
return m_uniqueId;
}
/// QAction->shortcut() if it exists, otherwise KoToolFactoryBase::shortcut()
QKeySequence shortcut() const;
public Q_SLOTS:
void activate();
Q_SIGNALS:
/// Emitted when the tool should be activated, e.g. by pressing the tool's assigned button in the toolbox
void toolActivated(ToolHelper *tool);
private Q_SLOTS:
void shortcutToolActionUpdated();
private:
KoToolFactoryBase * const m_toolFactory;
const int m_uniqueId;
QKeySequence m_customShortcut;
bool m_hasCustomShortcut;
KoToolAction *m_toolAction;
};
/// \internal
/// Helper class to transform a simple signal selection changed into a signal with a parameter
class Connector : public QObject
{
Q_OBJECT
public:
explicit Connector(KoShapeManager *parent);
public Q_SLOTS:
void selectionChanged();
Q_SIGNALS:
void selectionChanged(const QList<KoShape*> &shape);
private:
KoShapeManager *m_shapeManager;
};
/// \internal
/// Helper class to provide a action for tool shortcuts
class ShortcutToolAction : public QAction
{
Q_OBJECT
public:
ShortcutToolAction(const QString &id, const QString &name, QObject *parent);
virtual ~ShortcutToolAction();
private Q_SLOTS:
void actionTriggered();
private:
QString m_toolID;
};
#endif
diff --git a/libs/flake/KoToolProxy.cpp b/libs/flake/KoToolProxy.cpp
index 3e30f6ccb5..fcdb2f3b82 100644
--- a/libs/flake/KoToolProxy.cpp
+++ b/libs/flake/KoToolProxy.cpp
@@ -1,648 +1,589 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (c) 2006-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 "KoToolProxy.h"
#include "KoToolProxy_p.h"
#include <QMimeData>
#include <QUrl>
#include <QTimer>
#include <QApplication>
#include <QTouchEvent>
#include <QClipboard>
#include <kundo2command.h>
#include <KoProperties.h>
#include <FlakeDebug.h>
#include <klocalizedstring.h>
#include "KoToolBase.h"
#include "KoPointerEvent.h"
#include "KoInputDevice.h"
#include "KoToolManager_p.h"
#include "KoToolSelection.h"
#include "KoCanvasBase.h"
#include "KoCanvasController.h"
#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoShapeLayer.h"
-#include "KoShapePaste.h"
#include "KoShapeRegistry.h"
#include "KoShapeController.h"
#include "KoOdf.h"
#include "KoViewConverter.h"
#include "KoShapeFactoryBase.h"
KoToolProxyPrivate::KoToolProxyPrivate(KoToolProxy *p)
: activeTool(0),
tabletPressed(false),
hasSelection(false),
controller(0),
parent(p)
{
scrollTimer.setInterval(100);
mouseLeaveWorkaround = false;
multiClickCount = 0;
}
void KoToolProxyPrivate::timeout() // Auto scroll the canvas
{
Q_ASSERT(controller);
QPoint offset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY());
QPoint origin = controller->canvas()->documentOrigin();
QPoint viewPoint = widgetScrollPoint - origin - offset;
QRectF mouseArea(viewPoint, QSizeF(10, 10));
mouseArea.setTopLeft(mouseArea.center());
controller->ensureVisible(mouseArea, true);
QPoint newOffset = QPoint(controller->canvasOffsetX(), controller->canvasOffsetY());
QPoint moved = offset - newOffset;
if (moved.isNull())
return;
widgetScrollPoint += moved;
QPointF documentPoint = parent->widgetToDocument(widgetScrollPoint);
QMouseEvent event(QEvent::MouseMove, widgetScrollPoint, Qt::LeftButton, Qt::LeftButton, 0);
KoPointerEvent ev(&event, documentPoint);
activeTool->mouseMoveEvent(&ev);
}
void KoToolProxyPrivate::checkAutoScroll(const KoPointerEvent &event)
{
if (controller == 0) return;
if (!activeTool) return;
if (!activeTool->wantsAutoScroll()) return;
if (!event.isAccepted()) return;
if (event.buttons() != Qt::LeftButton) return;
widgetScrollPoint = event.pos();
if (! scrollTimer.isActive())
scrollTimer.start();
}
void KoToolProxyPrivate::selectionChanged(bool newSelection)
{
if (hasSelection == newSelection)
return;
hasSelection = newSelection;
emit parent->selectionChanged(hasSelection);
}
bool KoToolProxyPrivate::isActiveLayerEditable()
{
if (!activeTool)
return false;
KoShapeManager * shapeManager = activeTool->canvas()->shapeManager();
KoShapeLayer * activeLayer = shapeManager->selection()->activeLayer();
if (activeLayer && !activeLayer->isEditable())
return false;
return true;
}
KoToolProxy::KoToolProxy(KoCanvasBase *canvas, QObject *parent)
: QObject(parent),
d(new KoToolProxyPrivate(this))
{
KoToolManager::instance()->priv()->registerToolProxy(this, canvas);
connect(&d->scrollTimer, SIGNAL(timeout()), this, SLOT(timeout()));
}
KoToolProxy::~KoToolProxy()
{
delete d;
}
void KoToolProxy::paint(QPainter &painter, const KoViewConverter &converter)
{
if (d->activeTool) d->activeTool->paint(painter, converter);
}
void KoToolProxy::repaintDecorations()
{
if (d->activeTool) d->activeTool->repaintDecorations();
}
QPointF KoToolProxy::widgetToDocument(const QPointF &widgetPoint) const
{
QPoint offset = QPoint(d->controller->canvasOffsetX(), d->controller->canvasOffsetY());
QPoint origin = d->controller->canvas()->documentOrigin();
QPointF viewPoint = widgetPoint.toPoint() - QPointF(origin - offset);
return d->controller->canvas()->viewConverter()->viewToDocument(viewPoint);
}
KoCanvasBase* KoToolProxy::canvas() const
{
return d->controller->canvas();
}
void KoToolProxy::touchEvent(QTouchEvent *event)
{
QPointF point;
QList<KoTouchPoint> touchPoints;
bool isPrimary = true;
Q_FOREACH (QTouchEvent::TouchPoint p, event->touchPoints()) {
QPointF docPoint = widgetToDocument(p.screenPos());
if (isPrimary) {
point = docPoint;
isPrimary = false;
}
KoTouchPoint touchPoint;
touchPoint.touchPoint = p;
touchPoint.point = point;
touchPoint.lastPoint = widgetToDocument(p.lastNormalizedPos());
touchPoints << touchPoint;
}
KoPointerEvent ev(event, point, touchPoints);
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
switch (event->type()) {
case QEvent::TouchBegin:
ev.setTabletButton(Qt::LeftButton);
if (d->activeTool) {
if( d->activeTool->wantsTouch() )
d->activeTool->touchEvent(event);
else
d->activeTool->mousePressEvent(&ev);
}
break;
case QEvent::TouchUpdate:
ev.setTabletButton(Qt::LeftButton);
if (d->activeTool) {
if( d->activeTool->wantsTouch() )
d->activeTool->touchEvent(event);
else
d->activeTool->mouseMoveEvent(&ev);
}
break;
case QEvent::TouchEnd:
ev.setTabletButton(Qt::LeftButton);
if (d->activeTool) {
if( d->activeTool->wantsTouch() )
d->activeTool->touchEvent(event);
else
d->activeTool->mouseReleaseEvent(&ev);
}
break;
default:
; // ingore the rest
}
d->mouseLeaveWorkaround = true;
}
void KoToolProxy::tabletEvent(QTabletEvent *event, const QPointF &point)
{
// We get these events exclusively from KisToolProxy - accept them
event->accept();
KoInputDevice id(event->device(), event->pointerType(), event->uniqueId());
KoToolManager::instance()->priv()->switchInputDevice(id);
KoPointerEvent ev(event, point);
switch (event->type()) {
case QEvent::TabletPress:
ev.setTabletButton(Qt::LeftButton);
if (!d->tabletPressed && d->activeTool)
d->activeTool->mousePressEvent(&ev);
d->tabletPressed = true;
break;
case QEvent::TabletRelease:
ev.setTabletButton(Qt::LeftButton);
d->tabletPressed = false;
d->scrollTimer.stop();
if (d->activeTool)
d->activeTool->mouseReleaseEvent(&ev);
break;
case QEvent::TabletMove:
if (d->tabletPressed)
ev.setTabletButton(Qt::LeftButton);
if (d->activeTool)
d->activeTool->mouseMoveEvent(&ev);
d->checkAutoScroll(ev);
default:
; // ignore the rest.
}
d->mouseLeaveWorkaround = true;
}
void KoToolProxy::mousePressEvent(KoPointerEvent *ev)
{
d->mouseLeaveWorkaround = false;
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
d->mouseDownPoint = ev->pos();
if (d->tabletPressed) // refuse to send a press unless there was a release first.
return;
QPointF globalPoint = ev->globalPos();
if (d->multiClickGlobalPoint != globalPoint) {
if (qAbs(globalPoint.x() - d->multiClickGlobalPoint.x()) > 5||
qAbs(globalPoint.y() - d->multiClickGlobalPoint.y()) > 5) {
d->multiClickCount = 0;
}
d->multiClickGlobalPoint = globalPoint;
}
if (d->multiClickCount && d->multiClickTimeStamp.elapsed() < QApplication::doubleClickInterval()) {
// One more multiclick;
d->multiClickCount++;
} else {
d->multiClickTimeStamp.start();
d->multiClickCount = 1;
}
if (d->activeTool) {
switch (d->multiClickCount) {
case 0:
case 1:
d->activeTool->mousePressEvent(ev);
break;
case 2:
d->activeTool->mouseDoubleClickEvent(ev);
break;
case 3:
default:
d->activeTool->mouseTripleClickEvent(ev);
break;
}
} else {
d->multiClickCount = 0;
ev->ignore();
}
}
void KoToolProxy::mousePressEvent(QMouseEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
mousePressEvent(&ev);
}
void KoToolProxy::mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
mouseDoubleClickEvent(&ev);
}
void KoToolProxy::mouseDoubleClickEvent(KoPointerEvent *event)
{
// let us handle it as any other mousepress (where we then detect multi clicks
mousePressEvent(event);
- if (!event->isAccepted() && d->activeTool)
- d->activeTool->canvas()->shapeManager()->suggestChangeTool(event);
}
void KoToolProxy::mouseMoveEvent(QMouseEvent *event, const QPointF &point)
{
- if (d->mouseLeaveWorkaround) {
- d->mouseLeaveWorkaround = false;
- return;
- }
- KoInputDevice id;
- KoToolManager::instance()->priv()->switchInputDevice(id);
- if (d->activeTool == 0) {
- event->ignore();
- return;
- }
-
KoPointerEvent ev(event, point);
- d->activeTool->mouseMoveEvent(&ev);
-
- d->checkAutoScroll(ev);
+ mouseMoveEvent(&ev);
}
void KoToolProxy::mouseMoveEvent(KoPointerEvent *event)
{
if (d->mouseLeaveWorkaround) {
d->mouseLeaveWorkaround = false;
return;
}
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
if (d->activeTool == 0) {
event->ignore();
return;
}
d->activeTool->mouseMoveEvent(event);
d->checkAutoScroll(*event);
}
void KoToolProxy::mouseReleaseEvent(QMouseEvent *event, const QPointF &point)
{
- d->mouseLeaveWorkaround = false;
- KoInputDevice id;
- KoToolManager::instance()->priv()->switchInputDevice(id);
- d->scrollTimer.stop();
-
KoPointerEvent ev(event, point);
- if (d->activeTool) {
- d->activeTool->mouseReleaseEvent(&ev);
-
- if (! event->isAccepted() && event->button() == Qt::LeftButton && event->modifiers() == 0
- && qAbs(d->mouseDownPoint.x() - event->x()) < 5
- && qAbs(d->mouseDownPoint.y() - event->y()) < 5) {
- // we potentially will change the selection
- Q_ASSERT(d->activeTool->canvas());
- KoShapeManager *manager = d->activeTool->canvas()->shapeManager();
- Q_ASSERT(manager);
- // only change the selection if that will not lead to losing a complex selection
- if (manager->selection() && manager->selection()->count() <= 1) {
- KoShape *shape = manager->shapeAt(point);
- if (shape && !manager->selection()->isSelected(shape)) { // make the clicked shape the active one
- manager->selection()->deselectAll();
- manager->selection()->select(shape);
- QList<KoShape*> shapes;
- shapes << shape;
- QString tool = KoToolManager::instance()->preferredToolForSelection(shapes);
- KoToolManager::instance()->switchToolRequested(tool);
- }
- }
- }
- } else {
- event->ignore();
- }
+ mouseReleaseEvent(&ev);
}
void KoToolProxy::mouseReleaseEvent(KoPointerEvent* event)
{
d->mouseLeaveWorkaround = false;
KoInputDevice id;
KoToolManager::instance()->priv()->switchInputDevice(id);
d->scrollTimer.stop();
if (d->activeTool) {
d->activeTool->mouseReleaseEvent(event);
-
- if (!event->isAccepted() && event->button() == Qt::LeftButton && event->modifiers() == 0
- && qAbs(d->mouseDownPoint.x() - event->x()) < 5
- && qAbs(d->mouseDownPoint.y() - event->y()) < 5) {
- // we potentially will change the selection
- Q_ASSERT(d->activeTool->canvas());
- KoShapeManager *manager = d->activeTool->canvas()->shapeManager();
- Q_ASSERT(manager);
- // only change the selection if that will not lead to losing a complex selection
- if (manager->selection() && manager->selection()->count() <= 1) {
- KoShape *shape = manager->shapeAt(event->point);
- if (shape && !manager->selection()->isSelected(shape)) { // make the clicked shape the active one
- manager->selection()->deselectAll();
- manager->selection()->select(shape);
- QList<KoShape*> shapes;
- shapes << shape;
- QString tool = KoToolManager::instance()->preferredToolForSelection(shapes);
- KoToolManager::instance()->switchToolRequested(tool);
- }
- }
- }
} else {
event->ignore();
}
}
void KoToolProxy::keyPressEvent(QKeyEvent *event)
{
if (d->activeTool)
d->activeTool->keyPressEvent(event);
else
event->ignore();
}
void KoToolProxy::keyReleaseEvent(QKeyEvent *event)
{
if (d->activeTool)
d->activeTool->keyReleaseEvent(event);
else
event->ignore();
}
void KoToolProxy::wheelEvent(QWheelEvent *event, const QPointF &point)
{
KoPointerEvent ev(event, point);
if (d->activeTool)
d->activeTool->wheelEvent(&ev);
else
event->ignore();
}
void KoToolProxy::wheelEvent(KoPointerEvent *event)
{
if (d->activeTool)
d->activeTool->wheelEvent(event);
else
event->ignore();
}
+void KoToolProxy::explicitUserStrokeEndRequest()
+{
+ if (d->activeTool) {
+ d->activeTool->explicitUserStrokeEndRequest();
+ }
+}
+
QVariant KoToolProxy::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const
{
if (d->activeTool)
return d->activeTool->inputMethodQuery(query, converter);
return QVariant();
}
void KoToolProxy::inputMethodEvent(QInputMethodEvent *event)
{
if (d->activeTool) d->activeTool->inputMethodEvent(event);
}
+QMenu *KoToolProxy::popupActionsMenu()
+{
+ return d->activeTool ? d->activeTool->popupActionsMenu() : 0;
+}
+
void KoToolProxy::setActiveTool(KoToolBase *tool)
{
if (d->activeTool)
disconnect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool)));
d->activeTool = tool;
if (tool) {
connect(d->activeTool, SIGNAL(selectionChanged(bool)), this, SLOT(selectionChanged(bool)));
d->selectionChanged(hasSelection());
emit toolChanged(tool->toolId());
}
}
void KoToolProxyPrivate::setCanvasController(KoCanvasController *c)
{
controller = c;
}
QHash<QString, QAction *> KoToolProxy::actions() const
{
return d->activeTool ? d->activeTool->actions() : QHash<QString, QAction *>();
}
bool KoToolProxy::hasSelection() const
{
return d->activeTool ? d->activeTool->hasSelection() : false;
}
void KoToolProxy::cut()
{
if (d->activeTool && d->isActiveLayerEditable())
d->activeTool->cut();
}
void KoToolProxy::copy() const
{
if (d->activeTool)
d->activeTool->copy();
}
bool KoToolProxy::paste()
{
bool success = false;
KoCanvasBase *canvas = d->controller->canvas();
- if (d->activeTool && d->isActiveLayerEditable())
+ if (d->activeTool && d->isActiveLayerEditable()) {
success = d->activeTool->paste();
-
- if (!success) {
- const QMimeData *data = QApplication::clipboard()->mimeData();
-
- if (data->hasFormat(KoOdf::mimeType(KoOdf::Text))) {
- KoShapeManager *shapeManager = canvas->shapeManager();
- KoShapePaste paste(canvas, shapeManager->selection()->activeLayer());
- success = paste.paste(KoOdf::Text, data);
- if (success) {
- shapeManager->selection()->deselectAll();
- Q_FOREACH (KoShape *shape, paste.pastedShapes()) {
- shapeManager->selection()->select(shape);
- }
- }
- }
}
if (!success) {
const QMimeData *data = QApplication::clipboard()->mimeData();
QList<QImage> imageList;
QImage image = QApplication::clipboard()->image();
if (!image.isNull()) {
imageList << image;
}
// QT5TODO: figure out how to download data synchronously, which is deprecated in frameworks.
else if (data->hasUrls()) {
QList<QUrl> urls = QApplication::clipboard()->mimeData()->urls();
foreach (const QUrl &url, urls) {
QImage image;
image.load(url.toLocalFile());
if (!image.isNull()) {
imageList << image;
}
}
}
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value("PictureShape");
QWidget *canvasWidget = canvas->canvasWidget();
const KoViewConverter *converter = canvas->viewConverter();
if (imageList.length() > 0 && factory && canvasWidget) {
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Paste Image"));
+ QList<KoShape*> pastedShapes;
+
Q_FOREACH (const QImage &image, imageList) {
if (!image.isNull()) {
QPointF p = converter->viewToDocument(canvasWidget->mapFromGlobal(QCursor::pos()) + canvas->canvasController()->documentOffset()- canvasWidget->pos());
KoProperties params;
params.setProperty("qimage", image);
KoShape *shape = factory->createShape(&params, canvas->shapeController()->resourceManager());
shape->setPosition(p);
-
- // add shape to the document
- canvas->shapeController()->addShapeDirect(shape, cmd);
+ pastedShapes << shape;
success = true;
}
}
- canvas->addCommand(cmd);
+
+ if (!pastedShapes.isEmpty()) {
+ // add shape to the document
+ canvas->shapeController()->addShapesDirect(pastedShapes, cmd);
+ canvas->addCommand(cmd);
+ }
}
}
return success;
}
void KoToolProxy::dragMoveEvent(QDragMoveEvent *event, const QPointF &point)
{
if (d->activeTool)
d->activeTool->dragMoveEvent(event, point);
}
void KoToolProxy::dragLeaveEvent(QDragLeaveEvent *event)
{
if (d->activeTool)
d->activeTool->dragLeaveEvent(event);
}
void KoToolProxy::dropEvent(QDropEvent *event, const QPointF &point)
{
if (d->activeTool)
d->activeTool->dropEvent(event, point);
}
-QStringList KoToolProxy::supportedPasteMimeTypes() const
-{
- if (d->activeTool)
- return d->activeTool->supportedPasteMimeTypes();
-
- return QStringList();
-}
-
-QList<QAction*> KoToolProxy::popupActionList() const
-{
- if (d->activeTool)
- return d->activeTool->popupActionList();
- return QList<QAction*>();
-}
-
void KoToolProxy::deleteSelection()
{
if (d->activeTool)
- return d->activeTool->deleteSelection();
+ d->activeTool->deleteSelection();
}
void KoToolProxy::processEvent(QEvent *e) const
{
if(e->type()==QEvent::ShortcutOverride
&& d->activeTool
&& d->activeTool->isInTextMode()
&& (static_cast<QKeyEvent*>(e)->modifiers()==Qt::NoModifier ||
static_cast<QKeyEvent*>(e)->modifiers()==Qt::ShiftModifier)) {
e->accept();
}
}
+void KoToolProxy::requestUndoDuringStroke()
+{
+ if (d->activeTool) {
+ d->activeTool->requestUndoDuringStroke();
+ }
+}
+
+void KoToolProxy::requestStrokeCancellation()
+{
+ if (d->activeTool) {
+ d->activeTool->requestStrokeCancellation();
+ }
+}
+
+void KoToolProxy::requestStrokeEnd()
+{
+ if (d->activeTool) {
+ d->activeTool->requestStrokeEnd();
+ }
+}
+
KoToolProxyPrivate *KoToolProxy::priv()
{
return d;
}
//have to include this because of Q_PRIVATE_SLOT
#include "moc_KoToolProxy.cpp"
diff --git a/libs/flake/KoToolProxy.h b/libs/flake/KoToolProxy.h
index 6e1ecec0df..5a2ae003c2 100644
--- a/libs/flake/KoToolProxy.h
+++ b/libs/flake/KoToolProxy.h
@@ -1,190 +1,201 @@
/* This file is part of the KDE project
*
* Copyright (c) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* 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 _KO_TOOL_PROXY_H_
#define _KO_TOOL_PROXY_H_
#include "kritaflake_export.h"
#include <QObject>
#include <QHash>
class QAction;
class QAction;
class QMouseEvent;
class QKeyEvent;
class QWheelEvent;
class QTabletEvent;
class KoCanvasBase;
class KoViewConverter;
class KoToolBase;
class KoToolProxyPrivate;
class QInputMethodEvent;
class KoPointerEvent;
class QDragMoveEvent;
class QDragLeaveEvent;
class QDropEvent;
class QTouchEvent;
class QPainter;
class QPointF;
+class QMenu;
/**
* Tool proxy object which allows an application to address the current tool.
*
* Applications typically have a canvas and a canvas requires a tool for
* the user to do anything. Since the flake system is responsible for handling
* tools and also to change the active tool when needed we provide one class
* that can be used by an application canvas to route all the native events too
* which will transparantly be routed to the active tool. Without the application
* having to bother about which tool is active.
*/
class KRITAFLAKE_EXPORT KoToolProxy : public QObject
{
Q_OBJECT
public:
/**
* Constructor
* @param canvas Each canvas has 1 toolProxy. Pass the parent here.
* @param parent a parent QObject for memory management purposes.
*/
explicit KoToolProxy(KoCanvasBase *canvas, QObject *parent = 0);
virtual ~KoToolProxy();
/// Forwarded to the current KoToolBase
void paint(QPainter &painter, const KoViewConverter &converter);
/// Forwarded to the current KoToolBase
void repaintDecorations();
/**
* Forward the given touch event to the current KoToolBase.
* The viewconverter and document offset are necessary to convert all
* the QTouchPoints to KoTouchPoints that work in document coordinates.
*/
void touchEvent(QTouchEvent *event);
/// Forwarded to the current KoToolBase
void tabletEvent(QTabletEvent *event, const QPointF &point);
/// Forwarded to the current KoToolBase
void mousePressEvent(QMouseEvent *event, const QPointF &point);
void mousePressEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void mouseDoubleClickEvent(QMouseEvent *event, const QPointF &point);
void mouseDoubleClickEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void mouseMoveEvent(QMouseEvent *event, const QPointF &point);
void mouseMoveEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void mouseReleaseEvent(QMouseEvent *event, const QPointF &point);
void mouseReleaseEvent(KoPointerEvent *event);
/// Forwarded to the current KoToolBase
void keyPressEvent(QKeyEvent *event);
/// Forwarded to the current KoToolBase
void keyReleaseEvent(QKeyEvent *event);
/// Forwarded to the current KoToolBase
void wheelEvent(QWheelEvent * event, const QPointF &point);
void wheelEvent(KoPointerEvent *event);
+ /// Forwarded to the current KoToolBase
+ void explicitUserStrokeEndRequest();
+
/// Forwarded to the current KoToolBase
QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const;
/// Forwarded to the current KoToolBase
void inputMethodEvent(QInputMethodEvent *event);
/// Forwarded to the current KoToolBase
- QList<QAction*> popupActionList() const;
+ QMenu* popupActionsMenu();
/// Forwarded to the current KoToolBase
void deleteSelection();
/// This method gives the proxy a chance to do things. for example it is need to have working singlekey
/// shortcuts. call it from the canvas' event function and forward it to QWidget::event() later.
void processEvent(QEvent *) const;
/**
* Retrieves the entire collection of actions for the active tool
* or an empty hash if there is no active tool yet.
*/
QHash<QString, QAction *> actions() const;
/// returns true if the current tool holds a selection
bool hasSelection() const;
/// Forwarded to the current KoToolBase
void cut();
/// Forwarded to the current KoToolBase
void copy() const;
/// Forwarded to the current KoToolBase
bool paste();
- /// Forwarded to the current KoToolBase
- QStringList supportedPasteMimeTypes() const;
-
/// Forwarded to the current KoToolBase
void dragMoveEvent(QDragMoveEvent *event, const QPointF &point);
/// Forwarded to the current KoToolBase
void dragLeaveEvent(QDragLeaveEvent *event);
/// Forwarded to the current KoToolBase
void dropEvent(QDropEvent *event, const QPointF &point);
/// Set the new active tool.
virtual void setActiveTool(KoToolBase *tool);
/// \internal
KoToolProxyPrivate *priv();
+protected Q_SLOTS:
+ /// Forwarded to the current KoToolBase
+ void requestUndoDuringStroke();
+
+ /// Forwarded to the current KoToolBase
+ void requestStrokeCancellation();
+
+ /// Forwarded to the current KoToolBase
+ void requestStrokeEnd();
+
Q_SIGNALS:
/**
* A tool can have a selection that is copy-able, this signal is emitted when that status changes.
* @param hasSelection is true when the tool holds selected data.
*/
void selectionChanged(bool hasSelection);
/**
* Emitted every time a tool is changed.
* @param toolId the id of the tool.
* @see KoToolBase::toolId()
*/
void toolChanged(const QString &toolId);
protected:
virtual QPointF widgetToDocument(const QPointF &widgetPoint) const;
KoCanvasBase* canvas() const;
private:
Q_PRIVATE_SLOT(d, void timeout())
Q_PRIVATE_SLOT(d, void selectionChanged(bool))
friend class KoToolProxyPrivate;
KoToolProxyPrivate * const d;
};
#endif // _KO_TOOL_PROXY_H_
diff --git a/libs/flake/KoTosContainer.cpp b/libs/flake/KoTosContainer.cpp
index 78909eeee5..33ce6cadc8 100644
--- a/libs/flake/KoTosContainer.cpp
+++ b/libs/flake/KoTosContainer.cpp
@@ -1,352 +1,360 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 KO GmbH <boud@kogmbh.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.
*/
#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>
KoTosContainerPrivate::KoTosContainerPrivate(KoShapeContainer *q)
: KoShapeContainerPrivate(q)
, resizeBehavior(KoTosContainer::IndependentSizes)
{
}
+KoTosContainerPrivate::KoTosContainerPrivate(const KoTosContainerPrivate &rhs, KoShapeContainer *q)
+ : KoShapeContainerPrivate(rhs, q),
+ resizeBehavior(rhs.resizeBehavior),
+ preferredTextRect(rhs.preferredTextRect),
+ alignment(rhs.alignment)
+{
+}
+
KoTosContainerPrivate::~KoTosContainerPrivate()
{
}
KoTosContainer::KoTosContainer()
- : KoShapeContainer(*(new KoTosContainerPrivate(this)))
+ : KoShapeContainer(new KoTosContainerPrivate(this))
{
}
KoTosContainer::~KoTosContainer()
{
delete textShape();
}
-KoTosContainer::KoTosContainer(KoTosContainerPrivate &dd)
+KoTosContainer::KoTosContainer(KoTosContainerPrivate *dd)
: KoShapeContainer(dd)
{
}
void KoTosContainer::paintComponent(QPainter &, const KoViewConverter &, KoShapePaintingContext &)
{
}
bool KoTosContainer::loadText(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_D(const KoTosContainer);
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)
{
Q_D(KoTosContainer);
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)
{
Q_D(KoTosContainer);
if (d->resizeBehavior == resizeBehavior) {
return;
}
d->resizeBehavior = resizeBehavior;
if (d->model) {
d->model->containerChanged(this, KoShape::SizeChanged);
}
}
KoTosContainer::ResizeBehavior KoTosContainer::resizeBehavior() const
{
Q_D(const KoTosContainer);
return d->resizeBehavior;
}
void KoTosContainer::setTextAlignment(Qt::Alignment alignment)
{
Q_D(KoTosContainer);
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)
{
Q_D(KoTosContainer);
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
{
Q_D(const KoTosContainer);
return d->preferredTextRect;
}
KoShape *KoTosContainer::createTextShape(KoDocumentResourceManager *documentResources)
{
if (!documentResources) {
warnFlake << "KoDocumentResourceManager not found";
return 0;
}
Q_D(KoTosContainer);
delete textShape();
delete d->model;
d->model = new KoTosContainerModel();
QSet<KoShape*> delegates;
delegates << this;
KoShape *textShape = 0;
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get("TextShapeID");
if (factory) { // not installed, thats 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);
Q_D(KoTosContainer);
if (d->model == 0) {
return;
}
if (type == SizeChanged || type == ContentChanged) {
d->model->containerChanged(this, type);
}
// TODO is this needed?
#if 0
Q_FOREACH (KoShape *shape, d->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 102b3400c9..cc8f3b7554 100644
--- a/libs/flake/KoTosContainer.h
+++ b/libs/flake/KoTosContainer.h
@@ -1,132 +1,132 @@
/* 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 KoTosContainerPrivate;
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();
virtual ~KoTosContainer();
// reimplemented
virtual void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintcontext);
// 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);
virtual void setRunThrough(short int runThrough);
protected:
/// constructor
- KoTosContainer(KoTosContainerPrivate &);
+ KoTosContainer(KoTosContainerPrivate *);
//reimplemented
void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context);
//reimplemented
virtual QString saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const;
/**
* 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;
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
-private:
+protected:
Q_DECLARE_PRIVATE(KoTosContainer)
};
#endif
diff --git a/libs/flake/KoTosContainerModel.cpp b/libs/flake/KoTosContainerModel.cpp
index 9e75ff9662..6987ea2759 100644
--- a/libs/flake/KoTosContainerModel.cpp
+++ b/libs/flake/KoTosContainerModel.cpp
@@ -1,116 +1,117 @@
/* This file is part of the KDE project
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 "KoTosContainerModel.h"
#include "KoTextShapeDataBase.h"
#include "KoTosContainer.h"
#include <FlakeDebug.h>
#include <QSizeF>
KoTosContainerModel::KoTosContainerModel()
: m_textShape(0)
{
}
KoTosContainerModel::~KoTosContainerModel()
{
}
void KoTosContainerModel::add(KoShape *shape)
{
// make sure shape is a text shape
KoTextShapeDataBase *shapeData = qobject_cast<KoTextShapeDataBase*>(shape->userData());
Q_ASSERT(shapeData != 0);
if (shapeData) {
+ delete m_textShape;
m_textShape = shape;
}
}
void KoTosContainerModel::remove(KoShape *shape)
{
Q_ASSERT(m_textShape == 0 || shape == m_textShape);
if (shape == m_textShape) {
m_textShape = 0;
}
}
void KoTosContainerModel::setClipped(const KoShape *shape, bool clipping)
{
Q_UNUSED(shape);
Q_UNUSED(clipping);
}
bool KoTosContainerModel::isClipped(const KoShape *shape) const
{
Q_UNUSED(shape)
return false;
}
void KoTosContainerModel::setInheritsTransform(const KoShape *shape, bool inherit)
{
Q_UNUSED(shape);
Q_UNUSED(inherit);
}
bool KoTosContainerModel::inheritsTransform(const KoShape *shape) const
{
Q_UNUSED(shape)
return true;
}
bool KoTosContainerModel::isChildLocked(const KoShape *child) const
{
Q_ASSERT(child == m_textShape);
Q_ASSERT(child->parent());
// TODO do we need to guard this?
return child->isGeometryProtected() || child->parent()->isGeometryProtected();
}
int KoTosContainerModel::count() const
{
return m_textShape != 0 ? 1 : 0;
}
QList<KoShape*> KoTosContainerModel::shapes() const
{
QList<KoShape*> shapes;
if (m_textShape) {
shapes << m_textShape;
}
return shapes;
}
void KoTosContainerModel::containerChanged(KoShapeContainer *container, KoShape::ChangeType type)
{
debugFlake << "change type:" << type << KoShape::SizeChanged << KoShape::ContentChanged;
if (type != KoShape::SizeChanged && type != KoShape::ContentChanged) {
return;
}
KoTosContainer *tosContainer = dynamic_cast<KoTosContainer*>(container);
debugFlake << "tosContainer" << tosContainer;
if (tosContainer) {
debugFlake << "behaviour" << tosContainer->resizeBehavior() << KoTosContainer::TextFollowsPreferredTextRect;
}
if ( m_textShape && tosContainer && tosContainer->resizeBehavior() != KoTosContainer::TextFollowsPreferredTextRect ) {
debugFlake << "change type setSize";
m_textShape->setSize(tosContainer->size());
}
}
diff --git a/libs/flake/KoTosContainer_p.h b/libs/flake/KoTosContainer_p.h
index fb43e69202..6b9d0172b9 100644
--- a/libs/flake/KoTosContainer_p.h
+++ b/libs/flake/KoTosContainer_p.h
@@ -1,21 +1,23 @@
#ifndef KOTOSCONTAINER_P_H
#define KOTOSCONTAINER_P_H
+#include "kritaflake_export.h"
#include "KoShapeContainer_p.h"
#include "KoTosContainer.h"
-class KoTosContainerPrivate : public KoShapeContainerPrivate
+class KRITAFLAKE_EXPORT KoTosContainerPrivate : public KoShapeContainerPrivate
{
public:
explicit KoTosContainerPrivate(KoShapeContainer *q);
+ explicit KoTosContainerPrivate(const KoTosContainerPrivate &rhs, KoShapeContainer *q);
virtual ~KoTosContainerPrivate();
KoTosContainer::ResizeBehavior resizeBehavior;
QRectF preferredTextRect;
Qt::Alignment alignment;
};
#endif // KOTOSCONTAINER_P_H
diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp
index d6881b8d70..240a9d2ba1 100644
--- a/libs/flake/KoUnavailShape.cpp
+++ b/libs/flake/KoUnavailShape.cpp
@@ -1,656 +1,656 @@
/* This file is part of the KDE project
*
* Copyright (C) 2010-2011 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 "KoUnavailShape.h"
// Qt
#include <QPen>
#include <QPainter>
#include <QByteArray>
#include <QBuffer>
#include <QDataStream>
#include <QPixmap>
#include <QStringList>
#include <QSvgRenderer>
#include <QStandardPaths>
// Calligra
#include <KoUnit.h>
#include <KoStore.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoOdfManifestEntry.h>
#include <KoOdfLoadingContext.h>
#include <KoEmbeddedDocumentSaver.h>
#include "KoShapeLoadingContext.h"
#include "KoShapeSavingContext.h"
-#include "KoShapeContainerDefaultModel.h"
+#include "SimpleShapeContainerModel.h"
#include "KoShapeBackground.h"
#include <FlakeDebug.h>
// The XML of a frame looks something like this:
//
// 1. <draw:frame ...attributes...>
// 2. <draw:object xlink:href="./Object1" ...more attributes>
// 3. <draw:image xlink:href="./ObjectReplacements/Object1" ...more attributes>
// 4. </draw:frame>
//
// or
//
// 1. <draw:frame ...attributes...>
// 2. <math:math>...inline xml here...</math:math>
// 3. <draw:image xlink:href="./ObjectReplacements/Object1" ...more attributes>
// 4. </draw:frame>
//
// We define each Xml statement on lines 2 and 3 above as an "object".
// (Strictly only the first child element is an object in the ODF sense,
// but we have to have some terminology here.)
//
// In an ODF frame, only the first line, i.e. the first object
// contains the real contents. All the rest of the objects are used /
// shown if we cannot handle the first one. The most common cases are
// that there is only one object inside the frame OR that there are 2
// and the 2nd is a picture.
//
// Sometimes, e.g. in the case of an embedded document, the reference
// points not to a file but to a directory structure inside the ODF
// store.
//
// When we load and save in the UnavailShape, we have to be general
// enough to cover all possible cases of references and inline XML,
// embedded files and embedded directory structures.
//
// We also have to be careful because we cannot reuse the object names
// that are in the original files when saving. Instead we need to
// create new object names because the ones that were used in the
// original file may already be used by other embedded files/objects
// that are saved by other shapes.
//
// FIXME: There should only be ONE place where new object / file names
// are generated, not 2(?) like there are now:
// KoEmbeddedDocumentSaver and the KoImageCollection.
//
// An ObjectEntry is used to store information about objects in the
// frame, as defined above.
struct ObjectEntry {
QByteArray objectXmlContents; // the XML tree in the object
QString objectName; // object name in the frame without "./"
// This is extracted from objectXmlContents.
bool isDir;
KoOdfManifestEntry *manifestEntry; // manifest entry for the above.
};
// A FileEntry is used to store information about embedded files
// inside (i.e. referred to by) an object.
struct FileEntry {
QString path; // Normalized filename, i.e. without "./".
QString mimeType;
bool isDir;
QByteArray contents;
};
class KoUnavailShape::Private
{
public:
Private(KoUnavailShape* qq);
~Private();
void draw(QPainter &painter) const;
void drawNull(QPainter &painter) const;
void storeObjects(const KoXmlElement &element);
void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer,
ObjectEntry *object, QHash<QString, QString> &unknownNamespaces);
void storeFile(const QString &filename, KoShapeLoadingContext &context);
QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context);
// Objects inside the frame. For each file, we store:
// - The XML code for the object
// - Any embedded files (names, contents) that are referenced by xlink:href
// - Whether they are directories, i.e. if they contain a file tree and not just one file.
// - The manifest entries
QList<ObjectEntry*> objectEntries;
// Embedded files
QList<FileEntry*> embeddedFiles; // List of embedded files.
// Some cached values.
QPixmap questionMark;
QPixmap pixmapPreview;
QSvgRenderer *scalablePreview;
KoUnavailShape* q;
};
KoUnavailShape::Private::Private(KoUnavailShape* qq)
: scalablePreview(new QSvgRenderer())
, q(qq)
{
// Get the question mark "icon".
questionMark.load(":/questionmark.png");
}
KoUnavailShape::Private::~Private()
{
qDeleteAll(objectEntries);
qDeleteAll(embeddedFiles);
// It's a QObject, but we haven't parented it.
delete(scalablePreview);
}
// ----------------------------------------------------------------
// The main class
KoUnavailShape::KoUnavailShape()
: KoFrameShape( "", "" )
-, KoShapeContainer(new KoShapeContainerDefaultModel())
+, KoShapeContainer(new SimpleShapeContainerModel())
, d(new Private(this))
{
setShapeId(KoUnavailShape_SHAPEID);
// Default size of the shape.
KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) );
}
KoUnavailShape::~KoUnavailShape()
{
delete d;
}
void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
applyConversion(painter, converter);
// If the frame is empty, just draw a background.
debugFlake << "Number of objects:" << d->objectEntries.size();
if (d->objectEntries.isEmpty()) {
// But... only try to draw the background if there's one such
if (background()) {
QPainterPath p;
p.addRect(QRectF(QPointF(), size()));
background()->paint(painter, converter, paintContext, p);
}
} else {
if(shapes().isEmpty()) {
d->draw(painter);
}
}
}
void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
void KoUnavailShape::Private::draw(QPainter &painter) const
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
// Run through the previews in order of preference. Draw a placeholder
// questionmark if there is no preview available for rendering.
if (scalablePreview->isValid()) {
QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height());
scalablePreview->render(&painter, bounds);
}
else if (!pixmapPreview.isNull()) {
QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height());
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.drawPixmap(bounds, pixmapPreview);
}
else if (q->shapes().isEmpty()) {
// Draw a nice question mark with a frame around it if there
// is no other preview image. If there is a contained image
// shape, we don't need to draw anything.
// Get the question mark "icon".
// FIXME: We should be able to use d->questionMark here.
QPixmap questionMark;
questionMark.load(":/questionmark.png");
// The size of the image is:
// - the size of the shape if shapesize < 2cm
// - 2 cm if 2cm <= shapesize <= 8cm
// - shapesize / 4 if shapesize > 8cm
qreal width = q->size().width();
qreal height = q->size().height();
qreal picSize = CM_TO_POINT(2); // Default size is 2 cm.
if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2))
picSize = qMin(width, height);
else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8))
picSize = qMin(width, height) / qreal(4.0);
painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0),
picSize, picSize, questionMark);
// Draw a gray rectangle around the shape.
painter.setPen(QPen(QColor(172, 196, 206), 0));
painter.drawRect(QRectF(QPointF(0,0), q->size()));
}
painter.restore();
}
void KoUnavailShape::Private::drawNull(QPainter &painter) const
{
QRectF rect(QPointF(0,0), q->size());
painter.save();
// Draw a simple cross in a rectangle just to indicate that there is something here.
painter.drawLine(rect.topLeft(), rect.bottomRight());
painter.drawLine(rect.bottomLeft(), rect.topRight());
painter.restore();
}
// ----------------------------------------------------------------
// Loading and Saving
void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const
{
debugFlake << "START SAVING ##################################################";
KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver();
KoXmlWriter &writer = context.xmlWriter();
writer.startElement("draw:frame");
// See also loadOdf() in loadOdfAttributes.
saveOdfAttributes( context, OdfAllAttributes );
// Write the stored XML to the file, but don't reuse object names.
int lap = 0;
QString newName;
foreach (const ObjectEntry *object, d->objectEntries) {
QByteArray xmlArray(object->objectXmlContents);
QString objectName(object->objectName); // Possibly empty.
KoOdfManifestEntry *manifestEntry(object->manifestEntry);
// Create a name for this object. If this is not the first
// object, i.e. a replacement object (most likely a picture),
// then reuse the name but put it in ReplacementObjects.
if (++lap == 1) {
// The first lap in the loop is the actual object. All
// other laps are replacement objects.
newName = fileSaver.getFilename("Object ");
}
else if (lap == 2) {
newName = "ObjectReplacements/" + newName;
}
else
// FIXME: what should replacement 2 and onwards be called?
newName = newName + "_";
// If there was a previous object name, replace it with the new one.
if (!objectName.isEmpty() && manifestEntry) {
// FIXME: We must make a copy of the byte array here because
// otherwise we won't be able to save > 1 time.
xmlArray.replace(objectName.toLatin1(), newName.toLatin1());
}
writer.addCompleteElement(xmlArray.data());
// If the objectName is empty, this may be inline XML.
// If so, we are done now.
if (objectName.isEmpty() || !manifestEntry) {
continue;
}
// Save embedded files for this object.
foreach (FileEntry *entry, d->embeddedFiles) {
QString fileName(entry->path);
// If we found a file for this object, we need to write it
// but with the new object name instead of the old one.
if (!fileName.startsWith(objectName))
continue;
debugFlake << "Object name: " << objectName << "newName: " << newName
<< "filename: " << fileName << "isDir: " << entry->isDir;
fileName.replace(objectName, newName);
fileName.prepend("./");
debugFlake << "New filename: " << fileName;
// FIXME: Check if we need special treatment of directories.
fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents);
}
// Write the manifest entry for the object itself. If it's a
// file, the manifest is already written by saveFile, so skip
// it here.
if (object->isDir) {
fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(),
manifestEntry->version());
}
}
writer.endElement(); // draw:frame
}
bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context)
{
debugFlake << "START LOADING ##################################################";
//debugFlake << "Loading ODF frame in the KoUnavailShape. Element = "
// << frameElement.tagName();
loadOdfAttributes(frameElement, context, OdfAllAttributes);
// NOTE: We cannot use loadOdfFrame() because we want to save all
// the things inside the frame, not just one of them, like
// loadOdfFrame() provides.
// Get the manifest.
QList<KoOdfManifestEntry*> manifest = context.odfLoadingContext().manifestEntries();
#if 0 // Enable to show all manifest entries.
debugFlake << "MANIFEST: ";
foreach (KoOdfManifestEntry *entry, manifest) {
debugFlake << entry->mediaType() << entry->fullPath() << entry->version();
}
#endif
// 1. Get the XML contents of the objects from the draw:frame. As
// a side effect, this extracts the object names from all
// xlink:href and stores them into d->objectNames. The saved
// xml contents itself is saved into d->objectXmlContents
// (QByteArray) so we can save it back from saveOdf().
d->storeObjects(frameElement);
#if 1
// Debug only
debugFlake << "----------------------------------------------------------------";
debugFlake << "After storeObjects():";
foreach (ObjectEntry *object, d->objectEntries) {
debugFlake << "objectXmlContents: " << object->objectXmlContents
<< "objectName: " << object->objectName;
// Note: at this point, isDir and manifestEntry are not set.
#endif
}
// 2. Loop through the objects that were found in the frame and
// save all the files associated with them. Some of the
// objects are files, and some are directories. The
// directories are searched and the files within are saved as
// well.
//
// In this loop, isDir and manifestEntry of each ObjectEntry are set.
bool foundPreview = false;
foreach (ObjectEntry *object, d->objectEntries) {
QString objectName = object->objectName;
if (objectName.isEmpty())
continue;
debugFlake << "Storing files for object named:" << objectName;
// Try to find out if the entry is a directory.
// If the object is a directory, then save all the files
// inside it, otherwise save the file as it is.
QString dirName = objectName + '/';
bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty();
if (isDir) {
// A directory: the files can be found in the manifest.
foreach (KoOdfManifestEntry *entry, manifest) {
if (entry->fullPath() == dirName)
continue;
if (entry->fullPath().startsWith(dirName)) {
d->storeFile(entry->fullPath(), context);
}
}
}
else {
// A file: save it.
d->storeFile(objectName, context);
}
// Get the manifest entry for this object.
KoOdfManifestEntry *entry = 0;
QString entryName = isDir ? dirName : objectName;
for (int j = 0; j < manifest.size(); ++j) {
KoOdfManifestEntry *temp = manifest.value(j);
if (temp->fullPath() == entryName) {
entry = new KoOdfManifestEntry(*temp);
break;
}
}
object->isDir = isDir;
object->manifestEntry = entry;
// If we have not already found a preview in previous times
// through the loop, then see if this one may be a preview.
if (!foundPreview) {
debugFlake << "Attempting to load preview from " << objectName;
QByteArray previewData = d->loadFile(objectName, context);
// Check to see if we know the mimetype for this entry. Specifically:
// 1. Check to see if the item is a loadable SVG file
// FIXME: Perhaps check in the manifest first? But this
// seems to work well.
d->scalablePreview->load(previewData);
if (d->scalablePreview->isValid()) {
debugFlake << "Found scalable preview image!";
d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg"));
foundPreview = true;
continue;
}
// 2. Otherwise check to see if it's a loadable pixmap file
d->pixmapPreview.loadFromData(previewData);
if (!d->pixmapPreview.isNull()) {
debugFlake << "Found pixel based preview image!";
foundPreview = true;
}
}
}
#if 0 // Enable to get more detailed debug messages
debugFlake << "Object manifest entries:";
for (int i = 0; i < d->manifestEntries.size(); ++i) {
KoOdfManifestEntry *entry = d->manifestEntries.value(i);
debugFlake << i << ":" << entry;
if (entry)
debugFlake << entry->fullPath() << entry->mediaType() << entry->version();
else
debugFlake << "--";
}
debugFlake << "END LOADING ####################################################";
#endif
return true;
}
// Load the actual contents inside the frame.
bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/,
KoShapeLoadingContext &/*context*/)
{
return true;
}
// ----------------------------------------------------------------
// Private functions
void KoUnavailShape::Private::storeObjects(const KoXmlElement &element)
{
// 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 = new ObjectEntry;
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.
object->manifestEntry = 0;
objectEntries.append(object);
}
}
void KoUnavailShape::Private::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());
// Copy all the attributes, including namespaces.
QList< QPair<QString, QString> > attributeNames = el.attributeFullNames();
for (int i = 0; i < attributeNames.size(); ++i) {
QPair<QString, QString> attrPair(attributeNames.value(i));
if (attrPair.first.isEmpty()) {
writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second));
}
else {
// This somewhat convoluted code is because we need the
// namespace, not the namespace URI.
QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1());
// in case we don't find the namespace in our list create a own one and use that
// so the document created on saving is valid.
if (nsShort.isEmpty()) {
nsShort = unknownNamespaces.value(attrPair.first);
if (nsShort.isEmpty()) {
nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1);
unknownNamespaces.insert(attrPair.first, nsShort);
}
QString name = QString("xmlns:") + nsShort;
writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1());
}
QString attr(nsShort + ':' + attrPair.second);
writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first,
attrPair.second));
}
}
// 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();
}
/**
* This function stores the embedded file in an internal store - it does not save files to disk,
* and thus it is named in this manner, to avoid the function being confused with functions which
* save files to disk.
*/
void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context)
{
debugFlake << "Saving file: " << fileName;
// Directories need to be saved too, but they don't have any file contents.
if (fileName.endsWith('/')) {
FileEntry *entry = new FileEntry;
entry->path = fileName;
entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path);
entry->isDir = true;
embeddedFiles.append(entry);
}
QByteArray fileContent = loadFile(fileName, context);
if (fileContent.isNull())
return;
// Actually store the file in the list.
FileEntry *entry = new FileEntry;
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;
embeddedFiles.append(entry);
debugFlake << "File length: " << fileContent.size();
}
QByteArray KoUnavailShape::Private::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;
}
diff --git a/libs/flake/KoVectorPatternBackground.cpp b/libs/flake/KoVectorPatternBackground.cpp
new file mode 100644
index 0000000000..5e8484220a
--- /dev/null
+++ b/libs/flake/KoVectorPatternBackground.cpp
@@ -0,0 +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.
+ */
+
+#include "KoVectorPatternBackground.h"
+
+#include <QTransform>
+#include <KoShape.h>
+#include <KoShapePainter.h>
+#include "KoShapeBackground_p.h"
+#include <KoBakedShapeRenderer.h>
+#include <KoViewConverter.h>
+
+class KoVectorPatternBackgroundPrivate : public KoShapeBackgroundPrivate
+{
+public:
+ KoVectorPatternBackgroundPrivate()
+ {
+ }
+
+ ~KoVectorPatternBackgroundPrivate()
+ {
+ qDeleteAll(shapes);
+ shapes.clear();
+ }
+
+ QList<KoShape*> shapes;
+ KoFlake::CoordinateSystem referenceCoordinates =
+ KoFlake::ObjectBoundingBox;
+ KoFlake::CoordinateSystem contentCoordinates =
+ KoFlake::UserSpaceOnUse;
+ QRectF referenceRect;
+ QTransform patternTransform;
+};
+
+KoVectorPatternBackground::KoVectorPatternBackground()
+ : KoShapeBackground(*(new KoVectorPatternBackgroundPrivate()))
+{
+}
+
+KoVectorPatternBackground::~KoVectorPatternBackground()
+{
+
+}
+
+bool KoVectorPatternBackground::compareTo(const KoShapeBackground *other) const
+{
+ Q_UNUSED(other);
+ return false;
+}
+
+void KoVectorPatternBackground::setReferenceCoordinates(KoFlake::CoordinateSystem value)
+{
+ Q_D(KoVectorPatternBackground);
+ d->referenceCoordinates = value;
+}
+
+KoFlake::CoordinateSystem KoVectorPatternBackground::referenceCoordinates() const
+{
+ Q_D(const KoVectorPatternBackground);
+ return d->referenceCoordinates;
+}
+
+void KoVectorPatternBackground::setContentCoordinates(KoFlake::CoordinateSystem value)
+{
+ Q_D(KoVectorPatternBackground);
+ d->contentCoordinates = value;
+}
+
+KoFlake::CoordinateSystem KoVectorPatternBackground::contentCoordinates() const
+{
+ Q_D(const KoVectorPatternBackground);
+ return d->contentCoordinates;
+}
+
+void KoVectorPatternBackground::setReferenceRect(const QRectF &value)
+{
+ Q_D(KoVectorPatternBackground);
+ d->referenceRect = value;
+}
+
+QRectF KoVectorPatternBackground::referenceRect() const
+{
+ Q_D(const KoVectorPatternBackground);
+ return d->referenceRect;
+}
+
+void KoVectorPatternBackground::setPatternTransform(const QTransform &value)
+{
+ Q_D(KoVectorPatternBackground);
+ d->patternTransform = value;
+}
+
+QTransform KoVectorPatternBackground::patternTransform() const
+{
+ Q_D(const KoVectorPatternBackground);
+ return d->patternTransform;
+}
+
+void KoVectorPatternBackground::setShapes(const QList<KoShape*> value)
+{
+ Q_D(KoVectorPatternBackground);
+ qDeleteAll(d->shapes);
+ d->shapes.clear();
+
+ d->shapes = value;
+}
+
+QList<KoShape *> KoVectorPatternBackground::shapes() const
+{
+ Q_D(const KoVectorPatternBackground);
+ return d->shapes;
+}
+
+void KoVectorPatternBackground::paint(QPainter &painter, const KoViewConverter &converter_Unused, KoShapePaintingContext &context_Unused, const QPainterPath &fillPath) const
+{
+ Q_UNUSED(context_Unused);
+ Q_UNUSED(converter_Unused);
+
+ Q_D(const KoVectorPatternBackground);
+
+ 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);
+
+ // 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
new file mode 100644
index 0000000000..eba915be77
--- /dev/null
+++ b/libs/flake/KoVectorPatternBackground.h
@@ -0,0 +1,68 @@
+/*
+ * 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>
+
+class KoShape;
+class QPointF;
+class QRectF;
+class QTransform;
+class KoVectorPatternBackgroundPrivate;
+
+
+class KoVectorPatternBackground : public KoShapeBackground
+{
+public:
+ KoVectorPatternBackground();
+ ~KoVectorPatternBackground();
+
+ 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;
+ bool hasTransparency() const override;
+ void fillStyle(KoGenStyle &style, KoShapeSavingContext &context) override;
+ bool loadStyle(KoOdfLoadingContext &context, const QSizeF &shapeSize) override;
+
+private:
+ Q_DECLARE_PRIVATE(KoVectorPatternBackground)
+ Q_DISABLE_COPY(KoVectorPatternBackground)
+};
+
+#endif // KOVECTORPATTERNBACKGROUND_H
diff --git a/libs/flake/SimpleShapeContainerModel.h b/libs/flake/SimpleShapeContainerModel.h
index 3e042b6813..384198ba0b 100644
--- a/libs/flake/SimpleShapeContainerModel.h
+++ b/libs/flake/SimpleShapeContainerModel.h
@@ -1,68 +1,130 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007, 2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2011 Boudewijn Rempt <boud@kogmbh.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef SIMPLESHAPECONTAINERMODEL_H
#define SIMPLESHAPECONTAINERMODEL_H
#include "KoShapeContainerModel.h"
+#include <kis_debug.h>
/// \internal
class SimpleShapeContainerModel: public KoShapeContainerModel
{
public:
SimpleShapeContainerModel() {}
~SimpleShapeContainerModel() {}
+
+ SimpleShapeContainerModel(const SimpleShapeContainerModel &rhs)
+ : KoShapeContainerModel(rhs),
+ m_inheritsTransform(rhs.m_inheritsTransform),
+ m_clipped(rhs.m_clipped)
+ {
+ Q_FOREACH (KoShape *shape, rhs.m_members) {
+ m_members << shape->cloneShape();
+ }
+
+ KIS_ASSERT_RECOVER(m_members.size() == m_inheritsTransform.size() &&
+ m_members.size() == m_clipped.size())
+ {
+ qDeleteAll(m_members);
+ m_members.clear();
+ m_inheritsTransform.clear();
+ m_clipped.clear();
+ }
+ }
+
void add(KoShape *child) {
if (m_members.contains(child))
return;
m_members.append(child);
+ m_clipped.append(false);
+ m_inheritsTransform.append(true);
}
- void setClipped(const KoShape *, bool) { }
- bool isClipped(const KoShape *) const {
- return false;
+ void setClipped(const KoShape *shape, bool value) {
+ const int index = indexOf(shape);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0);
+ m_clipped[index] = value;
}
- void remove(KoShape *child) {
- m_members.removeAll(child);
+ bool isClipped(const KoShape *shape) const {
+ const int index = indexOf(shape);
+ KIS_SAFE_ASSERT_RECOVER(index >= 0) { return false;}
+ return m_clipped[index];
+ }
+ void remove(KoShape *shape) {
+ const int index = indexOf(shape);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0);
+
+ m_members.removeAt(index);
+ m_clipped.removeAt(index);
+ m_inheritsTransform.removeAt(index);
}
int count() const {
return m_members.count();
}
QList<KoShape*> shapes() const {
return QList<KoShape*>(m_members);
}
void containerChanged(KoShapeContainer *, KoShape::ChangeType) { }
bool isChildLocked(const KoShape *child) const {
Q_ASSERT(child->parent());
if (child->parent()) {
return child->isGeometryProtected() || child->parent()->isGeometryProtected();
}
else {
return child->isGeometryProtected();
}
}
- void setInheritsTransform(const KoShape *, bool ) { }
- bool inheritsTransform(const KoShape *) const {
- return false;
+ void setInheritsTransform(const KoShape *shape, bool value) {
+ const int index = indexOf(shape);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0);
+ m_inheritsTransform[index] = value;
+ }
+ bool inheritsTransform(const KoShape *shape) const {
+ const int index = indexOf(shape);
+ KIS_SAFE_ASSERT_RECOVER(index >= 0) { return true;}
+ return m_inheritsTransform[index];
+ }
+
+ void proposeMove(KoShape *shape, QPointF &move)
+ {
+ KoShapeContainer *parent = shape->parent();
+ bool allowedToMove = true;
+ while (allowedToMove && parent) {
+ allowedToMove = parent->isEditable();
+ parent = parent->parent();
+ }
+ if (! allowedToMove) {
+ move.setX(0);
+ move.setY(0);
+ }
+ }
+
+private:
+ int indexOf(const KoShape *shape) const {
+ // workaround indexOf constness!
+ return m_members.indexOf(const_cast<KoShape*>(shape));
}
private: // members
QList <KoShape *> m_members;
+ QList <bool> m_inheritsTransform;
+ QList <bool> m_clipped;
};
#endif
diff --git a/libs/flake/commands/KoMultiPathPointJoinCommand.cpp b/libs/flake/commands/KoMultiPathPointJoinCommand.cpp
new file mode 100644
index 0000000000..3af7f3b84b
--- /dev/null
+++ b/libs/flake/commands/KoMultiPathPointJoinCommand.cpp
@@ -0,0 +1,37 @@
+/*
+ * 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 "KoMultiPathPointJoinCommand.h"
+
+#include <KoSubpathJoinCommand.h>
+
+KoMultiPathPointJoinCommand::KoMultiPathPointJoinCommand(const KoPathPointData &pointData1,
+ const KoPathPointData &pointData2,
+ KoShapeBasedDocumentBase *controller,
+ KoSelection *selection,
+ KUndo2Command *parent)
+ : KoMultiPathPointMergeCommand(pointData1, pointData2, controller, selection, parent)
+{
+ setText(kundo2_i18n("Join subpaths"));
+}
+
+KUndo2Command *KoMultiPathPointJoinCommand::createMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2)
+{
+ return new KoSubpathJoinCommand(pointData1, pointData2);
+}
+
diff --git a/libs/flake/commands/KoMultiPathPointJoinCommand.h b/libs/flake/commands/KoMultiPathPointJoinCommand.h
new file mode 100644
index 0000000000..b1505f3ff3
--- /dev/null
+++ b/libs/flake/commands/KoMultiPathPointJoinCommand.h
@@ -0,0 +1,38 @@
+/*
+ * 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 KOMULTIPATHPOINTJOINCOMMAND_H
+#define KOMULTIPATHPOINTJOINCOMMAND_H
+
+#include <KoMultiPathPointMergeCommand.h>
+
+class KRITAFLAKE_EXPORT KoMultiPathPointJoinCommand : public KoMultiPathPointMergeCommand
+{
+public:
+ KoMultiPathPointJoinCommand(const KoPathPointData &pointData1,
+ const KoPathPointData &pointData2,
+ KoShapeBasedDocumentBase *controller,
+ KoSelection *selection,
+ KUndo2Command *parent = 0);
+
+protected:
+ virtual KUndo2Command *createMergeCommand(const KoPathPointData &pointData1,
+ const KoPathPointData &pointData2);
+};
+
+#endif // KOMULTIPATHPOINTJOINCOMMAND_H
diff --git a/libs/flake/commands/KoMultiPathPointMergeCommand.cpp b/libs/flake/commands/KoMultiPathPointMergeCommand.cpp
new file mode 100644
index 0000000000..3d7408db38
--- /dev/null
+++ b/libs/flake/commands/KoMultiPathPointMergeCommand.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 "KoMultiPathPointMergeCommand.h"
+#include <KoPathPointData.h>
+
+#include <KoPathCombineCommand.h>
+#include <KoPathPointMergeCommand.h>
+#include <KoSelection.h>
+
+#include "kis_assert.h"
+
+
+struct Q_DECL_HIDDEN KoMultiPathPointMergeCommand::Private
+{
+ Private(const KoPathPointData &_pointData1, const KoPathPointData &_pointData2, KoShapeBasedDocumentBase *_controller, KoSelection *_selection)
+ : pointData1(_pointData1),
+ pointData2(_pointData2),
+ controller(_controller),
+ selection(_selection)
+ {
+ }
+
+ KoPathPointData pointData1;
+ KoPathPointData pointData2;
+ KoShapeBasedDocumentBase *controller;
+ KoSelection *selection;
+
+
+ QScopedPointer<KoPathCombineCommand> combineCommand;
+ QScopedPointer<KUndo2Command> mergeCommand;
+};
+
+KoMultiPathPointMergeCommand::KoMultiPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KoShapeBasedDocumentBase *controller, KoSelection *selection, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18n("Merge points"), parent),
+ m_d(new Private(pointData1, pointData2, controller, selection))
+{
+}
+
+KoMultiPathPointMergeCommand::~KoMultiPathPointMergeCommand()
+{
+}
+
+KUndo2Command *KoMultiPathPointMergeCommand::createMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2)
+{
+ return new KoPathPointMergeCommand(pointData1, pointData2);
+}
+
+void KoMultiPathPointMergeCommand::redo()
+{
+ if (m_d->selection) {
+ m_d->selection->deselectAll();
+ }
+
+ KoShape *mergedShape = 0;
+
+ if (m_d->pointData1.pathShape != m_d->pointData2.pathShape) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->controller);
+
+ QList<KoPathShape*> shapes = {m_d->pointData1.pathShape, m_d->pointData2.pathShape};
+ m_d->combineCommand.reset(new KoPathCombineCommand(m_d->controller, shapes));
+ m_d->combineCommand->redo();
+
+ KoPathPointData newPD1 = m_d->combineCommand->originalToCombined(m_d->pointData1);
+ KoPathPointData newPD2 = m_d->combineCommand->originalToCombined(m_d->pointData2);
+
+ m_d->mergeCommand.reset(createMergeCommand(newPD1, newPD2));
+ m_d->mergeCommand->redo();
+
+ mergedShape = m_d->combineCommand->combinedPath();
+
+ } else {
+ m_d->mergeCommand.reset(createMergeCommand(m_d->pointData1, m_d->pointData2));
+ m_d->mergeCommand->redo();
+
+ mergedShape = m_d->pointData1.pathShape;
+ }
+
+ if (m_d->selection) {
+ m_d->selection->select(mergedShape);
+ }
+
+ KUndo2Command::redo();
+}
+
+KoPathShape *KoMultiPathPointMergeCommand::testingCombinedPath() const
+{
+ return m_d->combineCommand ? m_d->combineCommand->combinedPath() : 0;
+}
+
+void KoMultiPathPointMergeCommand::undo()
+{
+ KUndo2Command::undo();
+
+ if (m_d->selection) {
+ m_d->selection->deselectAll();
+ }
+
+ if (m_d->mergeCommand) {
+ m_d->mergeCommand->undo();
+ m_d->mergeCommand.reset();
+ }
+
+ if (m_d->combineCommand) {
+ m_d->combineCommand->undo();
+ m_d->combineCommand.reset();
+ }
+
+ if (m_d->selection) {
+ m_d->selection->select(m_d->pointData1.pathShape);
+ m_d->selection->select(m_d->pointData2.pathShape);
+ }
+}
+
diff --git a/libs/flake/commands/KoMultiPathPointMergeCommand.h b/libs/flake/commands/KoMultiPathPointMergeCommand.h
new file mode 100644
index 0000000000..597593ba0c
--- /dev/null
+++ b/libs/flake/commands/KoMultiPathPointMergeCommand.h
@@ -0,0 +1,56 @@
+/*
+ * 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 KOMULTIPATHPOINTMERGECOMMAND_H
+#define KOMULTIPATHPOINTMERGECOMMAND_H
+
+#include <kundo2command.h>
+
+#include "kritaflake_export.h"
+#include <QScopedPointer>
+
+class KoSelection;
+class KoPathShape;
+class KoPathPointData;
+class KoShapeBasedDocumentBase;
+
+
+class KRITAFLAKE_EXPORT KoMultiPathPointMergeCommand : public KUndo2Command
+{
+public:
+ KoMultiPathPointMergeCommand(const KoPathPointData &pointData1,
+ const KoPathPointData &pointData2,
+ KoShapeBasedDocumentBase *controller,
+ KoSelection *selection,
+ KUndo2Command *parent = 0);
+ ~KoMultiPathPointMergeCommand();
+
+ void undo();
+ void redo();
+
+ KoPathShape *testingCombinedPath() const;
+
+protected:
+ virtual KUndo2Command *createMergeCommand(const KoPathPointData &pointData1,
+ const KoPathPointData &pointData2);
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KOMULTIPATHPOINTMERGECOMMAND_H
diff --git a/libs/flake/commands/KoParameterHandleMoveCommand.cpp b/libs/flake/commands/KoParameterHandleMoveCommand.cpp
index d3d2a865e3..e130792589 100644
--- a/libs/flake/commands/KoParameterHandleMoveCommand.cpp
+++ b/libs/flake/commands/KoParameterHandleMoveCommand.cpp
@@ -1,58 +1,81 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoParameterHandleMoveCommand.h"
#include "KoParameterShape.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
KoParameterHandleMoveCommand::KoParameterHandleMoveCommand(KoParameterShape *shape, int handleId, const QPointF &startPoint, const QPointF &endPoint, Qt::KeyboardModifiers keyModifiers, KUndo2Command *parent)
: KUndo2Command(parent)
, m_shape(shape)
, m_handleId(handleId)
, m_startPoint(startPoint)
, m_endPoint(endPoint)
, m_keyModifiers(keyModifiers)
{
setText(kundo2_i18n("Change parameter"));
}
KoParameterHandleMoveCommand::~KoParameterHandleMoveCommand()
{
}
/// redo the command
void KoParameterHandleMoveCommand::redo()
{
KUndo2Command::redo();
m_shape->update();
m_shape->moveHandle(m_handleId, m_endPoint, m_keyModifiers);
m_shape->update();
}
/// revert the actions done in redo
void KoParameterHandleMoveCommand::undo()
{
KUndo2Command::undo();
m_shape->update();
m_shape->moveHandle(m_handleId, m_startPoint);
m_shape->update();
}
+int KoParameterHandleMoveCommand::id() const
+{
+ return KisCommandUtils::ChangeShapeParameterId;
+}
+
+bool KoParameterHandleMoveCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoParameterHandleMoveCommand *other = dynamic_cast<const KoParameterHandleMoveCommand*>(command);
+
+ if (!other ||
+ other->m_shape != m_shape ||
+ other->m_handleId != m_handleId ||
+ other->m_keyModifiers != m_keyModifiers) {
+
+ return false;
+ }
+
+ m_endPoint = other->m_endPoint;
+
+ return true;
+}
+
diff --git a/libs/flake/commands/KoParameterHandleMoveCommand.h b/libs/flake/commands/KoParameterHandleMoveCommand.h
index b253e1a625..d21d8140f0 100644
--- a/libs/flake/commands/KoParameterHandleMoveCommand.h
+++ b/libs/flake/commands/KoParameterHandleMoveCommand.h
@@ -1,60 +1,64 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOPARAMETERHANDLEMOVECOMMAND_H
#define KOPARAMETERHANDLEMOVECOMMAND_H
#include <kundo2command.h>
#include <QPointF>
#include "kritaflake_export.h"
class KoParameterShape;
/// The undo / redo command for changing a parameter
class KoParameterHandleMoveCommand : public KUndo2Command
{
public:
/**
* Constructor.
* @param shape the shape this command works on
* @param handleId the ID under which the parameterShape knows the handle in KoParameterShape::moveHandle()
* @param startPoint The old position
* @param endPoint The new position
* @param keyModifiers the key modifiers used while moving.
* @param parent the parent command if this is a compound undo command.
*/
KoParameterHandleMoveCommand(KoParameterShape *shape, int handleId, const QPointF &startPoint, const QPointF &endPoint, Qt::KeyboardModifiers keyModifiers, KUndo2Command *parent = 0);
virtual ~KoParameterHandleMoveCommand();
/// redo the command
- void redo();
+ void redo() override;
/// revert the actions done in redo
- void undo();
+ void undo() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
KoParameterShape *m_shape;
int m_handleId;
QPointF m_startPoint;
QPointF m_endPoint;
Qt::KeyboardModifiers m_keyModifiers;
};
#endif // KOPARAMETERHANDLEMOVECOMMAND_H
diff --git a/libs/flake/commands/KoParameterToPathCommand.cpp b/libs/flake/commands/KoParameterToPathCommand.cpp
index 0168498336..141eea08fc 100644
--- a/libs/flake/commands/KoParameterToPathCommand.cpp
+++ b/libs/flake/commands/KoParameterToPathCommand.cpp
@@ -1,113 +1,112 @@
/* 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>
*
* 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 "KoParameterToPathCommand.h"
#include "KoPathPoint.h"
#include "KoParameterShape.h"
#include <klocalizedstring.h>
class KoParameterToPathCommandPrivate
{
public:
~KoParameterToPathCommandPrivate() {
qDeleteAll(copies);
}
void initialize();
void copyPath(KoPathShape *destination, KoPathShape *source);
QList<KoParameterShape*> shapes;
QList<KoPathShape*> copies;
};
KoParameterToPathCommand::KoParameterToPathCommand(KoParameterShape *shape, KUndo2Command *parent)
: KUndo2Command(parent),
d(new KoParameterToPathCommandPrivate())
{
d->shapes.append(shape);
d->initialize();
setText(kundo2_i18n("Convert to Path"));
}
KoParameterToPathCommand::KoParameterToPathCommand(const QList<KoParameterShape*> &shapes, KUndo2Command *parent)
: KUndo2Command(parent),
d(new KoParameterToPathCommandPrivate())
{
d->shapes = shapes;
d->initialize();
setText(kundo2_i18n("Convert to Path"));
}
KoParameterToPathCommand::~KoParameterToPathCommand()
{
delete d;
}
void KoParameterToPathCommand::redo()
{
KUndo2Command::redo();
for (int i = 0; i < d->shapes.size(); ++i) {
KoParameterShape *parameterShape = d->shapes.at(i);
parameterShape->update();
parameterShape->setParametricShape(false);
parameterShape->update();
}
}
void KoParameterToPathCommand::undo()
{
KUndo2Command::undo();
for (int i = 0; i < d->shapes.size(); ++i) {
KoParameterShape * parameterShape = d->shapes.at(i);
parameterShape->update();
parameterShape->setParametricShape(true);
d->copyPath(parameterShape, d->copies[i]);
parameterShape->update();
}
}
void KoParameterToPathCommandPrivate::initialize()
{
Q_FOREACH (KoParameterShape *shape, shapes) {
KoPathShape *p = new KoPathShape();
copyPath(p, shape);
copies.append(p);
}
}
void KoParameterToPathCommandPrivate::copyPath(KoPathShape *destination, KoPathShape *source)
{
destination->clear();
int subpathCount = source->subpathCount();
for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) {
int pointCount = source->subpathPointCount(subpathIndex);
if (! pointCount)
continue;
KoSubpath * subpath = new KoSubpath;
for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) {
KoPathPoint * p = source->pointByIndex(KoPathPointIndex(subpathIndex, pointIndex));
- KoPathPoint * c = new KoPathPoint(*p);
- c->setParent(destination);
+ KoPathPoint * c = new KoPathPoint(*p, destination);
subpath->append(c);
}
destination->addSubpath(subpath, subpathIndex);
}
destination->setTransformation(source->transformation());
}
diff --git a/libs/flake/commands/KoPathCombineCommand.cpp b/libs/flake/commands/KoPathCombineCommand.cpp
index db89f6d7b2..80264c9137 100644
--- a/libs/flake/commands/KoPathCombineCommand.cpp
+++ b/libs/flake/commands/KoPathCombineCommand.cpp
@@ -1,121 +1,144 @@
/* 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>
*
* 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 "KoPathCombineCommand.h"
#include "KoShapeBasedDocumentBase.h"
#include "KoShapeContainer.h"
#include "KoPathShape.h"
#include <klocalizedstring.h>
+#include "kis_assert.h"
+#include <KoPathPointData.h>
+
+#include <QHash>
class Q_DECL_HIDDEN KoPathCombineCommand::Private
{
public:
Private(KoShapeBasedDocumentBase *c, const QList<KoPathShape*> &p)
: controller(c), paths(p)
- , combinedPath(0), combinedPathParent(0)
+ , combinedPath(0)
+ , combinedPathParent(0)
, isCombined(false)
{
- foreach (KoPathShape * path, paths)
+ foreach (KoPathShape * path, paths) {
oldParents.append(path->parent());
+ }
}
~Private() {
if (isCombined && controller) {
- Q_FOREACH (KoPathShape* path, paths)
+ Q_FOREACH (KoPathShape* path, paths) {
delete path;
- } else
+ }
+ } else {
delete combinedPath;
+ }
}
KoShapeBasedDocumentBase *controller;
QList<KoPathShape*> paths;
QList<KoShapeContainer*> oldParents;
KoPathShape *combinedPath;
KoShapeContainer *combinedPathParent;
+
+ QHash<KoPathShape*, int> shapeStartSegmentIndex;
+
bool isCombined;
};
KoPathCombineCommand::KoPathCombineCommand(KoShapeBasedDocumentBase *controller,
const QList<KoPathShape*> &paths, KUndo2Command *parent)
-: KUndo2Command(parent)
-, d(new Private(controller, paths))
+ : KUndo2Command(kundo2_i18n("Combine paths"), parent)
+ , d(new Private(controller, paths))
{
- setText(kundo2_i18n("Combine paths"));
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!paths.isEmpty());
- d->combinedPath = new KoPathShape();
- d->combinedPath->setStroke(d->paths.first()->stroke());
- d->combinedPath->setShapeId(d->paths.first()->shapeId());
- // combine the paths
Q_FOREACH (KoPathShape* path, d->paths) {
- d->combinedPath->combine(path);
- if (! d->combinedPathParent && path->parent())
+ if (!d->combinedPath) {
+ KoPathShape *clone = dynamic_cast<KoPathShape*>(path->cloneShape());
+ KIS_ASSERT_RECOVER_BREAK(clone);
+
+ d->combinedPath = clone;
d->combinedPathParent = path->parent();
+ d->shapeStartSegmentIndex[path] = 0;
+ } else {
+ const int startSegmentIndex = d->combinedPath->combine(path);
+ d->shapeStartSegmentIndex[path] = startSegmentIndex;
+ }
}
}
KoPathCombineCommand::~KoPathCombineCommand()
{
delete d;
}
void KoPathCombineCommand::redo()
{
KUndo2Command::redo();
-
- if (! d->paths.size())
- return;
+ if (d->paths.isEmpty()) return;
d->isCombined = true;
if (d->controller) {
- QList<KoShapeContainer*>::iterator parentIt = d->oldParents.begin();
Q_FOREACH (KoPathShape* p, d->paths) {
d->controller->removeShape(p);
- if (*parentIt)
- (*parentIt)->removeShape(p);
- ++parentIt;
-
+ p->setParent(0);
}
- if (d->combinedPathParent)
- d->combinedPathParent->addShape(d->combinedPath);
+ d->combinedPath->setParent(d->combinedPathParent);
d->controller->addShape(d->combinedPath);
}
}
void KoPathCombineCommand::undo()
{
if (! d->paths.size())
return;
d->isCombined = false;
if (d->controller) {
d->controller->removeShape(d->combinedPath);
- if (d->combinedPath->parent())
- d->combinedPath->parent()->removeShape(d->combinedPath);
- QList<KoShapeContainer*>::iterator parentIt = d->oldParents.begin();
+ d->combinedPath->setParent(0);
+
+ auto parentIt = d->oldParents.begin();
Q_FOREACH (KoPathShape* p, d->paths) {
- d->controller->addShape(p);
p->setParent(*parentIt);
+ d->controller->addShape(p);
++parentIt;
}
}
KUndo2Command::undo();
}
+KoPathShape *KoPathCombineCommand::combinedPath() const
+{
+ return d->combinedPath;
+}
+
+KoPathPointData KoPathCombineCommand::originalToCombined(KoPathPointData pd) const
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->shapeStartSegmentIndex.contains(pd.pathShape), pd);
+
+ const int segmentOffet = d->shapeStartSegmentIndex[pd.pathShape];
+
+ KoPathPointIndex newIndex(segmentOffet + pd.pointIndex.first, pd.pointIndex.second);
+ return KoPathPointData(d->combinedPath, newIndex);
+}
+
diff --git a/libs/flake/commands/KoPathCombineCommand.h b/libs/flake/commands/KoPathCombineCommand.h
index 680e872df4..4c3ddb8bf8 100644
--- a/libs/flake/commands/KoPathCombineCommand.h
+++ b/libs/flake/commands/KoPathCombineCommand.h
@@ -1,53 +1,57 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 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 KOPATHCOMBINECOMMAND_H
#define KOPATHCOMBINECOMMAND_H
#include <kundo2command.h>
#include <QList>
#include "kritaflake_export.h"
class KoShapeBasedDocumentBase;
class KoPathShape;
+class KoPathPointData;
/// The undo / redo command for combining two or more paths into one
class KRITAFLAKE_EXPORT KoPathCombineCommand : public KUndo2Command
{
public:
/**
* Command for combining a list of paths into one single path.
* @param controller the controller to used for removing/inserting.
* @param paths the list of paths to combine
* @param parent the parent command used for macro commands
*/
KoPathCombineCommand(KoShapeBasedDocumentBase *controller, const QList<KoPathShape*> &paths, KUndo2Command *parent = 0);
virtual ~KoPathCombineCommand();
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
+ KoPathShape *combinedPath() const;
+ KoPathPointData originalToCombined(KoPathPointData pd) const;
+
private:
class Private;
Private * const d;
};
#endif // KOPATHCOMBINECOMMAND_H
diff --git a/libs/flake/commands/KoPathControlPointMoveCommand.cpp b/libs/flake/commands/KoPathControlPointMoveCommand.cpp
index fa367815cc..882a111d3d 100644
--- a/libs/flake/commands/KoPathControlPointMoveCommand.cpp
+++ b/libs/flake/commands/KoPathControlPointMoveCommand.cpp
@@ -1,96 +1,117 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 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 "KoPathControlPointMoveCommand.h"
#include <klocalizedstring.h>
#include <math.h>
+#include "kis_command_ids.h"
KoPathControlPointMoveCommand::KoPathControlPointMoveCommand(
const KoPathPointData &pointData,
const QPointF &offset,
KoPathPoint::PointType pointType,
KUndo2Command *parent)
: KUndo2Command(parent)
, m_pointData(pointData)
, m_pointType(pointType)
{
Q_ASSERT(offset.x() < 1e14 && offset.y() < 1e14);
KoPathShape * pathShape = m_pointData.pathShape;
KoPathPoint * point = pathShape->pointByIndex(m_pointData.pointIndex);
if (point) {
m_offset = point->parent()->documentToShape(offset) - point->parent()->documentToShape(QPointF(0, 0));
}
setText(kundo2_i18n("Move control point"));
}
void KoPathControlPointMoveCommand::redo()
{
KUndo2Command::redo();
KoPathShape * pathShape = m_pointData.pathShape;
KoPathPoint * point = pathShape->pointByIndex(m_pointData.pointIndex);
if (point) {
pathShape->update();
if (m_pointType == KoPathPoint::ControlPoint1) {
point->setControlPoint1(point->controlPoint1() + m_offset);
if (point->properties() & KoPathPoint::IsSymmetric) {
// set the other control point so that it lies on the line between the moved
// control point and the point, with the same distance to the point as the moved point
point->setControlPoint2(2.0 * point->point() - point->controlPoint1());
} else if (point->properties() & KoPathPoint::IsSmooth) {
// move the other control point so that it lies on the line through point and control point
// keeping its distance to the point
QPointF direction = point->point() - point->controlPoint1();
direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
QPointF distance = point->point() - point->controlPoint2();
qreal length = sqrt(distance.x() * distance.x() + distance.y() * distance.y());
point->setControlPoint2(point->point() + length * direction);
}
} else if (m_pointType == KoPathPoint::ControlPoint2) {
point->setControlPoint2(point->controlPoint2() + m_offset);
if (point->properties() & KoPathPoint::IsSymmetric) {
// set the other control point so that it lies on the line between the moved
// control point and the point, with the same distance to the point as the moved point
point->setControlPoint1(2.0 * point->point() - point->controlPoint2());
} else if (point->properties() & KoPathPoint::IsSmooth) {
// move the other control point so that it lies on the line through point and control point
// keeping its distance to the point
QPointF direction = point->point() - point->controlPoint2();
direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y());
QPointF distance = point->point() - point->controlPoint1();
qreal length = sqrt(distance.x() * distance.x() + distance.y() * distance.y());
point->setControlPoint1(point->point() + length * direction);
}
}
pathShape->normalize();
pathShape->update();
}
}
void KoPathControlPointMoveCommand::undo()
{
KUndo2Command::undo();
m_offset *= -1.0;
redo();
m_offset *= -1.0;
}
+int KoPathControlPointMoveCommand::id() const
+{
+ return KisCommandUtils::ChangePathShapeControlPointId;
+}
+
+bool KoPathControlPointMoveCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoPathControlPointMoveCommand *other = dynamic_cast<const KoPathControlPointMoveCommand*>(command);
+
+ if (!other ||
+ other->m_pointData != m_pointData ||
+ other->m_pointType != m_pointType) {
+
+ return false;
+ }
+
+ m_offset += other->m_offset;
+
+ return true;
+}
diff --git a/libs/flake/commands/KoPathControlPointMoveCommand.h b/libs/flake/commands/KoPathControlPointMoveCommand.h
index 131256095d..e1d14381ba 100644
--- a/libs/flake/commands/KoPathControlPointMoveCommand.h
+++ b/libs/flake/commands/KoPathControlPointMoveCommand.h
@@ -1,54 +1,58 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 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 KOPATHCONTROLPOINTMOVECOMMAND_H
#define KOPATHCONTROLPOINTMOVECOMMAND_H
#include <kundo2command.h>
#include <QPointF>
#include "KoPathPointData.h"
#include "KoPathPoint.h"
#include "kritaflake_export.h"
/// The undo / redo command for path point moving.
class KRITAFLAKE_EXPORT KoPathControlPointMoveCommand : public KUndo2Command
{
public:
/**
* Command to move one control path point.
* @param offset the offset by which the point is moved in document coordinates
* @param pointType the type of the point to move
* @param parent the parent command used for macro commands
*/
KoPathControlPointMoveCommand(const KoPathPointData &pointData, const QPointF &offset,
KoPathPoint::PointType pointType, KUndo2Command *parent = 0);
/// redo the command
- void redo();
+ void redo() override;
/// revert the actions done in redo
- void undo();
+ void undo() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
KoPathPointData m_pointData;
// the offset in shape coordinates
QPointF m_offset;
KoPathPoint::PointType m_pointType;
};
#endif // KOPATHCONTROLPOINTMOVECOMMAND_H
diff --git a/libs/flake/commands/KoPathPointMergeCommand.cpp b/libs/flake/commands/KoPathPointMergeCommand.cpp
index 99f43a8c06..e0bbeae528 100644
--- a/libs/flake/commands/KoPathPointMergeCommand.cpp
+++ b/libs/flake/commands/KoPathPointMergeCommand.cpp
@@ -1,242 +1,262 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 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 "KoPathPointMergeCommand.h"
#include "KoPathPoint.h"
#include "KoPathPointData.h"
#include "KoPathShape.h"
#include <klocalizedstring.h>
#include <QPointF>
+#include "kis_assert.h"
+
class Q_DECL_HIDDEN KoPathPointMergeCommand::Private
{
public:
Private(const KoPathPointData &pointData1, const KoPathPointData &pointData2)
: pathShape(pointData1.pathShape)
, endPoint(pointData1.pointIndex)
, startPoint(pointData2.pointIndex)
, splitIndex(KoPathPointIndex(-1, -1))
, removedPoint(0)
+ , mergedPointIndex(-1, -1)
, reverse(ReverseNone)
{
}
~Private()
{
delete removedPoint;
}
KoPathPoint * mergePoints(KoPathPoint * p1, KoPathPoint * p2)
{
QPointF mergePosition = 0.5 * (p1->point() + p2->point());
QPointF mergeControlPoint1 = mergePosition + (p1->controlPoint1() - p1->point());
QPointF mergeControlPoint2 = mergePosition + (p2->controlPoint2() - p2->point());
// change position and control points of first merged point
p1->setPoint(mergePosition);
if (p1->activeControlPoint1()) {
p1->setControlPoint1(mergeControlPoint1);
}
if (p2->activeControlPoint2()) {
p1->setControlPoint2(mergeControlPoint2);
}
// remove the second merged point
KoPathPointIndex removeIndex = pathShape->pathPointIndex(p2);
return pathShape->removePoint(removeIndex);
}
void resetPoints(KoPathPointIndex index1, KoPathPointIndex index2)
{
KoPathPoint * p1 = pathShape->pointByIndex(index1);
KoPathPoint * p2 = pathShape->pointByIndex(index2);
p1->setPoint(pathShape->documentToShape(oldNodePoint1));
p2->setPoint(pathShape->documentToShape(oldNodePoint2));
if (p1->activeControlPoint1()) {
p1->setControlPoint1(pathShape->documentToShape(oldControlPoint1));
}
if (p2->activeControlPoint2()) {
p2->setControlPoint2(pathShape->documentToShape(oldControlPoint2));
}
}
KoPathShape * pathShape;
KoPathPointIndex endPoint;
KoPathPointIndex startPoint;
KoPathPointIndex splitIndex;
// the control points have to be stored in document positions
QPointF oldNodePoint1;
QPointF oldControlPoint1;
QPointF oldNodePoint2;
QPointF oldControlPoint2;
KoPathPoint * removedPoint;
+ KoPathPointIndex mergedPointIndex;
enum Reverse {
ReverseNone = 0,
ReverseFirst = 1,
ReverseSecond = 2
};
int reverse;
};
/**
* How does is work:
*
* The goal is to merge the point that is ending an open subpath with the one
* starting the same or another open subpath.
*/
KoPathPointMergeCommand::KoPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent)
: KUndo2Command(parent), d(new Private(pointData1, pointData2))
{
- Q_ASSERT(pointData1.pathShape == pointData2.pathShape);
- Q_ASSERT(d->pathShape);
- Q_ASSERT(!d->pathShape->isClosedSubpath(d->endPoint.first));
- Q_ASSERT(d->endPoint.second == 0 ||
+ KIS_ASSERT(pointData1.pathShape == pointData2.pathShape);
+ KIS_ASSERT(d->pathShape);
+
+ KIS_ASSERT(!d->pathShape->isClosedSubpath(d->endPoint.first));
+ KIS_ASSERT(!d->pathShape->isClosedSubpath(d->startPoint.first));
+
+ KIS_ASSERT(d->endPoint.second == 0 ||
d->endPoint.second == d->pathShape->subpathPointCount(d->endPoint.first) - 1);
- Q_ASSERT(!d->pathShape->isClosedSubpath(d->startPoint.first));
- Q_ASSERT(d->startPoint.second == 0 ||
+
+ KIS_ASSERT(d->startPoint.second == 0 ||
d->startPoint.second == d->pathShape->subpathPointCount(d->startPoint.first) - 1);
+ KIS_ASSERT(d->startPoint != d->endPoint);
+
// if we have two different subpaths we might need to reverse them
if (d->endPoint.first != d->startPoint.first) {
// sort by point index
if (d->startPoint < d->endPoint)
qSwap(d->endPoint, d->startPoint);
// mark first subpath to be reversed if first point starts a subpath with more than one point
if (d->endPoint.second == 0 && d->pathShape->subpathPointCount(d->endPoint.first) > 1)
d->reverse |= Private::ReverseFirst;
// mark second subpath to be reversed if second point does not start a subpath with more than one point
if (d->startPoint.second != 0 && d->pathShape->subpathPointCount(d->startPoint.first) > 1)
d->reverse |= Private::ReverseSecond;
} else {
Q_ASSERT(d->endPoint.second != d->startPoint.second);
if (d->endPoint < d->startPoint)
qSwap(d->endPoint, d->startPoint);
}
KoPathPoint * p1 = d->pathShape->pointByIndex(d->endPoint);
KoPathPoint * p2 = d->pathShape->pointByIndex(d->startPoint);
d->oldNodePoint1 = d->pathShape->shapeToDocument(p1->point());
if (d->reverse & Private::ReverseFirst) {
d->oldControlPoint1 = d->pathShape->shapeToDocument(p1->controlPoint2());
} else {
d->oldControlPoint1 = d->pathShape->shapeToDocument(p1->controlPoint1());
}
d->oldNodePoint2 = d->pathShape->shapeToDocument(p2->point());
if (d->reverse & Private::ReverseSecond) {
d->oldControlPoint2 = d->pathShape->shapeToDocument(p2->controlPoint1());
} else {
d->oldControlPoint2 = d->pathShape->shapeToDocument(p2->controlPoint2());
}
setText(kundo2_i18n("Merge points"));
}
KoPathPointMergeCommand::~KoPathPointMergeCommand()
{
delete d;
}
void KoPathPointMergeCommand::redo()
{
KUndo2Command::redo();
if (d->removedPoint)
return;
d->pathShape->update();
KoPathPoint * endPoint = d->pathShape->pointByIndex(d->endPoint);
KoPathPoint * startPoint = d->pathShape->pointByIndex(d->startPoint);
// are we just closing a single subpath ?
if (d->endPoint.first == d->startPoint.first) {
// change the endpoint of the subpath
d->removedPoint = d->mergePoints(endPoint, startPoint);
// set endpoint of subpath to close the subpath
endPoint->setProperty(KoPathPoint::CloseSubpath);
// set new startpoint of subpath to close the subpath
KoPathPointIndex newStartIndex(d->startPoint.first,0);
d->pathShape->pointByIndex(newStartIndex)->setProperty(KoPathPoint::CloseSubpath);
+
+ d->mergedPointIndex = d->pathShape->pathPointIndex(endPoint);
+
} else {
// first revert subpaths if needed
if (d->reverse & Private::ReverseFirst) {
d->pathShape->reverseSubpath(d->endPoint.first);
}
if (d->reverse & Private::ReverseSecond) {
d->pathShape->reverseSubpath(d->startPoint.first);
}
// move the subpaths so the second is directly after the first
d->pathShape->moveSubpath(d->startPoint.first, d->endPoint.first + 1);
d->splitIndex = d->pathShape->pathPointIndex(endPoint);
// join both subpaths
d->pathShape->join(d->endPoint.first);
// change the first point of the points to merge
d->removedPoint = d->mergePoints(endPoint, startPoint);
+
+ d->mergedPointIndex = d->pathShape->pathPointIndex(endPoint);
}
d->pathShape->normalize();
d->pathShape->update();
}
void KoPathPointMergeCommand::undo()
{
KUndo2Command::undo();
if (!d->removedPoint)
return;
d->pathShape->update();
// check if we just have closed a single subpath
if (d->endPoint.first == d->startPoint.first) {
// open the subpath at the old/new first point
d->pathShape->openSubpath(d->startPoint);
// reinsert the old first point
d->pathShape->insertPoint(d->removedPoint, d->startPoint);
// reposition the points
d->resetPoints(d->endPoint, d->startPoint);
} else {
// break merged subpaths apart
d->pathShape->breakAfter(d->splitIndex);
// reinsert the old second point
d->pathShape->insertPoint(d->removedPoint, KoPathPointIndex(d->splitIndex.first+1,0));
// reposition the first point
d->resetPoints(d->splitIndex, KoPathPointIndex(d->splitIndex.first+1,0));
// move second subpath to its old position
d->pathShape->moveSubpath(d->splitIndex.first+1, d->startPoint.first);
// undo the reversion of the subpaths
if (d->reverse & Private::ReverseFirst) {
d->pathShape->reverseSubpath(d->endPoint.first);
}
if (d->reverse & Private::ReverseSecond) {
d->pathShape->reverseSubpath(d->startPoint.first);
}
}
d->pathShape->normalize();
d->pathShape->update();
// reset the removed point
d->removedPoint = 0;
+ d->mergedPointIndex = KoPathPointIndex(-1,-1);
+}
+
+KoPathPointData KoPathPointMergeCommand::mergedPointData() const
+{
+ return KoPathPointData(d->pathShape, d->mergedPointIndex);
}
diff --git a/libs/flake/commands/KoPathPointMergeCommand.h b/libs/flake/commands/KoPathPointMergeCommand.h
index 740fe8611e..a33d634c45 100644
--- a/libs/flake/commands/KoPathPointMergeCommand.h
+++ b/libs/flake/commands/KoPathPointMergeCommand.h
@@ -1,56 +1,58 @@
/* 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.
*/
#ifndef KOPATHPOINTMERGECOMMAND_H
#define KOPATHPOINTMERGECOMMAND_H
#include <kundo2command.h>
#include "kritaflake_export.h"
class KoPathPointData;
/// The undo / redo command for merging two subpath end points
class KRITAFLAKE_EXPORT KoPathPointMergeCommand : public KUndo2Command
{
public:
/**
* Command to merge two subpath end points.
*
* The points have to be from the same path shape.
*
* @param pointData1 the data of the first point to merge
* @param pointData2 the data of the second point to merge
* @param parent the parent command used for macro commands
*/
KoPathPointMergeCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent = 0);
~KoPathPointMergeCommand();
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
+ KoPathPointData mergedPointData() const;
+
private:
class Private;
Private * const d;
};
#endif // KOPATHPOINTMERGECOMMAND_H
diff --git a/libs/flake/commands/KoPathPointMoveCommand.cpp b/libs/flake/commands/KoPathPointMoveCommand.cpp
index 892272e362..e5ca17d40b 100644
--- a/libs/flake/commands/KoPathPointMoveCommand.cpp
+++ b/libs/flake/commands/KoPathPointMoveCommand.cpp
@@ -1,120 +1,138 @@
/* This file is part of the KDE project
* Copyright (C) 2006,2008-2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoPathPointMoveCommand.h"
#include "KoPathPoint.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
+#include "krita_container_utils.h"
class KoPathPointMoveCommandPrivate
{
public:
- KoPathPointMoveCommandPrivate() : undoCalled(true) { }
+ KoPathPointMoveCommandPrivate() { }
void applyOffset(qreal factor);
- bool undoCalled; // this command stores diffs; so calling undo twice will give wrong results. Guard against that.
QMap<KoPathPointData, QPointF > points;
QSet<KoPathShape*> paths;
};
KoPathPointMoveCommand::KoPathPointMoveCommand(const QList<KoPathPointData> &pointData, const QPointF &offset, KUndo2Command *parent)
: KUndo2Command(parent),
d(new KoPathPointMoveCommandPrivate())
{
setText(kundo2_i18n("Move points"));
foreach (const KoPathPointData &data, pointData) {
if (!d->points.contains(data)) {
d->points[data] = offset;
d->paths.insert(data.pathShape);
}
}
}
KoPathPointMoveCommand::KoPathPointMoveCommand(const QList<KoPathPointData> &pointData, const QList<QPointF> &offsets, KUndo2Command *parent)
: KUndo2Command(parent),
d(new KoPathPointMoveCommandPrivate())
{
Q_ASSERT(pointData.count() == offsets.count());
setText(kundo2_i18n("Move points"));
uint dataCount = pointData.count();
for (uint i = 0; i < dataCount; ++i) {
const KoPathPointData & data = pointData[i];
if (!d->points.contains(data)) {
d->points[data] = offsets[i];
d->paths.insert(data.pathShape);
}
}
}
KoPathPointMoveCommand::~KoPathPointMoveCommand()
{
delete d;
}
void KoPathPointMoveCommand::redo()
{
KUndo2Command::redo();
- if (! d->undoCalled)
- return;
-
d->applyOffset(1.0);
- d->undoCalled = false;
}
void KoPathPointMoveCommand::undo()
{
KUndo2Command::undo();
- if (d->undoCalled)
- return;
-
d->applyOffset(-1.0);
- d->undoCalled = true;
+}
+
+int KoPathPointMoveCommand::id() const
+{
+ return KisCommandUtils::ChangePathShapePointId;
+}
+
+bool KoPathPointMoveCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoPathPointMoveCommand *other = dynamic_cast<const KoPathPointMoveCommand*>(command);
+
+ if (!other ||
+ other->d->paths != d->paths ||
+ !KritaUtils::compareListsUnordered(other->d->points.keys(), d->points.keys())) {
+
+ return false;
+ }
+
+ auto it = d->points.begin();
+ while (it != d->points.end()) {
+ it.value() += other->d->points[it.key()];
+ ++it;
+ }
+
+ return true;
}
void KoPathPointMoveCommandPrivate::applyOffset(qreal factor)
{
foreach (KoPathShape *path, paths) {
// repaint old bounding rect
path->update();
}
QMap<KoPathPointData, QPointF>::iterator it(points.begin());
for (; it != points.end(); ++it) {
KoPathShape *path = it.key().pathShape;
// transform offset from document to shape coordinate system
QPointF shapeOffset = path->documentToShape(factor*it.value()) - path->documentToShape(QPointF());
QTransform matrix;
matrix.translate(shapeOffset.x(), shapeOffset.y());
KoPathPoint *p = path->pointByIndex(it.key().pointIndex);
if (p)
p->map(matrix);
}
foreach (KoPathShape *path, paths) {
path->normalize();
// repaint new bounding rect
path->update();
}
}
diff --git a/libs/flake/commands/KoPathPointMoveCommand.h b/libs/flake/commands/KoPathPointMoveCommand.h
index c0ed4f7dee..93a2e9ceb5 100644
--- a/libs/flake/commands/KoPathPointMoveCommand.h
+++ b/libs/flake/commands/KoPathPointMoveCommand.h
@@ -1,65 +1,68 @@
/* This file is part of the KDE project
* Copyright (C) 2006,2009 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2006,2007 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOPATHPOINTMOVECOMMAND_H
#define KOPATHPOINTMOVECOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
#include "KoPathPointData.h"
class KoPathPointMoveCommandPrivate;
class QPointF;
/// The undo / redo command for path point moving.
class KRITAFLAKE_EXPORT KoPathPointMoveCommand : public KUndo2Command
{
public:
/**
* Command to move path points.
* @param pointData the path points to move
* @param offset the offset by which the point is moved in document coordinates
* @param parent the parent command used for macro commands
*/
KoPathPointMoveCommand(const QList<KoPathPointData> &pointData, const QPointF &offset, KUndo2Command *parent = 0);
/**
* Command to move path points.
* @param pointData the path points to move
* @param offsets the offsets by which the points are moved in document coordinates
* @param parent the parent command used for macro commands
*/
KoPathPointMoveCommand(const QList<KoPathPointData> &pointData, const QList<QPointF> &offsets, KUndo2Command *parent = 0);
~KoPathPointMoveCommand();
/// redo the command
- void redo();
+ void redo() override;
/// revert the actions done in redo
- void undo();
+ void undo() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
private:
KoPathPointMoveCommandPrivate * const d;
};
#endif // KOPATHPOINTMOVECOMMAND_H
diff --git a/libs/flake/commands/KoPathShapeMarkerCommand.cpp b/libs/flake/commands/KoPathShapeMarkerCommand.cpp
index 78d33704df..770ce629e2 100644
--- a/libs/flake/commands/KoPathShapeMarkerCommand.cpp
+++ b/libs/flake/commands/KoPathShapeMarkerCommand.cpp
@@ -1,63 +1,105 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Jeremy Lugagne <lugagne.jeremy@gmail.com>
* 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 "KoPathShapeMarkerCommand.h"
#include "KoMarker.h"
#include "KoPathShape.h"
+#include <QExplicitlySharedDataPointer>
+
+#include "kis_command_ids.h"
#include <klocalizedstring.h>
-KoPathShapeMarkerCommand::KoPathShapeMarkerCommand(const QList<KoPathShape*> &shapes, KoMarker *marker, KoMarkerData::MarkerPosition position, KUndo2Command *parent)
-: KUndo2Command(parent)
-, m_shapes(shapes)
-, m_marker(marker)
-, m_position(position)
+class Q_DECL_HIDDEN KoPathShapeMarkerCommand::Private
+{
+public:
+ QList<KoPathShape*> shapes; ///< the shapes to set marker for
+ QList<QExplicitlySharedDataPointer<KoMarker>> oldMarkers; ///< the old markers, one for each shape
+ QExplicitlySharedDataPointer<KoMarker> marker; ///< the new marker to set
+ KoFlake::MarkerPosition position;
+ QList<bool> oldAutoFillMarkers;
+};
+
+KoPathShapeMarkerCommand::KoPathShapeMarkerCommand(const QList<KoPathShape*> &shapes, KoMarker *marker, KoFlake::MarkerPosition position, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18n("Set marker"), parent),
+ m_d(new Private)
{
- setText(kundo2_i18n("Set marker"));
+ m_d->shapes = shapes;
+ m_d->marker = marker;
+ m_d->position = position;
// save old markers
- Q_FOREACH (KoPathShape *shape, m_shapes) {
- m_oldMarkers.append(shape->marker(position));
+ Q_FOREACH (KoPathShape *shape, m_d->shapes) {
+ m_d->oldMarkers.append(QExplicitlySharedDataPointer<KoMarker>(shape->marker(position)));
+ m_d->oldAutoFillMarkers.append(shape->autoFillMarkers());
}
}
KoPathShapeMarkerCommand::~KoPathShapeMarkerCommand()
{
}
void KoPathShapeMarkerCommand::redo()
{
KUndo2Command::redo();
- Q_FOREACH (KoPathShape *shape, m_shapes) {
- shape->setMarker(m_marker, m_position);
+ Q_FOREACH (KoPathShape *shape, m_d->shapes) {
+ shape->update();
+ shape->setMarker(m_d->marker.data(), m_d->position);
+
+ // we have no GUI for selection auto-filling yet! So just enable it!
+ shape->setAutoFillMarkers(true);
+
shape->update();
}
}
void KoPathShapeMarkerCommand::undo()
{
KUndo2Command::undo();
- QList<KoMarker*>::iterator markerIt = m_oldMarkers.begin();
- Q_FOREACH (KoPathShape *shape, m_shapes) {
- shape->setMarker(*markerIt, m_position);
+ auto markerIt = m_d->oldMarkers.begin();
+ auto autoFillIt = m_d->oldAutoFillMarkers.begin();
+ Q_FOREACH (KoPathShape *shape, m_d->shapes) {
+ shape->update();
+ shape->setMarker((*markerIt).data(), m_d->position);
+ shape->setAutoFillMarkers(*autoFillIt);
shape->update();
++markerIt;
}
}
+
+int KoPathShapeMarkerCommand::id() const
+{
+ return KisCommandUtils::ChangeShapeMarkersId;
+}
+
+bool KoPathShapeMarkerCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoPathShapeMarkerCommand *other = dynamic_cast<const KoPathShapeMarkerCommand*>(command);
+
+ if (!other ||
+ other->m_d->shapes != m_d->shapes ||
+ other->m_d->position != m_d->position) {
+
+ return false;
+ }
+
+ m_d->marker = other->m_d->marker;
+ return true;
+}
diff --git a/libs/flake/commands/KoPathShapeMarkerCommand.h b/libs/flake/commands/KoPathShapeMarkerCommand.h
index ea672da59d..83d7ef7844 100644
--- a/libs/flake/commands/KoPathShapeMarkerCommand.h
+++ b/libs/flake/commands/KoPathShapeMarkerCommand.h
@@ -1,59 +1,61 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Jeremy Lugagne <lugagne.jeremy@gmail.com>
* 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 KoPathShapeMarkerCommand_H
#define KoPathShapeMarkerCommand_H
#include "kritaflake_export.h"
-#include "KoMarkerData.h"
+#include <QScopedPointer>
+
+#include "KoFlake.h"
#include <kundo2command.h>
#include <QList>
class KoPathShape;
class KoMarker;
/// The undo / redo command for setting the shape marker
class KRITAFLAKE_EXPORT KoPathShapeMarkerCommand : public KUndo2Command
{
public:
/**
* Command to set a new shape marker.
* @param shapes a set of all the shapes that should get the new marker.
* @param marker the new marker, the same for all given shapes
* @param position the position - start or end - of the marker on the shape
* @param parent the parent command used for macro commands
*/
- KoPathShapeMarkerCommand(const QList<KoPathShape*> &shapes, KoMarker *marker, KoMarkerData::MarkerPosition position, KUndo2Command *parent = 0);
+ KoPathShapeMarkerCommand(const QList<KoPathShape*> &shapes, KoMarker *marker, KoFlake::MarkerPosition position, KUndo2Command *parent = 0);
+
+ ~KoPathShapeMarkerCommand();
+
+ void redo() override;
+ void undo() override;
- virtual ~KoPathShapeMarkerCommand();
- /// redo the command
- void redo();
- /// revert the actions done in redo
- void undo();
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
private:
- QList<KoPathShape*> m_shapes; ///< the shapes to set marker for
- QList<KoMarker*> m_oldMarkers; ///< the old markers, one for each shape
- KoMarker* m_marker; ///< the new marker to set
- KoMarkerData::MarkerPosition m_position;
+ struct Private;
+ const QScopedPointer<Private> m_d;
};
#endif // KoPathShapeMarkerCommand_H
diff --git a/libs/flake/commands/KoShapeAlignCommand.cpp b/libs/flake/commands/KoShapeAlignCommand.cpp
index 60ad00c35e..7cc8ccd863 100644
--- a/libs/flake/commands/KoShapeAlignCommand.cpp
+++ b/libs/flake/commands/KoShapeAlignCommand.cpp
@@ -1,102 +1,102 @@
/* 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 "KoShapeAlignCommand.h"
#include "KoShape.h"
#include "KoShapeGroup.h"
#include "commands/KoShapeMoveCommand.h"
#include <klocalizedstring.h>
// #include <FlakeDebug.h>
class Q_DECL_HIDDEN KoShapeAlignCommand::Private
{
public:
Private() : command(0) {}
~Private() {
delete command;
}
KoShapeMoveCommand *command;
};
KoShapeAlignCommand::KoShapeAlignCommand(const QList<KoShape*> &shapes, Align align, const QRectF &boundingRect, KUndo2Command *parent)
: KUndo2Command(parent),
d(new Private())
{
QList<QPointF> previousPositions;
QList<QPointF> newPositions;
QPointF position;
QPointF delta;
QRectF bRect;
Q_FOREACH (KoShape *shape, shapes) {
// if (dynamic_cast<KoShapeGroup*> (shape))
// debugFlake <<"Found Group";
// else if (dynamic_cast<KoShapeContainer*> (shape))
// debugFlake <<"Found Container";
// else
// debugFlake <<"Found shape";
- position = shape->position();
+ position = shape->absolutePosition();
previousPositions << position;
- bRect = shape->boundingRect();
+ bRect = shape->absoluteOutlineRect();
switch (align) {
case HorizontalLeftAlignment:
delta = QPointF(boundingRect.left(), bRect.y()) - bRect.topLeft();
break;
case HorizontalCenterAlignment:
delta = QPointF(boundingRect.center().x() - bRect.width() / 2, bRect.y()) - bRect.topLeft();
break;
case HorizontalRightAlignment:
delta = QPointF(boundingRect.right() - bRect.width(), bRect.y()) - bRect.topLeft();
break;
case VerticalTopAlignment:
delta = QPointF(bRect.x(), boundingRect.top()) - bRect.topLeft();
break;
case VerticalCenterAlignment:
delta = QPointF(bRect.x(), boundingRect.center().y() - bRect.height() / 2) - bRect.topLeft();
break;
case VerticalBottomAlignment:
delta = QPointF(bRect.x(), boundingRect.bottom() - bRect.height()) - bRect.topLeft();
break;
};
newPositions << position + delta;
//debugFlake <<"-> moving" << position.x() <<"," << position.y() <<" to" <<
// (position + delta).x() << ", " << (position+delta).y() << endl;
}
d->command = new KoShapeMoveCommand(shapes, previousPositions, newPositions);
setText(kundo2_i18n("Align shapes"));
}
KoShapeAlignCommand::~KoShapeAlignCommand()
{
delete d;
}
void KoShapeAlignCommand::redo()
{
KUndo2Command::redo();
d->command->redo();
}
void KoShapeAlignCommand::undo()
{
KUndo2Command::undo();
d->command->undo();
}
diff --git a/libs/flake/commands/KoShapeBackgroundCommand.cpp b/libs/flake/commands/KoShapeBackgroundCommand.cpp
index 48ac5de231..d316dcb820 100644
--- a/libs/flake/commands/KoShapeBackgroundCommand.cpp
+++ b/libs/flake/commands/KoShapeBackgroundCommand.cpp
@@ -1,117 +1,139 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* 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 "KoShapeBackgroundCommand.h"
#include "KoShape.h"
#include "KoShapeBackground.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
+
+
class Q_DECL_HIDDEN KoShapeBackgroundCommand::Private
{
public:
Private() {
}
~Private() {
oldFills.clear();
newFills.clear();
}
void addOldFill(QSharedPointer<KoShapeBackground> oldFill)
{
oldFills.append(oldFill);
}
void addNewFill(QSharedPointer<KoShapeBackground> newFill)
{
newFills.append(newFill);
}
QList<KoShape*> shapes; ///< the shapes to set background for
QList<QSharedPointer<KoShapeBackground> > oldFills;
QList<QSharedPointer<KoShapeBackground> > newFills;
};
KoShapeBackgroundCommand::KoShapeBackgroundCommand(const QList<KoShape*> &shapes, QSharedPointer<KoShapeBackground> fill,
KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes = shapes;
Q_FOREACH (KoShape *shape, d->shapes) {
d->addOldFill(shape->background());
d->addNewFill(fill);
}
setText(kundo2_i18n("Set background"));
}
KoShapeBackgroundCommand::KoShapeBackgroundCommand(KoShape * shape, QSharedPointer<KoShapeBackground> fill, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes.append(shape);
d->addOldFill(shape->background());
d->addNewFill(fill);
setText(kundo2_i18n("Set background"));
}
KoShapeBackgroundCommand::KoShapeBackgroundCommand(const QList<KoShape*> &shapes, const QList<QSharedPointer<KoShapeBackground> > &fills, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes = shapes;
Q_FOREACH (KoShape *shape, d->shapes) {
d->addOldFill(shape->background());
}
foreach (QSharedPointer<KoShapeBackground> fill, fills) {
d->addNewFill(fill);
}
setText(kundo2_i18n("Set background"));
}
void KoShapeBackgroundCommand::redo()
{
KUndo2Command::redo();
QList<QSharedPointer<KoShapeBackground> >::iterator brushIt = d->newFills.begin();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->setBackground(*brushIt);
shape->update();
++brushIt;
}
}
void KoShapeBackgroundCommand::undo()
{
KUndo2Command::undo();
QList<QSharedPointer<KoShapeBackground> >::iterator brushIt = d->oldFills.begin();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->setBackground(*brushIt);
shape->update();
++brushIt;
}
}
+int KoShapeBackgroundCommand::id() const
+{
+ return KisCommandUtils::ChangeShapeBackgroundId;
+}
+
+bool KoShapeBackgroundCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoShapeBackgroundCommand *other = dynamic_cast<const KoShapeBackgroundCommand*>(command);
+
+ if (!other ||
+ other->d->shapes != d->shapes) {
+
+ return false;
+ }
+
+ d->newFills= other->d->newFills;
+ return true;
+}
+
KoShapeBackgroundCommand::~KoShapeBackgroundCommand()
{
delete d;
}
diff --git a/libs/flake/commands/KoShapeBackgroundCommand.h b/libs/flake/commands/KoShapeBackgroundCommand.h
index 1bceb6de66..01d82683a7 100644
--- a/libs/flake/commands/KoShapeBackgroundCommand.h
+++ b/libs/flake/commands/KoShapeBackgroundCommand.h
@@ -1,71 +1,75 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* Copyright (C) 2006,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 KOSHAPEBACKGROUNDCOMMAND_H
#define KOSHAPEBACKGROUNDCOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
#include <QList>
#include <QSharedPointer>
class KoShape;
class KoShapeBackground;
/// The undo / redo command for setting the shape background
class KRITAFLAKE_EXPORT KoShapeBackgroundCommand : public KUndo2Command
{
public:
/**
* Command to set a new shape background.
* @param shapes a set of all the shapes that should get the new background.
* @param fill the new shape background
* @param parent the parent command used for macro commands
*/
KoShapeBackgroundCommand(const QList<KoShape*> &shapes, QSharedPointer<KoShapeBackground> fill, KUndo2Command *parent = 0);
/**
* Command to set a new shape background.
* @param shape a single shape that should get the new background.
* @param fill the new shape background
* @param parent the parent command used for macro commands
*/
KoShapeBackgroundCommand(KoShape *shape, QSharedPointer<KoShapeBackground> fill, KUndo2Command *parent = 0);
/**
* Command to set new shape backgrounds.
* @param shapes a set of all the shapes that should get a new background.
* @param fills the new backgrounds, one for each shape
* @param parent the parent command used for macro commands
*/
KoShapeBackgroundCommand(const QList<KoShape*> &shapes, const QList<QSharedPointer<KoShapeBackground> > &fills, KUndo2Command *parent = 0);
virtual ~KoShapeBackgroundCommand();
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
class Private;
Private * const d;
};
#endif
diff --git a/libs/flake/commands/KoShapeClipCommand.cpp b/libs/flake/commands/KoShapeClipCommand.cpp
index f413dac8e3..a36df37bae 100644
--- a/libs/flake/commands/KoShapeClipCommand.cpp
+++ b/libs/flake/commands/KoShapeClipCommand.cpp
@@ -1,133 +1,132 @@
/* 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 "KoShapeClipCommand.h"
#include "KoClipPath.h"
#include "KoShape.h"
#include "KoShapeContainer.h"
#include "KoPathShape.h"
#include "KoShapeBasedDocumentBase.h"
#include <klocalizedstring.h>
+#include "kis_pointer_utils.h"
+
class Q_DECL_HIDDEN KoShapeClipCommand::Private
{
public:
Private(KoShapeBasedDocumentBase *c)
: controller(c), executed(false) {
}
~Private() {
if (executed) {
qDeleteAll(oldClipPaths);
} else {
- clipData->removeClipShapesOwnership();
qDeleteAll(newClipPaths);
}
}
QList<KoShape*> shapesToClip;
QList<KoClipPath*> oldClipPaths;
QList<KoPathShape*> clipPathShapes;
QList<KoClipPath*> newClipPaths;
QList<KoShapeContainer*> oldParents;
- QExplicitlySharedDataPointer<KoClipData> clipData;
KoShapeBasedDocumentBase *controller;
bool executed;
};
KoShapeClipCommand::KoShapeClipCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape*> &shapes, const QList<KoPathShape*> &clipPathShapes, KUndo2Command *parent)
: KUndo2Command(parent), d(new Private(controller))
{
d->shapesToClip = shapes;
d->clipPathShapes = clipPathShapes;
- d->clipData = new KoClipData(clipPathShapes);
+
Q_FOREACH (KoShape *shape, d->shapesToClip) {
d->oldClipPaths.append(shape->clipPath());
- d->newClipPaths.append(new KoClipPath(shape, d->clipData.data()));
+ d->newClipPaths.append(new KoClipPath(implicitCastList<KoShape*>(clipPathShapes), KoFlake::UserSpaceOnUse));
}
Q_FOREACH (KoPathShape *path, clipPathShapes) {
d->oldParents.append(path->parent());
}
setText(kundo2_i18n("Clip Shape"));
}
KoShapeClipCommand::KoShapeClipCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, const QList<KoPathShape*> &clipPathShapes, KUndo2Command *parent)
: KUndo2Command(parent), d(new Private(controller))
{
d->shapesToClip.append(shape);
d->clipPathShapes = clipPathShapes;
- d->clipData = new KoClipData(clipPathShapes);
d->oldClipPaths.append(shape->clipPath());
- d->newClipPaths.append(new KoClipPath(shape, d->clipData.data()));
+ d->newClipPaths.append(new KoClipPath(implicitCastList<KoShape*>(clipPathShapes), KoFlake::UserSpaceOnUse));
Q_FOREACH (KoPathShape *path, clipPathShapes) {
d->oldParents.append(path->parent());
}
setText(kundo2_i18n("Clip Shape"));
}
KoShapeClipCommand::~KoShapeClipCommand()
{
delete d;
}
void KoShapeClipCommand::redo()
{
const uint shapeCount = d->shapesToClip.count();
for (uint i = 0; i < shapeCount; ++i) {
d->shapesToClip[i]->setClipPath(d->newClipPaths[i]);
d->shapesToClip[i]->update();
}
const uint clipPathCount = d->clipPathShapes.count();
for (uint i = 0; i < clipPathCount; ++i) {
d->controller->removeShape(d->clipPathShapes[i]);
if (d->oldParents.at(i))
d->oldParents.at(i)->removeShape(d->clipPathShapes[i]);
}
d->executed = true;
KUndo2Command::redo();
}
void KoShapeClipCommand::undo()
{
KUndo2Command::undo();
const uint shapeCount = d->shapesToClip.count();
for (uint i = 0; i < shapeCount; ++i) {
d->shapesToClip[i]->setClipPath(d->oldClipPaths[i]);
d->shapesToClip[i]->update();
}
const uint clipPathCount = d->clipPathShapes.count();
for (uint i = 0; i < clipPathCount; ++i) {
if (d->oldParents.at(i))
d->oldParents.at(i)->addShape(d->clipPathShapes[i]);
// the parent has to be there when it is added to the KoShapeBasedDocumentBase
d->controller->addShape(d->clipPathShapes[i]);
}
d->executed = false;
}
diff --git a/libs/flake/commands/KoShapeCreateCommand.cpp b/libs/flake/commands/KoShapeCreateCommand.cpp
index dd16c3c648..a2c443c217 100644
--- a/libs/flake/commands/KoShapeCreateCommand.cpp
+++ b/libs/flake/commands/KoShapeCreateCommand.cpp
@@ -1,83 +1,127 @@
/* 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 "KoShapeCreateCommand.h"
#include "KoShape.h"
#include "KoShapeContainer.h"
#include "KoShapeBasedDocumentBase.h"
#include <klocalizedstring.h>
+#include "kis_assert.h"
+#include <KoShapeLayer.h>
+#include <KoShapeReorderCommand.h>
+
+#include <vector>
+#include <memory>
+
class Q_DECL_HIDDEN KoShapeCreateCommand::Private
{
public:
- Private(KoShapeBasedDocumentBase *c, KoShape *s)
- : controller(c),
- shape(s),
- shapeParent(shape->parent()),
- deleteShape(true) {
+ Private(KoShapeBasedDocumentBase *_document, const QList<KoShape*> &_shapes)
+ : shapesDocument(_document),
+ shapes(_shapes),
+ deleteShapes(true)
+ {
+ Q_FOREACH(KoShape *shape, shapes) {
+ originalShapeParents << shape->parent();
+ }
}
+
~Private() {
- if (shape && deleteShape)
- delete shape;
+ if (deleteShapes) {
+ qDeleteAll(shapes);
+ }
}
- KoShapeBasedDocumentBase *controller;
- KoShape *shape;
- KoShapeContainer *shapeParent;
- bool deleteShape;
+ KoShapeBasedDocumentBase *shapesDocument;
+ QList<KoShape*> shapes;
+ QList<KoShapeContainer*> originalShapeParents;
+ bool deleteShapes;
+
+ std::vector<std::unique_ptr<KUndo2Command>> reorderingCommands;
+
+ QScopedPointer<KUndo2Command> reorderingCommand;
};
KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent)
- : KUndo2Command(parent),
- d(new Private(controller, shape))
+ : KoShapeCreateCommand(controller, QList<KoShape *>() << shape, parent)
+{
+}
+
+KoShapeCreateCommand::KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape *> shapes, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18np("Create shape", "Create shapes", shapes.size()), parent),
+ d(new Private(controller, shapes))
{
- setText(kundo2_i18n("Create shape"));
}
KoShapeCreateCommand::~KoShapeCreateCommand()
{
delete d;
}
void KoShapeCreateCommand::redo()
{
KUndo2Command::redo();
- Q_ASSERT(d->shape);
- Q_ASSERT(d->controller);
- if (d->shapeParent)
- d->shapeParent->addShape(d->shape);
- // the parent has to be there when it is added to the KoShapeBasedDocumentBase
- d->controller->addShape(d->shape);
- d->shapeParent = d->shape->parent(); // update parent if the 'addShape' changed it
- d->deleteShape = false;
+ KIS_ASSERT(d->shapesDocument);
+
+ d->deleteShapes = false;
+ d->reorderingCommands.clear();
+
+ Q_FOREACH(KoShape *shape, d->shapes) {
+ d->shapesDocument->addShape(shape);
+
+ KoShapeContainer *shapeParent = shape->parent();
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(shape->parent() ||
+ dynamic_cast<KoShapeLayer*>(shape));
+
+ if (shapeParent) {
+ KUndo2Command *cmd = KoShapeReorderCommand::mergeInShape(shapeParent->shapes(), shape);
+
+ if (d->reorderingCommand) {
+ cmd->redo();
+ d->reorderingCommands.push_back(
+ std::unique_ptr<KUndo2Command>(cmd));
+ }
+ }
+ }
}
void KoShapeCreateCommand::undo()
{
KUndo2Command::undo();
- Q_ASSERT(d->shape);
- Q_ASSERT(d->controller);
- // the parent has to be there when it is removed from the KoShapeBasedDocumentBase
- d->controller->removeShape(d->shape);
- if (d->shapeParent)
- d->shapeParent->removeShape(d->shape);
- d->deleteShape = true;
+ KIS_ASSERT(d->shapesDocument);
+
+ while (!d->reorderingCommands.empty()) {
+ std::unique_ptr<KUndo2Command> cmd = std::move(d->reorderingCommands.back());
+ cmd->undo();
+ d->reorderingCommands.pop_back();
+ }
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN(d->shapes.size() == d->originalShapeParents.size());
+
+ for (int i = 0; i < d->shapes.size(); i++) {
+ d->shapesDocument->removeShape(d->shapes[i]);
+ d->shapes[i]->setParent(d->originalShapeParents[i]);
+ }
+
+ d->deleteShapes = true;
}
diff --git a/libs/flake/commands/KoShapeCreateCommand.h b/libs/flake/commands/KoShapeCreateCommand.h
index eefa2ef25d..9494e17dd6 100644
--- a/libs/flake/commands/KoShapeCreateCommand.h
+++ b/libs/flake/commands/KoShapeCreateCommand.h
@@ -1,52 +1,62 @@
/* 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 KOSHAPECREATECOMMAND_H
#define KOSHAPECREATECOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
class KoShape;
class KoShapeBasedDocumentBase;
/// The undo / redo command for creating shapes
class KRITAFLAKE_EXPORT KoShapeCreateCommand : public KUndo2Command
{
public:
/**
* Command used on creation of new shapes
* @param controller the controller used to add/remove the shape from
* @param shape the shape thats just been created.
* @param parent the parent command used for macro commands
*/
KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, KoShape *shape,
KUndo2Command *parent = 0);
+
+ /**
+ * Command used on creation of new shapes
+ * @param controller the controller used to add/remove the shape from
+ * @param shapes the shapes that have just been created.
+ * @param parent the parent command used for macro commands
+ */
+ KoShapeCreateCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape*> shape,
+ KUndo2Command *parent = 0);
+
virtual ~KoShapeCreateCommand();
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
private:
class Private;
Private * const d;
};
#endif
diff --git a/libs/flake/commands/KoShapeDistributeCommand.cpp b/libs/flake/commands/KoShapeDistributeCommand.cpp
index 789f7ef57a..5d4fe48d6a 100644
--- a/libs/flake/commands/KoShapeDistributeCommand.cpp
+++ b/libs/flake/commands/KoShapeDistributeCommand.cpp
@@ -1,179 +1,179 @@
/* 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 "KoShapeDistributeCommand.h"
#include "commands/KoShapeMoveCommand.h"
#include "KoShape.h"
#include <QMap>
#include <klocalizedstring.h>
class Q_DECL_HIDDEN KoShapeDistributeCommand::Private
{
public:
Private() : command(0) {}
~Private() {
delete command;
}
qreal getAvailableSpace(KoShape *first, KoShape *last, qreal extent, const QRectF &boundingRect);
Distribute distribute;
KoShapeMoveCommand *command;
};
KoShapeDistributeCommand::KoShapeDistributeCommand(const QList<KoShape*> &shapes, Distribute distribute, const QRectF &boundingRect, KUndo2Command *parent)
: KUndo2Command(parent),
d(new Private())
{
d->distribute = distribute;
QMap<qreal, KoShape*> sortedPos;
QRectF bRect;
qreal extent = 0.0;
// sort by position and calculate sum of objects widht/height
Q_FOREACH (KoShape *shape, shapes) {
- bRect = shape->boundingRect();
+ bRect = shape->absoluteOutlineRect();
switch (d->distribute) {
case HorizontalCenterDistribution:
sortedPos[bRect.center().x()] = shape;
break;
case HorizontalGapsDistribution:
case HorizontalLeftDistribution:
sortedPos[bRect.left()] = shape;
extent += bRect.width();
break;
case HorizontalRightDistribution:
sortedPos[bRect.right()] = shape;
break;
case VerticalCenterDistribution:
sortedPos[bRect.center().y()] = shape;
break;
case VerticalGapsDistribution:
case VerticalBottomDistribution:
sortedPos[bRect.bottom()] = shape;
extent += bRect.height();
break;
case VerticalTopDistribution:
sortedPos[bRect.top()] = shape;
break;
}
}
KoShape* first = sortedPos.begin().value();
KoShape* last = (--sortedPos.end()).value();
// determine the available space to distribute
qreal space = d->getAvailableSpace(first, last, extent, boundingRect);
qreal pos = 0.0, step = space / qreal(shapes.count() - 1);
QList<QPointF> previousPositions;
QList<QPointF> newPositions;
QPointF position;
QPointF delta;
QMapIterator<qreal, KoShape*> it(sortedPos);
while (it.hasNext()) {
it.next();
- position = it.value()->position();
+ position = it.value()->absolutePosition();
previousPositions << position;
- bRect = it.value()->boundingRect();
+ bRect = it.value()->absoluteOutlineRect();
switch (d->distribute) {
case HorizontalCenterDistribution:
- delta = QPointF(boundingRect.x() + first->boundingRect().width() / 2 + pos - bRect.width() / 2, bRect.y()) - bRect.topLeft();
+ delta = QPointF(boundingRect.x() + first->absoluteOutlineRect().width() / 2 + pos - bRect.width() / 2, bRect.y()) - bRect.topLeft();
break;
case HorizontalGapsDistribution:
delta = QPointF(boundingRect.left() + pos, bRect.y()) - bRect.topLeft();
pos += bRect.width();
break;
case HorizontalLeftDistribution:
delta = QPointF(boundingRect.left() + pos, bRect.y()) - bRect.topLeft();
break;
case HorizontalRightDistribution:
- delta = QPointF(boundingRect.left() + first->boundingRect().width() + pos - bRect.width(), bRect.y()) - bRect.topLeft();
+ delta = QPointF(boundingRect.left() + first->absoluteOutlineRect().width() + pos - bRect.width(), bRect.y()) - bRect.topLeft();
break;
case VerticalCenterDistribution:
- delta = QPointF(bRect.x(), boundingRect.y() + first->boundingRect().height() / 2 + pos - bRect.height() / 2) - bRect.topLeft();
+ delta = QPointF(bRect.x(), boundingRect.y() + first->absoluteOutlineRect().height() / 2 + pos - bRect.height() / 2) - bRect.topLeft();
break;
case VerticalGapsDistribution:
delta = QPointF(bRect.x(), boundingRect.top() + pos) - bRect.topLeft();
pos += bRect.height();
break;
case VerticalBottomDistribution:
- delta = QPointF(bRect.x(), boundingRect.top() + first->boundingRect().height() + pos - bRect.height()) - bRect.topLeft();
+ delta = QPointF(bRect.x(), boundingRect.top() + first->absoluteOutlineRect().height() + pos - bRect.height()) - bRect.topLeft();
break;
case VerticalTopDistribution:
delta = QPointF(bRect.x(), boundingRect.top() + pos) - bRect.topLeft();
break;
};
newPositions << position + delta;
pos += step;
}
d->command = new KoShapeMoveCommand(sortedPos.values(), previousPositions, newPositions);
setText(kundo2_i18n("Distribute shapes"));
}
KoShapeDistributeCommand::~KoShapeDistributeCommand()
{
delete d;
}
void KoShapeDistributeCommand::redo()
{
KUndo2Command::redo();
d->command->redo();
}
void KoShapeDistributeCommand::undo()
{
KUndo2Command::undo();
d->command->undo();
}
qreal KoShapeDistributeCommand::Private::getAvailableSpace(KoShape *first, KoShape *last, qreal extent, const QRectF &boundingRect)
{
switch (distribute) {
case HorizontalCenterDistribution:
- return boundingRect.width() - last->boundingRect().width() / 2 - first->boundingRect().width() / 2;
+ return boundingRect.width() - last->absoluteOutlineRect().width() / 2 - first->absoluteOutlineRect().width() / 2;
break;
case HorizontalGapsDistribution:
return boundingRect.width() - extent;
break;
case HorizontalLeftDistribution:
- return boundingRect.width() - last->boundingRect().width();
+ return boundingRect.width() - last->absoluteOutlineRect().width();
break;
case HorizontalRightDistribution:
- return boundingRect.width() - first->boundingRect().width();
+ return boundingRect.width() - first->absoluteOutlineRect().width();
break;
case VerticalCenterDistribution:
- return boundingRect.height() - last->boundingRect().height() / 2 - first->boundingRect().height() / 2;
+ return boundingRect.height() - last->absoluteOutlineRect().height() / 2 - first->absoluteOutlineRect().height() / 2;
break;
case VerticalGapsDistribution:
return boundingRect.height() - extent;
break;
case VerticalBottomDistribution:
- return boundingRect.height() - first->boundingRect().height();
+ return boundingRect.height() - first->absoluteOutlineRect().height();
break;
case VerticalTopDistribution:
- return boundingRect.height() - last->boundingRect().height();
+ return boundingRect.height() - last->absoluteOutlineRect().height();
break;
}
return 0.0;
}
diff --git a/libs/flake/commands/KoShapeGroupCommand.cpp b/libs/flake/commands/KoShapeGroupCommand.cpp
index e188d4b905..2fb56c05ca 100644
--- a/libs/flake/commands/KoShapeGroupCommand.cpp
+++ b/libs/flake/commands/KoShapeGroupCommand.cpp
@@ -1,220 +1,228 @@
/* 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 "KoShapeGroupCommand_p.h"
#include "KoShape.h"
#include "KoShapeGroup.h"
#include "KoShapeContainer.h"
#include <klocalizedstring.h>
// static
KoShapeGroupCommand * KoShapeGroupCommand::createCommand(KoShapeGroup *container, const QList<KoShape *> &shapes, KUndo2Command *parent)
{
QList<KoShape*> orderedShapes(shapes);
qSort(orderedShapes.begin(), orderedShapes.end(), KoShape::compareShapeZIndex);
if (!orderedShapes.isEmpty()) {
KoShape * top = orderedShapes.last();
container->setParent(top->parent());
container->setZIndex(top->zIndex());
}
return new KoShapeGroupCommand(container, orderedShapes, parent);
}
-KoShapeGroupCommandPrivate::KoShapeGroupCommandPrivate(KoShapeContainer *c, const QList<KoShape *> &s, const QList<bool> &clip, const QList<bool> &it)
+KoShapeGroupCommandPrivate::KoShapeGroupCommandPrivate(KoShapeContainer *c, const QList<KoShape *> &s, const QList<bool> &clip, const QList<bool> &it, bool _shouldNormalize)
: shapes(s),
clipped(clip),
inheritTransform(it),
+ shouldNormalize(_shouldNormalize),
container(c)
+
{
}
KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes, const QList<bool> &clipped, const QList<bool> &inheritTransform, KUndo2Command *parent)
: KUndo2Command(parent),
- d(new KoShapeGroupCommandPrivate(container,shapes, clipped, inheritTransform))
+ d(new KoShapeGroupCommandPrivate(container,shapes, clipped, inheritTransform, true))
{
Q_ASSERT(d->clipped.count() == d->shapes.count());
Q_ASSERT(d->inheritTransform.count() == d->shapes.count());
d->init(this);
}
KoShapeGroupCommand::KoShapeGroupCommand(KoShapeGroup *container, const QList<KoShape *> &shapes, KUndo2Command *parent)
: KUndo2Command(parent),
- d(new KoShapeGroupCommandPrivate(container,shapes))
+ d(new KoShapeGroupCommandPrivate(container,shapes, QList<bool>(), QList<bool>(), true))
{
for (int i = 0; i < shapes.count(); ++i) {
d->clipped.append(false);
d->inheritTransform.append(false);
}
d->init(this);
}
+KoShapeGroupCommand::KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
+ bool clipped, bool inheritTransform, bool shouldNormalize, KUndo2Command *parent)
+ : KUndo2Command(parent),
+ d(new KoShapeGroupCommandPrivate(container,shapes, QList<bool>(), QList<bool>(), shouldNormalize))
+{
+ for (int i = 0; i < shapes.count(); ++i) {
+ d->clipped.append(clipped);
+ d->inheritTransform.append(inheritTransform);
+ }
+ d->init(this);
+}
+
KoShapeGroupCommand::~KoShapeGroupCommand()
{
delete d;
}
KoShapeGroupCommand::KoShapeGroupCommand(KoShapeGroupCommandPrivate &dd, KUndo2Command *parent)
: KUndo2Command(parent),
d(&dd)
{
}
void KoShapeGroupCommandPrivate::init(KUndo2Command *q)
{
Q_FOREACH (KoShape* shape, shapes) {
oldParents.append(shape->parent());
oldClipped.append(shape->parent() && shape->parent()->isClipped(shape));
oldInheritTransform.append(shape->parent() && shape->parent()->inheritsTransform(shape));
oldZIndex.append(shape->zIndex());
}
if (container->shapes().isEmpty()) {
q->setText(kundo2_i18n("Group shapes"));
} else {
q->setText(kundo2_i18n("Add shapes to group"));
}
}
void KoShapeGroupCommand::redo()
{
KUndo2Command::redo();
- if (dynamic_cast<KoShapeGroup*>(d->container)) {
+ if (d->shouldNormalize && dynamic_cast<KoShapeGroup*>(d->container)) {
QRectF bound = d->containerBoundingRect();
- QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeftCorner);
- d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeftCorner);
+ 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();
int zIndex=0;
QList<KoShape*> shapes(d->container->shapes());
if (!shapes.isEmpty()) {
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
zIndex = shapes.last()->zIndex();
}
uint shapeCount = d->shapes.count();
for (uint i = 0; i < shapeCount; ++i) {
KoShape * shape = d->shapes[i];
shape->setZIndex(zIndex++);
if(d->inheritTransform[i]) {
shape->applyAbsoluteTransformation(groupTransform);
}
else {
QSizeF containerSize = d->container->size();
QPointF containerPos = d->container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height());
QTransform matrix;
matrix.translate(containerPos.x(), containerPos.y());
shape->applyAbsoluteTransformation(matrix.inverted());
}
d->container->addShape(shape);
d->container->setClipped(shape, d->clipped[i]);
d->container->setInheritsTransform(shape, d->inheritTransform[i]);
}
}
void KoShapeGroupCommand::undo()
{
KUndo2Command::undo();
QTransform ungroupTransform = d->container->absoluteTransformation(0);
for (int i = 0; i < d->shapes.count(); i++) {
KoShape * shape = d->shapes[i];
const bool inheritedTransform = d->container->inheritsTransform(shape);
d->container->removeShape(shape);
if (d->oldParents.at(i)) {
d->oldParents.at(i)->addShape(shape);
d->oldParents.at(i)->setClipped(shape, d->oldClipped.at(i));
d->oldParents.at(i)->setInheritsTransform(shape, d->oldInheritTransform.at(i));
}
if(inheritedTransform) {
shape->applyAbsoluteTransformation(ungroupTransform);
}
else {
QSizeF containerSize = d->container->size();
QPointF containerPos = d->container->absolutePosition() - QPointF(0.5 * containerSize.width(), 0.5 * containerSize.height());
QTransform matrix;
matrix.translate(containerPos.x(), containerPos.y());
shape->applyAbsoluteTransformation(matrix);
}
shape->setZIndex(d->oldZIndex[i]);
}
- if (dynamic_cast<KoShapeGroup*>(d->container)) {
- QPointF oldGroupPosition = d->container->absolutePosition(KoFlake::TopLeftCorner);
+ 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::TopLeftCorner);
+ d->container->setAbsolutePosition(bound.topLeft(), KoFlake::TopLeft);
d->container->setSize(bound.size());
}
}
}
QRectF KoShapeGroupCommandPrivate::containerBoundingRect()
{
- bool boundingRectInitialized = true;
QRectF bound;
- if (container->shapeCount() > 0)
- bound = container->boundingRect();
- else
- boundingRectInitialized = false;
+ if (container->shapeCount() > 0) {
+ bound = container->absoluteTransformation(0).mapRect(container->outlineRect());
+ }
Q_FOREACH (KoShape *shape, shapes) {
- if (boundingRectInitialized)
- bound = bound.united(shape->boundingRect());
- else {
- bound = shape->boundingRect();
- boundingRectInitialized = true;
- }
+ bound |= shape->absoluteTransformation(0).mapRect(shape->outlineRect());
}
+
return bound;
}
diff --git a/libs/flake/commands/KoShapeGroupCommand.h b/libs/flake/commands/KoShapeGroupCommand.h
index cb3142fc06..4cacf58807 100644
--- a/libs/flake/commands/KoShapeGroupCommand.h
+++ b/libs/flake/commands/KoShapeGroupCommand.h
@@ -1,81 +1,95 @@
/* 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.
*/
#ifndef KOSHAPEGROUPCOMMAND_H
#define KOSHAPEGROUPCOMMAND_H
#include "kritaflake_export.h"
#include <QList>
#include <kundo2command.h>
class KoShape;
class KoShapeGroup;
class KoShapeContainer;
class KoShapeGroupCommandPrivate;
/// The undo / redo command for grouping shapes
class KRITAFLAKE_EXPORT KoShapeGroupCommand : public KUndo2Command
{
public:
/**
* Create command to group a set of shapes into a predefined container.
* This uses the KoShapeGroupCommand(KoShapeGroup *container, const QList<KoShape *> &shapes, KUndo2Command *parent = 0);
* constructor.
* The createCommand will make sure that the group will have the z-index and the parent of the top most shape in the group.
*
* @param container the group to group the shapes under.
* @param parent the parent command if the resulting command is a compound undo command.
* @param shapes a list of all the shapes that should be grouped.
*/
static KoShapeGroupCommand *createCommand(KoShapeGroup *container, const QList<KoShape *> &shapes, KUndo2Command *parent = 0);
/**
* Command to group a set of shapes into a predefined container.
* @param container the container to group the shapes under.
* @param shapes a list of all the shapes that should be grouped.
* @param clipped a list of the same length as the shapes list with one bool for each shape.
* See KoShapeContainer::isClipped()
* @param inheritTransform a list of the same length as the shapes list with one bool for each shape.
* See KoShapeContainer::inheritsTransform()
* @param parent the parent command used for macro commands
*/
KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
const QList<bool> &clipped, const QList<bool> &inheritTransform, KUndo2Command *parent = 0);
+
+ /**
+ * Command to group a set of shapes into a predefined container.
+ * @param container the container to group the shapes under.
+ * @param shapes a list of all the shapes that should be grouped.
+ * @param clipped shows whether the shapes should be clipped by the container
+ * See KoShapeContainer::isClipped()
+ * @param inheritTransform shows whether the shapes should inherit the parent transformation
+ * See KoShapeContainer::inheritsTransform()
+ * @param parent the parent command used for macro commands
+ */
+ KoShapeGroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
+ bool clipped, bool inheritTransform, bool shouldNormalize, KUndo2Command *parent = 0);
+
/**
* Command to group a set of shapes into a predefined container.
* Convenience constructor since KoShapeGroup does not allow clipping.
* @param container the group to group the shapes under.
* @param parent the parent command if the resulting command is a compound undo command.
* @param shapes a list of all the shapes that should be grouped.
*/
KoShapeGroupCommand(KoShapeGroup *container, const QList<KoShape *> &shapes, KUndo2Command *parent = 0);
virtual ~KoShapeGroupCommand();
/// redo the command
virtual void redo();
/// revert the actions done in redo
virtual void undo();
protected:
KoShapeGroupCommandPrivate *d;
KoShapeGroupCommand(KoShapeGroupCommandPrivate &, KUndo2Command *parent);
};
#endif
diff --git a/libs/flake/commands/KoShapeGroupCommand_p.h b/libs/flake/commands/KoShapeGroupCommand_p.h
index 062eb5a347..a70b8fff6a 100644
--- a/libs/flake/commands/KoShapeGroupCommand_p.h
+++ b/libs/flake/commands/KoShapeGroupCommand_p.h
@@ -1,50 +1,51 @@
/* This file is part of the KDE project
* Copyright (C) 2006,2009,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.
*/
#ifndef KoShapeGroupCommandPrivate_H
#define KoShapeGroupCommandPrivate_H
#include <QPair>
#include <QList>
class KoShape;
class KoShapeContainer;
class KUndo2Command;
class QRectF;
class KoShapeGroupCommandPrivate
{
public:
- KoShapeGroupCommandPrivate(KoShapeContainer *container, const QList<KoShape *> &shapes, const QList<bool> &clipped = QList<bool>(), const QList<bool> &inheritTransform = QList<bool>());
+ KoShapeGroupCommandPrivate(KoShapeContainer *container, const QList<KoShape *> &shapes, const QList<bool> &clipped, const QList<bool> &inheritTransform, bool _shouldNormalize);
void init(KUndo2Command *q);
QRectF containerBoundingRect();
QList<KoShape*> shapes; ///<list of shapes to be grouped
QList<bool> clipped; ///< list of booleans to specify the shape of the same index to be clipped
QList<bool> inheritTransform; ///< list of booleans to specify the shape of the same index to inherit transform
+ 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
QList<bool> oldClipped; ///< if the shape was clipped in the old parent
QList<bool> oldInheritTransform; ///< if the shape was inheriting transform in the old parent
QList<int> oldZIndex; ///< the old z-index of the shapes
QList<QPair<KoShape*, int> > oldAncestorsZIndex; // only used by the ungroup command
};
#endif
diff --git a/libs/flake/commands/KoShapeKeepAspectRatioCommand.h b/libs/flake/commands/KoShapeKeepAspectRatioCommand.h
index 53f77a5974..24f13cc3ed 100644
--- a/libs/flake/commands/KoShapeKeepAspectRatioCommand.h
+++ b/libs/flake/commands/KoShapeKeepAspectRatioCommand.h
@@ -1,55 +1,56 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Peter Simonsson <peter.simonsson@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOSHAPEKEEPASPECTRATIOCOMMAND_H
#define KOSHAPEKEEPASPECTRATIOCOMMAND_H
+#include "kritaflake_export.h"
#include <kundo2command.h>
#include <QList>
class KoShape;
/**
* Command that changes the keepAspectRatio property of KoShape
*/
-class KoShapeKeepAspectRatioCommand : public KUndo2Command
+class KRITAFLAKE_EXPORT KoShapeKeepAspectRatioCommand : public KUndo2Command
{
public:
/**
* Constructor
* @param shapes the shapes affected by the command
* @param oldKeepAspectRatio the old settings
* @param newKeepAspectRatio the new settings
* @param parent the parent command
*/
KoShapeKeepAspectRatioCommand(const QList<KoShape*> &shapes, const QList<bool> &oldKeepAspectRatio, const QList<bool> &newKeepAspectRatio, KUndo2Command* parent = 0);
~KoShapeKeepAspectRatioCommand();
/// Execute the command
virtual void redo();
/// Unexecute the command
virtual void undo();
private:
QList<KoShape*> m_shapes;
QList<bool> m_oldKeepAspectRatio;
QList<bool> m_newKeepAspectRatio;
};
#endif
diff --git a/libs/flake/commands/KoShapeMoveCommand.cpp b/libs/flake/commands/KoShapeMoveCommand.cpp
index 26e37f3931..b9a88e67f7 100644
--- a/libs/flake/commands/KoShapeMoveCommand.cpp
+++ b/libs/flake/commands/KoShapeMoveCommand.cpp
@@ -1,75 +1,104 @@
/* 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 "KoShapeMoveCommand.h"
#include <KoShape.h>
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
class Q_DECL_HIDDEN KoShapeMoveCommand::Private
{
public:
QList<KoShape*> shapes;
QList<QPointF> previousPositions, newPositions;
+ KoFlake::AnchorPosition anchor;
};
-KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions, KUndo2Command *parent)
- : KUndo2Command(parent),
+KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions, KoFlake::AnchorPosition anchor, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18n("Move shapes"), parent),
d(new Private())
{
d->shapes = shapes;
d->previousPositions = previousPositions;
d->newPositions = newPositions;
+ d->anchor = anchor;
Q_ASSERT(d->shapes.count() == d->previousPositions.count());
Q_ASSERT(d->shapes.count() == d->newPositions.count());
+}
+
+KoShapeMoveCommand::KoShapeMoveCommand(const QList<KoShape *> &shapes, const QPointF &offset, KUndo2Command *parent)
+ : KUndo2Command(kundo2_i18n("Move shapes"), parent),
+ d(new Private())
+{
+ d->shapes = shapes;
+ d->anchor = KoFlake::Center;
- setText(kundo2_i18n("Move shapes"));
+ Q_FOREACH (KoShape *shape, d->shapes) {
+ const QPointF pos = shape->absolutePosition();
+
+ d->previousPositions << pos;
+ d->newPositions << pos + offset;
+ }
}
KoShapeMoveCommand::~KoShapeMoveCommand()
{
delete d;
}
void KoShapeMoveCommand::redo()
{
KUndo2Command::redo();
for (int i = 0; i < d->shapes.count(); i++) {
d->shapes.at(i)->update();
- d->shapes.at(i)->setPosition(d->newPositions.at(i));
+ d->shapes.at(i)->setAbsolutePosition(d->newPositions.at(i), d->anchor);
d->shapes.at(i)->update();
}
}
void KoShapeMoveCommand::undo()
{
KUndo2Command::undo();
for (int i = 0; i < d->shapes.count(); i++) {
d->shapes.at(i)->update();
- d->shapes.at(i)->setPosition(d->previousPositions.at(i));
+ d->shapes.at(i)->setAbsolutePosition(d->previousPositions.at(i), d->anchor);
d->shapes.at(i)->update();
}
}
-/// update newPositions list with new postions.
-void KoShapeMoveCommand::setNewPositions(QList<QPointF> newPositions)
+int KoShapeMoveCommand::id() const
{
- d->newPositions = newPositions;
+ return KisCommandUtils::MoveShapeId;
+}
+
+bool KoShapeMoveCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoShapeMoveCommand *other = dynamic_cast<const KoShapeMoveCommand*>(command);
+
+ if (other->d->shapes != d->shapes ||
+ other->d->anchor != d->anchor) {
+
+ return false;
+ }
+
+ d->newPositions = other->d->newPositions;
+ return true;
}
diff --git a/libs/flake/commands/KoShapeMoveCommand.h b/libs/flake/commands/KoShapeMoveCommand.h
index 4be508b7eb..711222440a 100644
--- a/libs/flake/commands/KoShapeMoveCommand.h
+++ b/libs/flake/commands/KoShapeMoveCommand.h
@@ -1,61 +1,65 @@
/* 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.
*/
#ifndef KOSHAPEMOVECOMMAND_H
#define KOSHAPEMOVECOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
#include <QList>
#include <QPointF>
+#include <KoFlake.h>
class KoShape;
/// The undo / redo command for shape moving.
class KRITAFLAKE_EXPORT KoShapeMoveCommand : public KUndo2Command
{
public:
/**
* Constructor.
* @param shapes the set of objects that are moved.
* @param previousPositions the known set of previous positions for each of the objects.
* this list naturally must have the same amount of items as the shapes set.
* @param newPositions the new positions for the shapes.
* this list naturally must have the same amount of items as the shapes set.
* @param parent the parent command used for macro commands
*/
KoShapeMoveCommand(const QList<KoShape*> &shapes, QList<QPointF> &previousPositions, QList<QPointF> &newPositions,
- KUndo2Command *parent = 0);
+ KoFlake::AnchorPosition anchor = KoFlake::Center, KUndo2Command *parent = 0);
+
+ KoShapeMoveCommand(const QList<KoShape*> &shapes, const QPointF &offset, KUndo2Command *parent = 0);
+
~KoShapeMoveCommand();
/// redo the command
- void redo();
+ void redo() override;
/// revert the actions done in redo
- void undo();
+ void undo() override;
- /// update newPositions list with new postions.
- void setNewPositions(QList<QPointF> newPositions);
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
private:
class Private;
Private * const d;
};
#endif
diff --git a/libs/flake/commands/KoShapeReorderCommand.cpp b/libs/flake/commands/KoShapeReorderCommand.cpp
index 489d564dbe..4a1d37471c 100644
--- a/libs/flake/commands/KoShapeReorderCommand.cpp
+++ b/libs/flake/commands/KoShapeReorderCommand.cpp
@@ -1,181 +1,220 @@
/* 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.
*/
#include "KoShapeReorderCommand.h"
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeManager.h"
#include "KoShapeContainer.h"
#include <klocalizedstring.h>
#include <FlakeDebug.h>
#include <limits.h>
class KoShapeReorderCommandPrivate
{
public:
KoShapeReorderCommandPrivate(const QList<KoShape*> &s, QList<int> &ni)
: shapes(s), newIndexes(ni)
{
}
QList<KoShape*> shapes;
QList<int> previousIndexes;
QList<int> newIndexes;
};
KoShapeReorderCommand::KoShapeReorderCommand(const QList<KoShape*> &shapes, QList<int> &newIndexes, KUndo2Command *parent)
: KUndo2Command(parent),
d(new KoShapeReorderCommandPrivate(shapes, newIndexes))
{
Q_ASSERT(shapes.count() == newIndexes.count());
foreach (KoShape *shape, shapes)
d->previousIndexes.append(shape->zIndex());
setText(kundo2_i18n("Reorder shapes"));
}
KoShapeReorderCommand::~KoShapeReorderCommand()
{
delete d;
}
void KoShapeReorderCommand::redo()
{
KUndo2Command::redo();
for (int i = 0; i < d->shapes.count(); i++) {
d->shapes.at(i)->update();
d->shapes.at(i)->setZIndex(d->newIndexes.at(i));
d->shapes.at(i)->update();
}
}
void KoShapeReorderCommand::undo()
{
KUndo2Command::undo();
for (int i = 0; i < d->shapes.count(); i++) {
d->shapes.at(i)->update();
d->shapes.at(i)->setZIndex(d->previousIndexes.at(i));
d->shapes.at(i)->update();
}
}
static void prepare(KoShape *s, QMap<KoShape*, QList<KoShape*> > &newOrder, KoShapeManager *manager, KoShapeReorderCommand::MoveShapeType move)
{
KoShapeContainer *parent = s->parent();
QMap<KoShape*, QList<KoShape*> >::iterator it(newOrder.find(parent));
if (it == newOrder.end()) {
QList<KoShape*> children;
if (parent != 0) {
children = parent->shapes();
}
else {
// get all toplevel shapes
children = manager->topLevelShapes();
}
qSort(children.begin(), children.end(), KoShape::compareShapeZIndex);
// the append and prepend are needed so that the raise/lower of all shapes works as expected.
children.append(0);
children.prepend(0);
it = newOrder.insert(parent, children);
}
QList<KoShape *> & shapes(newOrder[parent]);
int index = shapes.indexOf(s);
if (index != -1) {
shapes.removeAt(index);
switch (move) {
case KoShapeReorderCommand::BringToFront:
index = shapes.size();
break;
case KoShapeReorderCommand::RaiseShape:
if (index < shapes.size()) {
++index;
}
break;
case KoShapeReorderCommand::LowerShape:
if (index > 0) {
--index;
}
break;
case KoShapeReorderCommand::SendToBack:
index = 0;
break;
}
shapes.insert(index,s);
}
}
// static
KoShapeReorderCommand *KoShapeReorderCommand::createCommand(const QList<KoShape*> &shapes, KoShapeManager *manager, MoveShapeType move, KUndo2Command *parent)
{
QList<int> newIndexes;
QList<KoShape*> changedShapes;
QMap<KoShape*, QList<KoShape*> > newOrder;
QList<KoShape*> sortedShapes(shapes);
qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
if (move == BringToFront || move == LowerShape) {
for (int i = 0; i < sortedShapes.size(); ++i) {
prepare(sortedShapes.at(i), newOrder, manager, move);
}
}
else {
for (int i = sortedShapes.size() - 1; i >= 0; --i) {
prepare(sortedShapes.at(i), newOrder, manager, move);
}
}
QMap<KoShape*, QList<KoShape*> >::iterator newIt(newOrder.begin());
for (; newIt!= newOrder.end(); ++newIt) {
QList<KoShape*> order(newIt.value());
order.removeAll(0);
int index = -KoShapePrivate::MaxZIndex - 1; // set minimum zIndex
int pos = 0;
for (; pos < order.size(); ++pos) {
if (order[pos]->zIndex() > index) {
index = order[pos]->zIndex();
}
else {
break;
}
}
if (pos == order.size()) {
//nothing needs to be done
continue;
}
else if (pos <= order.size() / 2) {
// new index for the front
int startIndex = order[pos]->zIndex() - pos;
for (int i = 0; i < pos; ++i) {
changedShapes.append(order[i]);
newIndexes.append(startIndex++);
}
}
else {
//new index for the end
for (int i = pos; i < order.size(); ++i) {
changedShapes.append(order[i]);
newIndexes.append(++index);
}
}
}
Q_ASSERT(changedShapes.count() == newIndexes.count());
return changedShapes.isEmpty() ? 0: new KoShapeReorderCommand(changedShapes, newIndexes, parent);
}
+
+KoShapeReorderCommand *KoShapeReorderCommand::mergeInShape(QList<KoShape *> shapes, KoShape *newShape, KUndo2Command *parent)
+{
+ qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
+
+ QList<KoShape*> reindexedShapes;
+ QList<int> reindexedIndexes;
+
+ const int originalShapeZIndex = newShape->zIndex();
+ int newShapeZIndex = originalShapeZIndex;
+ int lastOccupiedShapeZIndex = originalShapeZIndex + 1;
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ if (shape == newShape) continue;
+
+ const int zIndex = shape->zIndex();
+
+ if (newShapeZIndex == originalShapeZIndex) {
+ if (zIndex == originalShapeZIndex) {
+ newShapeZIndex = originalShapeZIndex + 1;
+ lastOccupiedShapeZIndex = newShapeZIndex;
+
+ reindexedShapes << newShape;
+ reindexedIndexes << newShapeZIndex;
+ }
+ } else {
+ if (newShapeZIndex != originalShapeZIndex &&
+ zIndex >= newShapeZIndex &&
+ zIndex <= lastOccupiedShapeZIndex) {
+
+ lastOccupiedShapeZIndex = zIndex + 1;
+ reindexedShapes << shape;
+ reindexedIndexes << lastOccupiedShapeZIndex;
+ }
+ }
+ }
+
+ return !reindexedShapes.isEmpty() ? new KoShapeReorderCommand(reindexedShapes, reindexedIndexes, parent) : 0;
+}
diff --git a/libs/flake/commands/KoShapeReorderCommand.h b/libs/flake/commands/KoShapeReorderCommand.h
index 9eedb05f45..109f696a5c 100644
--- a/libs/flake/commands/KoShapeReorderCommand.h
+++ b/libs/flake/commands/KoShapeReorderCommand.h
@@ -1,75 +1,89 @@
/* 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 KOSHAPEREORDERCOMMAND_H
#define KOSHAPEREORDERCOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
#include <QList>
class KoShape;
class KoShapeManager;
class KoShapeReorderCommandPrivate;
/// This command allows you to change the zIndex of a number of shapes.
class KRITAFLAKE_EXPORT KoShapeReorderCommand : public KUndo2Command
{
public:
/**
* Constructor.
* @param shapes the set of objects that are moved.
* @param newIndexes the new indexes for the shapes.
* this list naturally must have the same amount of items as the shapes set.
* @param parent the parent command used for macro commands
*/
KoShapeReorderCommand(const QList<KoShape*> &shapes, QList<int> &newIndexes, KUndo2Command *parent = 0);
~KoShapeReorderCommand();
/// An enum for defining what kind of reordering to use.
enum MoveShapeType {
RaiseShape, ///< raise the selected shape to the level that it is above the shape that is on top of it.
LowerShape, ///< Lower the selected shape to the level that it is below the shape that is below it.
BringToFront, ///< Raise the selected shape to be on top of all shapes.
SendToBack ///< Lower the selected shape to be below all other shapes.
};
/**
* Create a new KoShapeReorderCommand by calculating the new indexes required to move the shapes
* according to the move parameter.
* @param shapes all the shapes that should be moved.
* @param manager the shapeManager that contains all the shapes that could have their indexes changed.
* @param move the moving type.
* @param parent the parent command for grouping purposes.
* @return command for reording the shapes or 0 if no reordering happend
*/
static KoShapeReorderCommand *createCommand(const QList<KoShape*> &shapes, KoShapeManager *manager,
MoveShapeType move, KUndo2Command *parent = 0);
+ /**
+ * @brief mergeInShape adjust zIndex of all the \p shapes and \p newShape to
+ * avoid collisions between \p shapes and \p newShape.
+ *
+ * Note1: \p newShape may or may not be contained in \p shapes, there
+ * is no difference.
+ * Note2: the collisions inside \p shapes are ignored. They are just
+ * adjusted to avoid collisions with \p newShape only
+ * @param parent the parent command for grouping purposes.
+ * @return command for reording the shapes or 0 if no reordering happend
+ */
+ static KoShapeReorderCommand *mergeInShape(QList<KoShape*> shapes, KoShape *newShape,
+ KUndo2Command *parent = 0);
+
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
private:
KoShapeReorderCommandPrivate * const d;
};
#endif
diff --git a/libs/flake/commands/KoShapeResizeCommand.cpp b/libs/flake/commands/KoShapeResizeCommand.cpp
new file mode 100644
index 0000000000..693e57628b
--- /dev/null
+++ b/libs/flake/commands/KoShapeResizeCommand.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "KoShapeResizeCommand.h"
+
+#include <KoShape.h>
+#include "kis_command_ids.h"
+
+
+struct Q_DECL_HIDDEN KoShapeResizeCommand::Private
+{
+ QList<KoShape *> shapes;
+ qreal scaleX;
+ qreal scaleY;
+ QPointF absoluteStillPoint;
+ bool useGlobalMode;
+ bool usePostScaling;
+ QTransform postScalingCoveringTransform;
+
+ QList<QSizeF> oldSizes;
+ QList<QTransform> oldTransforms;
+};
+
+
+KoShapeResizeCommand::KoShapeResizeCommand(const QList<KoShape*> &shapes,
+ qreal scaleX, qreal scaleY,
+ const QPointF &absoluteStillPoint,
+ bool useGLobalMode,
+ bool usePostScaling,
+ const QTransform &postScalingCoveringTransform,
+ KUndo2Command *parent)
+ : SkipFirstRedoBase(false, kundo2_i18n("Resize"), parent),
+ m_d(new Private)
+{
+ m_d->shapes = shapes;
+ m_d->scaleX = scaleX;
+ m_d->scaleY = scaleY;
+ m_d->absoluteStillPoint = absoluteStillPoint;
+ m_d->useGlobalMode = useGLobalMode;
+ m_d->usePostScaling = usePostScaling;
+ m_d->postScalingCoveringTransform = postScalingCoveringTransform;
+
+ Q_FOREACH (KoShape *shape, m_d->shapes) {
+ m_d->oldSizes << shape->size();
+ m_d->oldTransforms << shape->transformation();
+ }
+}
+
+KoShapeResizeCommand::~KoShapeResizeCommand()
+{
+}
+
+void KoShapeResizeCommand::redoImpl()
+{
+ Q_FOREACH (KoShape *shape, m_d->shapes) {
+ shape->update();
+
+ KoFlake::resizeShape(shape,
+ m_d->scaleX, m_d->scaleY,
+ m_d->absoluteStillPoint,
+ m_d->useGlobalMode,
+ m_d->usePostScaling,
+ m_d->postScalingCoveringTransform);
+
+ shape->update();
+ }
+}
+
+void KoShapeResizeCommand::undoImpl()
+{
+ for (int i = 0; i < m_d->shapes.size(); i++) {
+ KoShape *shape = m_d->shapes[i];
+
+ shape->update();
+ shape->setSize(m_d->oldSizes[i]);
+ shape->setTransformation(m_d->oldTransforms[i]);
+ shape->update();
+ }
+}
+
+int KoShapeResizeCommand::id() const
+{
+ return KisCommandUtils::ResizeShapeId;
+}
+
+bool KoShapeResizeCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoShapeResizeCommand *other = dynamic_cast<const KoShapeResizeCommand*>(command);
+
+ if (!other ||
+ other->m_d->absoluteStillPoint != m_d->absoluteStillPoint ||
+ other->m_d->shapes != m_d->shapes ||
+ other->m_d->useGlobalMode != m_d->useGlobalMode ||
+ other->m_d->usePostScaling != m_d->usePostScaling) {
+
+ return false;
+ }
+
+ // check if the significant orientations coincide
+ if (m_d->useGlobalMode && !m_d->usePostScaling) {
+ Qt::Orientation our = KoFlake::significantScaleOrientation(m_d->scaleX, m_d->scaleY);
+ Qt::Orientation their = KoFlake::significantScaleOrientation(other->m_d->scaleX, other->m_d->scaleY);
+
+ if (our != their) {
+ return false;
+ }
+ }
+
+ m_d->scaleX *= other->m_d->scaleX;
+ m_d->scaleY *= other->m_d->scaleY;
+ return true;
+}
diff --git a/libs/flake/commands/KoShapeResizeCommand.h b/libs/flake/commands/KoShapeResizeCommand.h
new file mode 100644
index 0000000000..3d9a01b05f
--- /dev/null
+++ b/libs/flake/commands/KoShapeResizeCommand.h
@@ -0,0 +1,57 @@
+/*
+ * 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 KOSHAPERESIZECOMMAND_H
+#define KOSHAPERESIZECOMMAND_H
+
+#include "kritaflake_export.h"
+#include "kundo2command.h"
+#include "kis_command_utils.h"
+
+#include <QList>
+#include <QPointF>
+#include <KoFlake.h>
+
+#include <QScopedPointer>
+
+class KoShape;
+
+
+class KRITAFLAKE_EXPORT KoShapeResizeCommand : public KisCommandUtils::SkipFirstRedoBase
+{
+public:
+ KoShapeResizeCommand(const QList<KoShape*> &shapes,
+ qreal scaleX, qreal scaleY,
+ const QPointF &absoluteStillPoint, bool useGLobalMode,
+ bool usePostScaling, const QTransform &postScalingCoveringTransform,
+ KUndo2Command *parent = 0);
+
+ ~KoShapeResizeCommand();
+ void redoImpl() override;
+ void undoImpl() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
+private:
+ class Private;
+ QScopedPointer<Private> const m_d;
+
+};
+
+#endif // KOSHAPERESIZECOMMAND_H
diff --git a/libs/flake/commands/KoShapeStrokeCommand.cpp b/libs/flake/commands/KoShapeStrokeCommand.cpp
index e5008a64fb..134708c9b7 100644
--- a/libs/flake/commands/KoShapeStrokeCommand.cpp
+++ b/libs/flake/commands/KoShapeStrokeCommand.cpp
@@ -1,132 +1,146 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
* 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.
*/
#include "KoShapeStrokeCommand.h"
#include "KoShape.h"
#include "KoShapeStrokeModel.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
+
+
class Q_DECL_HIDDEN KoShapeStrokeCommand::Private
{
public:
Private() {}
~Private()
{
- Q_FOREACH (KoShapeStrokeModel* stroke, oldStrokes) {
- if (stroke && !stroke->deref())
- delete stroke;
- }
}
- void addOldStroke(KoShapeStrokeModel * oldStroke)
+ void addOldStroke(KoShapeStrokeModelSP oldStroke)
{
- if (oldStroke)
- oldStroke->ref();
oldStrokes.append(oldStroke);
}
- void addNewStroke(KoShapeStrokeModel * newStroke)
+ void addNewStroke(KoShapeStrokeModelSP newStroke)
{
- if (newStroke)
- newStroke->ref();
newStrokes.append(newStroke);
}
QList<KoShape*> shapes; ///< the shapes to set stroke for
- QList<KoShapeStrokeModel*> oldStrokes; ///< the old strokes, one for each shape
- QList<KoShapeStrokeModel*> newStrokes; ///< the new strokes to set
+ QList<KoShapeStrokeModelSP> oldStrokes; ///< the old strokes, one for each shape
+ QList<KoShapeStrokeModelSP> newStrokes; ///< the new strokes to set
};
-KoShapeStrokeCommand::KoShapeStrokeCommand(const QList<KoShape*> &shapes, KoShapeStrokeModel *stroke, KUndo2Command *parent)
+KoShapeStrokeCommand::KoShapeStrokeCommand(const QList<KoShape*> &shapes, KoShapeStrokeModelSP stroke, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes = shapes;
// save old strokes
Q_FOREACH (KoShape *shape, d->shapes) {
d->addOldStroke(shape->stroke());
d->addNewStroke(stroke);
}
setText(kundo2_i18n("Set stroke"));
}
KoShapeStrokeCommand::KoShapeStrokeCommand(const QList<KoShape*> &shapes,
- const QList<KoShapeStrokeModel*> &strokes,
+ const QList<KoShapeStrokeModelSP> &strokes,
KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
Q_ASSERT(shapes.count() == strokes.count());
d->shapes = shapes;
// save old strokes
Q_FOREACH (KoShape *shape, shapes)
d->addOldStroke(shape->stroke());
- foreach (KoShapeStrokeModel * stroke, strokes)
+ foreach (KoShapeStrokeModelSP stroke, strokes)
d->addNewStroke(stroke);
setText(kundo2_i18n("Set stroke"));
}
-KoShapeStrokeCommand::KoShapeStrokeCommand(KoShape* shape, KoShapeStrokeModel *stroke, KUndo2Command *parent)
+KoShapeStrokeCommand::KoShapeStrokeCommand(KoShape* shape, KoShapeStrokeModelSP stroke, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes.append(shape);
d->addNewStroke(stroke);
d->addOldStroke(shape->stroke());
setText(kundo2_i18n("Set stroke"));
}
KoShapeStrokeCommand::~KoShapeStrokeCommand()
{
delete d;
}
void KoShapeStrokeCommand::redo()
{
KUndo2Command::redo();
- QList<KoShapeStrokeModel*>::iterator strokeIt = d->newStrokes.begin();
+ QList<KoShapeStrokeModelSP>::iterator strokeIt = d->newStrokes.begin();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->update();
shape->setStroke(*strokeIt);
shape->update();
++strokeIt;
}
}
void KoShapeStrokeCommand::undo()
{
KUndo2Command::undo();
- QList<KoShapeStrokeModel*>::iterator strokeIt = d->oldStrokes.begin();
+ QList<KoShapeStrokeModelSP>::iterator strokeIt = d->oldStrokes.begin();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->update();
shape->setStroke(*strokeIt);
shape->update();
++strokeIt;
}
}
+
+int KoShapeStrokeCommand::id() const
+{
+ return KisCommandUtils::ChangeShapeStrokeId;
+}
+
+bool KoShapeStrokeCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoShapeStrokeCommand *other = dynamic_cast<const KoShapeStrokeCommand*>(command);
+
+ if (!other ||
+ other->d->shapes != d->shapes) {
+
+ return false;
+ }
+
+ d->newStrokes = other->d->newStrokes;
+ return true;
+}
diff --git a/libs/flake/commands/KoShapeStrokeCommand.h b/libs/flake/commands/KoShapeStrokeCommand.h
index 35a003418d..19e0609881 100644
--- a/libs/flake/commands/KoShapeStrokeCommand.h
+++ b/libs/flake/commands/KoShapeStrokeCommand.h
@@ -1,72 +1,77 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* Copyright (C) 2006-2008 Jan Hambrecht <jaham@gmx.net>
* 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 KOSHAPESTROKECOMMAND_H
#define KOSHAPESTROKECOMMAND_H
#include "kritaflake_export.h"
+#include <KoFlakeTypes.h>
#include <kundo2command.h>
#include <QList>
class KoShape;
class KoShapeStrokeModel;
/// The undo / redo command for setting the shape stroke
class KRITAFLAKE_EXPORT KoShapeStrokeCommand : public KUndo2Command
{
public:
/**
* Command to set a new shape stroke.
* @param shapes a set of all the shapes that should get the new stroke.
* @param stroke the new stroke, the same for all given shapes
* @param parent the parent command used for macro commands
*/
- KoShapeStrokeCommand(const QList<KoShape*> &shapes, KoShapeStrokeModel *stroke, KUndo2Command *parent = 0);
+ KoShapeStrokeCommand(const QList<KoShape*> &shapes, KoShapeStrokeModelSP stroke, KUndo2Command *parent = 0);
/**
* Command to set new shape strokes.
* @param shapes a set of all the shapes that should get a new stroke.
* @param strokes the new strokes, one for each shape
* @param parent the parent command used for macro commands
*/
- KoShapeStrokeCommand(const QList<KoShape*> &shapes, const QList<KoShapeStrokeModel*> &strokes, KUndo2Command *parent = 0);
+ KoShapeStrokeCommand(const QList<KoShape*> &shapes, const QList<KoShapeStrokeModelSP> &strokes, KUndo2Command *parent = 0);
/**
* Command to set a new shape stroke.
* @param shape a single shape that should get the new stroke.
* @param stroke the new stroke
* @param parent the parent command used for macro commands
*/
- KoShapeStrokeCommand(KoShape* shape, KoShapeStrokeModel *stroke, KUndo2Command *parent = 0);
+ KoShapeStrokeCommand(KoShape* shape, KoShapeStrokeModelSP stroke, KUndo2Command *parent = 0);
virtual ~KoShapeStrokeCommand();
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
class Private;
Private * const d;
};
#endif
diff --git a/libs/flake/commands/KoShapeTransformCommand.cpp b/libs/flake/commands/KoShapeTransformCommand.cpp
index e37e27aa24..6432c71a46 100644
--- a/libs/flake/commands/KoShapeTransformCommand.cpp
+++ b/libs/flake/commands/KoShapeTransformCommand.cpp
@@ -1,77 +1,99 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
+#include "kis_command_ids.h"
+
#include "KoShapeTransformCommand.h"
#include "KoShape.h"
#include <QList>
#include <QTransform>
#include <FlakeDebug.h>
class Q_DECL_HIDDEN KoShapeTransformCommand::Private
{
public:
Private(const QList<KoShape*> &list) : shapes(list) { }
QList<KoShape*> shapes;
QList<QTransform> oldState;
QList<QTransform> newState;
};
KoShapeTransformCommand::KoShapeTransformCommand(const QList<KoShape*> &shapes, const QList<QTransform> &oldState, const QList<QTransform> &newState, KUndo2Command * parent)
: KUndo2Command(parent),
d(new Private(shapes))
{
Q_ASSERT(shapes.count() == oldState.count());
Q_ASSERT(shapes.count() == newState.count());
d->oldState = oldState;
d->newState = newState;
}
KoShapeTransformCommand::~KoShapeTransformCommand()
{
delete d;
}
void KoShapeTransformCommand::redo()
{
KUndo2Command::redo();
const int shapeCount = d->shapes.count();
for (int i = 0; i < shapeCount; ++i) {
KoShape * shape = d->shapes[i];
shape->update();
shape->setTransformation(d->newState[i]);
shape->update();
}
}
void KoShapeTransformCommand::undo()
{
KUndo2Command::undo();
const int shapeCount = d->shapes.count();
for (int i = 0; i < shapeCount; ++i) {
KoShape * shape = d->shapes[i];
shape->update();
shape->setTransformation(d->oldState[i]);
shape->update();
}
}
+
+int KoShapeTransformCommand::id() const
+{
+ return KisCommandUtils::TransformShapeId;
+}
+
+bool KoShapeTransformCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoShapeTransformCommand *other = dynamic_cast<const KoShapeTransformCommand*>(command);
+
+ if (!other ||
+ other->d->shapes != d->shapes ||
+ other->text() != text()) {
+
+ return false;
+ }
+
+ d->newState = other->d->newState;
+ return true;
+}
diff --git a/libs/flake/commands/KoShapeTransformCommand.h b/libs/flake/commands/KoShapeTransformCommand.h
index f1b62bb418..4a51014689 100644
--- a/libs/flake/commands/KoShapeTransformCommand.h
+++ b/libs/flake/commands/KoShapeTransformCommand.h
@@ -1,58 +1,61 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* 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 KOSHAPETRANSFORMCOMMAND_H
#define KOSHAPETRANSFORMCOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
class KoShape;
class QTransform;
/**
* A command to transform a selection of shapes with the same transformation.
*/
class KRITAFLAKE_EXPORT KoShapeTransformCommand : public KUndo2Command
{
public:
/**
* A command to transform a selection of shapes to the new state.
* Each shape passed has an old state and a new state of transformation passed in.
* @param shapes all the shapes that should be transformed
* @param oldState the old shapes transformations
* @param newState the new shapes transformations
* @see KoShape::transformation()
* @see KoShape::setTransformation()
*/
KoShapeTransformCommand(const QList<KoShape*> &shapes, const QList<QTransform> &oldState, const QList<QTransform> &newState, KUndo2Command * parent = 0);
~KoShapeTransformCommand();
/// redo the command
void redo();
/// revert the actions done in redo
void undo();
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
class Private;
Private * const d;
};
#endif // KOSHAPETRANSFORMCOMMAND_H
diff --git a/libs/flake/commands/KoShapeTransparencyCommand.cpp b/libs/flake/commands/KoShapeTransparencyCommand.cpp
index 1f6e5825ba..daf52042ef 100644
--- a/libs/flake/commands/KoShapeTransparencyCommand.cpp
+++ b/libs/flake/commands/KoShapeTransparencyCommand.cpp
@@ -1,100 +1,118 @@
/* 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 "KoShapeTransparencyCommand.h"
#include "KoShape.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
class Q_DECL_HIDDEN KoShapeTransparencyCommand::Private
{
public:
Private() {
}
~Private() {
}
QList<KoShape*> shapes; ///< the shapes to set background for
QList<qreal> oldTransparencies; ///< the old transparencies
QList<qreal> newTransparencies; ///< the new transparencies
};
KoShapeTransparencyCommand::KoShapeTransparencyCommand(const QList<KoShape*> &shapes, qreal transparency, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes = shapes;
Q_FOREACH (KoShape *shape, d->shapes) {
d->oldTransparencies.append(shape->transparency());
d->newTransparencies.append(transparency);
}
setText(kundo2_i18n("Set opacity"));
}
KoShapeTransparencyCommand::KoShapeTransparencyCommand(KoShape * shape, qreal transparency, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes.append(shape);
d->oldTransparencies.append(shape->transparency());
d->newTransparencies.append(transparency);
setText(kundo2_i18n("Set opacity"));
}
KoShapeTransparencyCommand::KoShapeTransparencyCommand(const QList<KoShape*> &shapes, const QList<qreal> &transparencies, KUndo2Command *parent)
: KUndo2Command(parent)
, d(new Private())
{
d->shapes = shapes;
Q_FOREACH (KoShape *shape, d->shapes) {
d->oldTransparencies.append(shape->transparency());
}
d->newTransparencies = transparencies;
setText(kundo2_i18n("Set opacity"));
}
KoShapeTransparencyCommand::~KoShapeTransparencyCommand()
{
delete d;
}
void KoShapeTransparencyCommand::redo()
{
KUndo2Command::redo();
QList<qreal>::iterator transparencyIt = d->newTransparencies.begin();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->setTransparency(*transparencyIt);
shape->update();
++transparencyIt;
}
}
void KoShapeTransparencyCommand::undo()
{
KUndo2Command::undo();
QList<qreal>::iterator transparencyIt = d->oldTransparencies.begin();
Q_FOREACH (KoShape *shape, d->shapes) {
shape->setTransparency(*transparencyIt);
shape->update();
++transparencyIt;
}
}
+
+int KoShapeTransparencyCommand::id() const
+{
+ return KisCommandUtils::ChangeShapeTransparencyId;
+}
+
+bool KoShapeTransparencyCommand::mergeWith(const KUndo2Command *command)
+{
+ const KoShapeTransparencyCommand *other = dynamic_cast<const KoShapeTransparencyCommand*>(command);
+
+ if (!other || other->d->shapes != d->shapes) {
+ return false;
+ }
+
+ d->newTransparencies = other->d->newTransparencies;
+ return true;
+}
diff --git a/libs/flake/commands/KoShapeTransparencyCommand.h b/libs/flake/commands/KoShapeTransparencyCommand.h
index bd06ef497c..4cad1f9b25 100644
--- a/libs/flake/commands/KoShapeTransparencyCommand.h
+++ b/libs/flake/commands/KoShapeTransparencyCommand.h
@@ -1,68 +1,72 @@
/* 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.
*/
#ifndef KOSHAPETRANSPARENCYCOMMAND_H
#define KOSHAPETRANSPARENCYCOMMAND_H
#include "kritaflake_export.h"
#include <kundo2command.h>
#include <QList>
class KoShape;
/// The undo / redo command for setting the shape transparency
class KRITAFLAKE_EXPORT KoShapeTransparencyCommand : public KUndo2Command
{
public:
/**
* Command to set a new shape transparency.
* @param shapes a set of all the shapes that should get the new background.
* @param transparency the new shape transparency
* @param parent the parent command used for macro commands
*/
KoShapeTransparencyCommand(const QList<KoShape*> &shapes, qreal transparency, KUndo2Command *parent = 0);
/**
* Command to set a new shape transparency.
* @param shape a single shape that should get the new transparency.
* @param transparency the new shape transparency
* @param parent the parent command used for macro commands
*/
KoShapeTransparencyCommand(KoShape *shape, qreal transparency, KUndo2Command *parent = 0);
/**
* Command to set new shape transparencies.
* @param shapes a set of all the shapes that should get a new transparency.
* @param fills the new transparencies, one for each shape
* @param parent the parent command used for macro commands
*/
KoShapeTransparencyCommand(const QList<KoShape*> &shapes, const QList<qreal> &transparencies, KUndo2Command *parent = 0);
virtual ~KoShapeTransparencyCommand();
/// redo the command
- void redo();
+ void redo() override;
/// revert the actions done in redo
- void undo();
+ void undo() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
class Private;
Private * const d;
};
#endif // KOSHAPETRANSPARENCYCOMMAND_H
diff --git a/libs/flake/commands/KoShapeUnclipCommand.cpp b/libs/flake/commands/KoShapeUnclipCommand.cpp
index 4f569f180f..a5c915fa6a 100644
--- a/libs/flake/commands/KoShapeUnclipCommand.cpp
+++ b/libs/flake/commands/KoShapeUnclipCommand.cpp
@@ -1,179 +1,154 @@
/* 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 "KoShapeUnclipCommand.h"
#include "KoClipPath.h"
#include "KoShape.h"
#include "KoShapeContainer.h"
#include "KoPathShape.h"
#include "KoShapeBasedDocumentBase.h"
-#include "KoShapeRegistry.h"
-#include "KoShapeLoadingContext.h"
-#include "KoShapeOdfSaveHelper.h"
-#include "KoDrag.h"
-#include <KoOdfPaste.h>
-#include <KoOdfLoadingContext.h>
-#include <KoOdfReadStore.h>
-#include <KoXmlReader.h>
+#include <kis_assert.h>
#include <klocalizedstring.h>
-class KoShapeUnclipCommand::Private : public KoOdfPaste
+class KoShapeUnclipCommand::Private
{
public:
Private(KoShapeBasedDocumentBase *c)
: controller(c), executed(false) {
}
- ~Private() override {
+ ~Private() {
if (executed) {
qDeleteAll(oldClipPaths);
} else {
qDeleteAll(clipPathShapes);
}
}
void createClipPathShapes() {
// check if we have already created the clip path shapes
if (!clipPathShapes.isEmpty())
return;
Q_FOREACH (KoShape *shape, shapesToUnclip) {
KoClipPath *clipPath = shape->clipPath();
if (!clipPath)
continue;
- QList<KoShape*> shapes;
+
Q_FOREACH (KoShape *clipShape, clipPath->clipPathShapes()) {
- shapes.append(clipShape);
- }
- KoShapeOdfSaveHelper saveHelper(shapes);
- KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
+ KoShape *clone = clipShape->cloneShape();
- const int pathShapeCount = clipPathShapes.count();
+ KoPathShape *pathShape = dynamic_cast<KoPathShape*>(clone);
+ KIS_SAFE_ASSERT_RECOVER(pathShape) {
+ delete clone;
+ continue;
+ }
- paste(KoOdf::Text, drag.mimeData());
+ clipPathShapes.append(pathShape);
+ }
// apply transformations
- for (int i = pathShapeCount; i < clipPathShapes.count(); ++i) {
- KoPathShape *pathShape = clipPathShapes[i];
+ Q_FOREACH (KoPathShape *pathShape, clipPathShapes) {
// apply transformation so that it matches the current clipped shapes clip path
pathShape->applyAbsoluteTransformation(clipPath->clipDataTransformation(shape));
- pathShape->setZIndex(shape->zIndex()+1);
+ pathShape->setZIndex(shape->zIndex() + 1);
clipPathParents.append(shape->parent());
}
}
}
- /// reimplemented from KoOdfPaste
- bool process(const KoXmlElement &body, KoOdfReadStore &odfStore) override {
- KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
- KoShapeLoadingContext context(loadingContext, controller->resourceManager());
-
- KoXmlElement element;
- forEachElement(element, body) {
- KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(element, context);
- if (!shape)
- continue;
- KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape);
- if (!pathShape) {
- delete shape;
- continue;
- }
- clipPathShapes.append(pathShape);
- }
- return true;
- }
-
QList<KoShape*> shapesToUnclip;
QList<KoClipPath*> oldClipPaths;
QList<KoPathShape*> clipPathShapes;
QList<KoShapeContainer*> clipPathParents;
+
+ // TODO: damn! this is not a controller, this is a document! Needs refactoring!
KoShapeBasedDocumentBase *controller;
bool executed;
};
KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeBasedDocumentBase *controller, const QList<KoShape*> &shapes, KUndo2Command *parent)
: KUndo2Command(parent), d(new Private(controller))
{
d->shapesToUnclip = shapes;
Q_FOREACH (KoShape *shape, d->shapesToUnclip) {
d->oldClipPaths.append(shape->clipPath());
}
setText(kundo2_i18n("Unclip Shape"));
}
KoShapeUnclipCommand::KoShapeUnclipCommand(KoShapeBasedDocumentBase *controller, KoShape *shape, KUndo2Command *parent)
: KUndo2Command(parent), d(new Private(controller))
{
d->shapesToUnclip.append(shape);
d->oldClipPaths.append(shape->clipPath());
setText(kundo2_i18n("Unclip Shapes"));
}
KoShapeUnclipCommand::~KoShapeUnclipCommand()
{
delete d;
}
void KoShapeUnclipCommand::redo()
{
d->createClipPathShapes();
const uint shapeCount = d->shapesToUnclip.count();
for (uint i = 0; i < shapeCount; ++i) {
d->shapesToUnclip[i]->setClipPath(0);
d->shapesToUnclip[i]->update();
}
const uint clipPathCount = d->clipPathShapes.count();
for (uint i = 0; i < clipPathCount; ++i) {
// the parent has to be there when it is added to the KoShapeBasedDocumentBase
if (d->clipPathParents.at(i))
d->clipPathParents.at(i)->addShape(d->clipPathShapes[i]);
d->controller->addShape(d->clipPathShapes[i]);
}
d->executed = true;
KUndo2Command::redo();
}
void KoShapeUnclipCommand::undo()
{
KUndo2Command::undo();
const uint shapeCount = d->shapesToUnclip.count();
for (uint i = 0; i < shapeCount; ++i) {
d->shapesToUnclip[i]->setClipPath(d->oldClipPaths[i]);
d->shapesToUnclip[i]->update();
}
const uint clipPathCount = d->clipPathShapes.count();
for (uint i = 0; i < clipPathCount; ++i) {
d->controller->removeShape(d->clipPathShapes[i]);
if (d->clipPathParents.at(i))
d->clipPathParents.at(i)->removeShape(d->clipPathShapes[i]);
}
d->executed = false;
}
diff --git a/libs/flake/commands/KoShapeUngroupCommand.cpp b/libs/flake/commands/KoShapeUngroupCommand.cpp
index 0158987fb6..a2afe90448 100644
--- a/libs/flake/commands/KoShapeUngroupCommand.cpp
+++ b/libs/flake/commands/KoShapeUngroupCommand.cpp
@@ -1,79 +1,84 @@
/* 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 "KoShapeGroupCommand_p.h"
#include "KoShapeContainer.h"
#include <klocalizedstring.h>
KoShapeUngroupCommand::KoShapeUngroupCommand(KoShapeContainer *container, const QList<KoShape *> &shapes,
const QList<KoShape*> &topLevelShapes, KUndo2Command *parent)
- : KoShapeGroupCommand(*(new KoShapeGroupCommandPrivate(container, shapes)), parent)
+ : KoShapeGroupCommand(*(new KoShapeGroupCommandPrivate(container,
+ shapes,
+ QList<bool>(),
+ QList<bool>(),
+ false)), parent)
{
QList<KoShape*> orderdShapes(shapes);
qSort(orderdShapes.begin(), orderdShapes.end(), KoShape::compareShapeZIndex);
d->shapes = orderdShapes;
QList<KoShape*> ancestors = d->container->parent()? d->container->parent()->shapes(): topLevelShapes;
if (ancestors.count()) {
qSort(ancestors.begin(), ancestors.end(), KoShape::compareShapeZIndex);
QList<KoShape*>::const_iterator it(qFind(ancestors, d->container));
Q_ASSERT(it != ancestors.constEnd());
for (; it != ancestors.constEnd(); ++it) {
d->oldAncestorsZIndex.append(QPair<KoShape*, int>(*it, (*it)->zIndex()));
}
}
int zIndex = d->container->zIndex();
Q_FOREACH (KoShape *shape, d->shapes) {
d->clipped.append(d->container->isClipped(shape));
d->oldParents.append(d->container->parent());
d->oldClipped.append(d->container->isClipped(shape));
d->oldInheritTransform.append(shape->parent() && shape->parent()->inheritsTransform(shape));
- d->inheritTransform.append(false);
+ d->inheritTransform.append(d->container->inheritsTransform(shape));
// TODO this might also need to change the children of the parent but that is very problematic if the parent is 0
d->oldZIndex.append(zIndex++);
}
+ d->shouldNormalize = false;
setText(kundo2_i18n("Ungroup shapes"));
}
void KoShapeUngroupCommand::redo()
{
KoShapeGroupCommand::undo();
if (d->oldAncestorsZIndex.count()) {
int zIndex = d->container->zIndex() + d->oldZIndex.count() - 1;
for (QList<QPair<KoShape*, int> >::const_iterator it(d->oldAncestorsZIndex.constBegin()); it != d->oldAncestorsZIndex.constEnd(); ++it) {
it->first->setZIndex(zIndex++);
}
}
}
void KoShapeUngroupCommand::undo()
{
KoShapeGroupCommand::redo();
if (d->oldAncestorsZIndex.count()) {
for (QList<QPair<KoShape*, int> >::const_iterator it(d->oldAncestorsZIndex.constBegin()); it != d->oldAncestorsZIndex.constEnd(); ++it) {
it->first->setZIndex(it->second);
}
}
}
diff --git a/libs/flake/flake.qrc b/libs/flake/flake.qrc
index 2de8a8221f..ff579c2238 100644
--- a/libs/flake/flake.qrc
+++ b/libs/flake/flake.qrc
@@ -1,32 +1,19 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file alias="snap-boundingbox.png">pics/16-actions-snap-boundingbox.png</file>
<file alias="snap-extension.png">pics/16-actions-snap-extension.png</file>
<file alias="convert-to-path.png">pics/22-actions-convert-to-path.png</file>
<file alias="createpath.png">pics/22-actions-createpath.png</file>
<file alias="editpath.png">pics/22-actions-editpath.png</file>
<file alias="hand.png">pics/22-actions-hand.png</file>
- <file alias="path-break-point.png">pics/22-actions-path-break-point.png</file>
- <file alias="path-break-segment.png">pics/22-actions-path-break-segment.png</file>
- <file alias="pathpoint-corner.png">pics/22-actions-pathpoint-corner.png</file>
- <file alias="pathpoint-curve.png">pics/22-actions-pathpoint-curve.png</file>
- <file alias="pathpoint-insert.png">pics/22-actions-pathpoint-insert.png</file>
- <file alias="pathpoint-join.png">pics/22-actions-pathpoint-join.png</file>
- <file alias="pathpoint-line.png">pics/22-actions-pathpoint-line.png</file>
- <file alias="pathpoint-merge.png">pics/22-actions-pathpoint-merge.png</file>
- <file alias="pathpoint-remove.png">pics/22-actions-pathpoint-remove.png</file>
- <file alias="pathpoint-smooth.png">pics/22-actions-pathpoint-smooth.png</file>
- <file alias="pathpoint-symmetric.png">pics/22-actions-pathpoint-symmetric.png</file>
- <file alias="pathsegment-curve.png">pics/22-actions-pathsegment-curve.png</file>
- <file alias="pathsegment-line.png">pics/22-actions-pathsegment-line.png</file>
<file alias="pathshape.png">pics/22-actions-pathshape.png</file>
<file alias="questionmark.png">pics/questionmark.png</file>
<file alias="snap-boundingbox.svg">pics/sc-actions-snap-boundingbox.svg</file>
<file alias="snap-extension.svg">pics/sc-actions-snap-extension.svg</file>
<file alias="zoom_in_cursor.png">pics/zoom_in_cursor.png</file>
<file alias="zoom_out_cursor.png">pics/zoom_out_cursor.png</file>
<file alias="x-shape-connection.png">pics/ox32-mimetype-x-shape-connection.png</file>
<file alias="x-shape-text.png">pics/ox32-mimetype-x-shape-text.png</file>
</qresource>
</RCC>
diff --git a/libs/flake/pics/22-actions-path-break-point.png b/libs/flake/pics/22-actions-path-break-point.png
deleted file mode 100644
index 0c27563f14..0000000000
Binary files a/libs/flake/pics/22-actions-path-break-point.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-path-break-segment.png b/libs/flake/pics/22-actions-path-break-segment.png
deleted file mode 100644
index 7fd8d9fe67..0000000000
Binary files a/libs/flake/pics/22-actions-path-break-segment.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-corner.png b/libs/flake/pics/22-actions-pathpoint-corner.png
deleted file mode 100644
index a3f23e866b..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-corner.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-curve.png b/libs/flake/pics/22-actions-pathpoint-curve.png
deleted file mode 100644
index 0f94984411..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-curve.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-insert.png b/libs/flake/pics/22-actions-pathpoint-insert.png
deleted file mode 100644
index 812e6995b0..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-insert.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-join.png b/libs/flake/pics/22-actions-pathpoint-join.png
deleted file mode 100644
index fa1791c1af..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-join.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-line.png b/libs/flake/pics/22-actions-pathpoint-line.png
deleted file mode 100644
index d661cb2391..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-line.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-merge.png b/libs/flake/pics/22-actions-pathpoint-merge.png
deleted file mode 100644
index 0a1064f69a..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-merge.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-remove.png b/libs/flake/pics/22-actions-pathpoint-remove.png
deleted file mode 100644
index 245ba51217..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-remove.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-smooth.png b/libs/flake/pics/22-actions-pathpoint-smooth.png
deleted file mode 100644
index 846889367b..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-smooth.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathpoint-symmetric.png b/libs/flake/pics/22-actions-pathpoint-symmetric.png
deleted file mode 100644
index d3ef551b3d..0000000000
Binary files a/libs/flake/pics/22-actions-pathpoint-symmetric.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathsegment-curve.png b/libs/flake/pics/22-actions-pathsegment-curve.png
deleted file mode 100644
index e359a90c29..0000000000
Binary files a/libs/flake/pics/22-actions-pathsegment-curve.png and /dev/null differ
diff --git a/libs/flake/pics/22-actions-pathsegment-line.png b/libs/flake/pics/22-actions-pathsegment-line.png
deleted file mode 100644
index d90e9c02e9..0000000000
Binary files a/libs/flake/pics/22-actions-pathsegment-line.png and /dev/null differ
diff --git a/libs/flake/styles/CMakeLists.txt b/libs/flake/styles/CMakeLists.txt
index 116f29c561..7a2999a9d5 100644
--- a/libs/flake/styles/CMakeLists.txt
+++ b/libs/flake/styles/CMakeLists.txt
@@ -1,3 +1,3 @@
install(FILES
- markers.xml
+ markers.svg
DESTINATION ${DATA_INSTALL_DIR}/krita/styles)
diff --git a/libs/flake/styles/markers.svg b/libs/flake/styles/markers.svg
new file mode 100644
index 0000000000..c7cf439dd0
--- /dev/null
+++ b/libs/flake/styles/markers.svg
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ viewBox="0 0 744.09448819 1052.3622047"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="markers.svg">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="DotS"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="DotS"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4209"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.2) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="DotM"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="DotM"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4206"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.4) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="DotL"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="DotL"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4203"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.8) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;"
+ inkscape:isstock="true">
+ <path
+ id="path4157"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4154"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;"
+ inkscape:isstock="true">
+ <path
+ id="path4151"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4148"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;"
+ inkscape:isstock="true">
+ <path
+ id="path4145"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4142"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.8) translate(12.5,0)" />
+ </marker>
+ </defs>
+
+</svg>
diff --git a/libs/flake/styles/markers.xml b/libs/flake/styles/markers.xml
deleted file mode 100644
index 2d64b44189..0000000000
--- a/libs/flake/styles/markers.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<office:document-styles 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:smil="urn:oasis:names:tc:opendocument:xmlns:smil-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" office:version="1.2">
- <office:styles>
- <draw:marker draw:name="Marker_0" draw:display-name="Arrow" svg:d="M10 0l-10 30h20z" svg:viewBox="0 0 20 30"/>
- <draw:marker draw:name="Marker_1" draw:display-name="Square" svg:d="M0 0h10v10h-10z" svg:viewBox="0 0 10 10"/>
- <draw:marker draw:name="Marker_2" draw:display-name="Small Arrow" svg:d="M1321 3493h-1321l702-3493z" svg:viewBox="0 0 1321 3493"/>
- <draw:marker draw:name="Marker_3" draw:display-name="Dimension Lines" svg:d="M0 0h278 278 280v36 36 38h-278-278-280v-36-36z" svg:viewBox="0 0 836 110"/>
- <draw:marker draw:name="Marker_4" draw:display-name="Double Arrow" svg:d="M737 1131h394l-564-1131-567 1131h398l-398 787h1131z" svg:viewBox="0 0 1131 1918"/>
- <draw:marker draw:name="Marker_5" draw:display-name="Rounded short Arrow" svg:d="M1009 1050l-449-1008-22-30-29-12-34 12-21 26-449 1012-5 13v8l5 21 12 21 17 13 21 4h903l21-4 21-13 9-21 4-21v-8z" svg:viewBox="0 0 1013 1130"/>
- <draw:marker draw:name="Marker_6" draw:display-name="Symmetric Arrow" svg:d="M564 0l-564 902h1131z" svg:viewBox="0 0 1131 902"/>
- <draw:marker draw:name="Marker_7" draw:display-name="Line Arrow" svg:d="M0 2108v17 17l12 42 30 34 38 21 43 4 29-8 30-21 25-26 13-34 343-1532 339 1520 13 42 29 34 39 21 42 4 42-12 34-30 21-42v-39-12l-4 4-440-1998-9-42-25-39-38-25-43-8-42 8-38 25-26 39-8 42z" svg:viewBox="0 0 1122 2243"/>
- <draw:marker draw:name="Marker_8" draw:display-name="Rounded large Arrow" svg:d="M1127 2120l-449-2006-9-42-25-39-38-25-38-8-43 8-38 25-25 39-9 42-449 2006v13l-4 9 9 42 25 38 38 25 42 9h903l42-9 38-25 26-38 8-42v-9z" svg:viewBox="0 0 1131 2256"/>
- <draw:marker draw:name="Marker_9" draw:display-name="Circle" svg:d="M462 1118l-102-29-102-51-93-72-72-93-51-102-29-102-13-105 13-102 29-106 51-102 72-89 93-72 102-50 102-34 106-9 101 9 106 34 98 50 93 72 72 89 51 102 29 106 13 102-13 105-29 102-51 102-72 93-93 72-98 51-106 29-101 13z" svg:viewBox="0 0 1131 1131"/>
- <draw:marker draw:name="Marker_10" draw:display-name="Square 45" svg:d="M0 564l564 567 567-567-567-564z" svg:viewBox="0 0 1131 1131"/>
- <draw:marker draw:name="Marker_11" draw:display-name="Arrow concave" svg:d="M1013 1491l118 89-567-1580-564 1580 114-85 136-68 148-46 161-17 161 13 153 46z" svg:viewBox="0 0 1131 1580"/>
- <draw:marker draw:name="Marker_12" draw:display-name="Short line Arrow" svg:d="M1500 0l1500 2789v211h-114l-1286-2392v2392h-200v-2392l-1286 2392h-114v-211z" svg:viewBox="0 0 3000 3000"/>
- <draw:marker draw:name="Marker_13" draw:display-name="Triangle unfilled" svg:d="M1500 0l1500 3000h-3000zM1500 447l-1176 2353h2353z" svg:viewBox="0 0 3000 3000"/>
- <draw:marker draw:name="Marker_14" draw:display-name="Diamond unfilled" svg:d="M1500 0l1500 3000-1500 3000-1500-3000zM1500 447l-1276 2553 1276 2553 1276-2553z" svg:viewBox="0 0 3000 6000"/>
- <draw:marker draw:name="Marker_15" draw:display-name="Diamond" svg:d="M1500 0l1500 3000-1500 3000-1500-3000z" svg:viewBox="0 0 3000 6000"/>
- <draw:marker draw:name="Marker_16" draw:display-name="Circle unfilled" svg:d="M1500 3000c-276 0-511-63-750-201s-411-310-549-549-201-474-201-750 63-511 201-750 310-411 549-549 474-201 750-201 511 63 750 201 411 310 549 549 201 474 201 750-63 511-201 750-310 411-549 549-474 201-750 201zM1500 2800c-239 0-443-55-650-174s-356-269-476-476-174-411-174-650 55-443 174-650 269-356 476-476c207-119 411-174 650-174s443 55 650 174c207 120 356 269 476 476s174 411 174 650-55 443-174 650-269 356-476 476c-207 119-411 174-650 174z" svg:viewBox="0 0 3000 3000"/>
- <draw:marker draw:name="Marker_17" draw:display-name="Square 45 unfilled" svg:d="M1500 3000l-1500-1500 1500-1500 1500 1500zM1500 2715l-1215-1215 1215-1215 1215 1215z" svg:viewBox="0 0 3000 3000"/>
- <draw:marker draw:name="Marker_18" draw:display-name="Square unfilled" svg:d="M0 0h300v300h-300zM20 20h260v260h-260z" svg:viewBox="0 0 300 300"/>
- <draw:marker draw:name="Marker_19" draw:display-name="Half Circle unfilled" svg:d="M14971 0c21 229 29 423 29 653 0 690-79 1328-244 1943-165 614-416 1206-761 1804-345 597-733 1110-1183 1560-451 450-964 837-1562 1182s-1190 596-1806 760c-600 161-1223 240-1894 244v600h-100v-600c-671-4-1294-83-1894-244-616-164-1208-415-1806-760s-1111-732-1562-1182c-450-450-838-963-1183-1560-345-598-596-1190-761-1804-165-615-244-1253-244-1943 0-230 8-424 29-653l298 26 299 26c-18 211-26 390-26 601 0 635 72 1222 224 1787 151 566 383 1110 700 1659 318 550 674 1022 1088 1437 415 414 888 769 1438 1087 550 317 1095 548 1661 700 566 151 1154 223 1789 223s1223-72 1789-223c566-152 1111-383 1661-700 550-318 1023-673 1438-1087 414-415 770-887 1088-1437 317-549 549-1093 700-1659 152-565 224-1152 224-1787 0-211-8-390-26-601l299-26z" svg:viewBox="0 0 15000 8746"/>
- </office:styles>
-</office:document-styles>
diff --git a/libs/flake/svg/KoShapePainter.cpp b/libs/flake/svg/KoShapePainter.cpp
index 7812a8c81d..610df1f4e7 100644
--- a/libs/flake/svg/KoShapePainter.cpp
+++ b/libs/flake/svg/KoShapePainter.cpp
@@ -1,214 +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 "KoShapeManagerPaintingStrategy.h"
#include "KoShape.h"
#include "KoViewConverter.h"
#include "KoShapeStrokeModel.h"
#include "KoShapeGroup.h"
#include "KoShapeContainer.h"
#include <KoUnit.h>
#include <QPainter>
#include <QImage>
class SimpleCanvas : public KoCanvasBase
{
public:
SimpleCanvas()
- : KoCanvasBase(0), m_shapeManager(new KoShapeManager(this))
+ : KoCanvasBase(0),
+ m_shapeManager(new KoShapeManager(this)),
+ m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
{
}
~SimpleCanvas() override
{
- delete m_shapeManager;
}
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;
+ 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:
- KoShapeManager *m_shapeManager;
+ 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(KoShapeManagerPaintingStrategy *strategy)
+KoShapePainter::KoShapePainter()
: d(new Private())
{
- if (strategy) {
- strategy->setShapeManager(d->canvas->shapeManager());
- d->canvas->shapeManager()->setPaintingStrategy(strategy);
- }
}
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)
{
foreach (KoShape *shape, d->canvas->shapeManager()->shapes()) {
shape->waitUntilReady(converter, false);
}
d->canvas->shapeManager()->paint(painter, converter, 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());
// finally paint the shapes
paint(painter, converter);
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(true))
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 0864d34fd0..078361ffd0 100644
--- a/libs/flake/svg/KoShapePainter.h
+++ b/libs/flake/svg/KoShapePainter.h
@@ -1,83 +1,83 @@
/* 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 KoShapeManagerPaintingStrategy;
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 loosing the cache between usages.
*/
-class KoShapePainter
+class KRITAFLAKE_EXPORT KoShapePainter
{
public:
- explicit KoShapePainter(KoShapeManagerPaintingStrategy *strategy = 0);
+ 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);
/**
* 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/SvgClipPathHelper.cpp b/libs/flake/svg/SvgClipPathHelper.cpp
index 0fc6378f31..a5868f32a5 100644
--- a/libs/flake/svg/SvgClipPathHelper.cpp
+++ b/libs/flake/svg/SvgClipPathHelper.cpp
@@ -1,49 +1,54 @@
/* 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 "SvgClipPathHelper.h"
SvgClipPathHelper::SvgClipPathHelper()
- : m_clipPathUnits(UserSpaceOnUse) // default as per svg spec
+ : m_clipPathUnits(KoFlake::UserSpaceOnUse) // default as per svg spec
{
}
SvgClipPathHelper::~SvgClipPathHelper()
{
}
-void SvgClipPathHelper::setClipPathUnits(Units clipPathUnits)
+void SvgClipPathHelper::setClipPathUnits(KoFlake::CoordinateSystem clipPathUnits)
{
m_clipPathUnits = clipPathUnits;
}
-SvgClipPathHelper::Units SvgClipPathHelper::clipPathUnits() const
+KoFlake::CoordinateSystem SvgClipPathHelper::clipPathUnits() const
{
return m_clipPathUnits;
}
-void SvgClipPathHelper::setContent(const KoXmlElement &content)
+QList<KoShape *> SvgClipPathHelper::shapes() const
{
- m_content = content;
+ return m_shapes;
}
-KoXmlElement SvgClipPathHelper::content() const
+void SvgClipPathHelper::setShapes(const QList<KoShape *> &shapes)
{
- return m_content;
+ m_shapes = shapes;
+}
+
+bool SvgClipPathHelper::isEmpty() const
+{
+ return m_shapes.isEmpty();
}
diff --git a/libs/flake/svg/SvgClipPathHelper.h b/libs/flake/svg/SvgClipPathHelper.h
index 90f30722e2..d940f0705c 100644
--- a/libs/flake/svg/SvgClipPathHelper.h
+++ b/libs/flake/svg/SvgClipPathHelper.h
@@ -1,48 +1,49 @@
/* 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 SVGCLIPPATHHELPER_H
#define SVGCLIPPATHHELPER_H
-#include <KoXmlReader.h>
+#include <KoFlakeCoordinateSystem.h>
+#include <QList>
+
+class KoShape;
class SvgClipPathHelper
{
public:
- enum Units { UserSpaceOnUse, ObjectBoundingBox };
-
SvgClipPathHelper();
~SvgClipPathHelper();
/// Set the clip path units type
- void setClipPathUnits(Units clipPathUnits);
+ void setClipPathUnits(KoFlake::CoordinateSystem clipPathUnits);
/// Returns the clip path units type
- Units clipPathUnits() const;
+ KoFlake::CoordinateSystem clipPathUnits() const;
+
+ QList<KoShape *> shapes() const;
+ void setShapes(const QList<KoShape *> &shapes);
- /// Sets the dom element containing the clip path
- void setContent(const KoXmlElement &content);
- /// Return the clip path element
- KoXmlElement content() const;
+ bool isEmpty() const;
private:
- Units m_clipPathUnits;
- KoXmlElement m_content;
+ KoFlake::CoordinateSystem m_clipPathUnits;
+ QList<KoShape*> m_shapes;
};
#endif // SVGCLIPPATHHELPER_H
diff --git a/libs/flake/svg/SvgFilterHelper.cpp b/libs/flake/svg/SvgFilterHelper.cpp
index 56a093a5d6..39d2515e91 100644
--- a/libs/flake/svg/SvgFilterHelper.cpp
+++ b/libs/flake/svg/SvgFilterHelper.cpp
@@ -1,91 +1,91 @@
/* 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 "SvgFilterHelper.h"
#include "SvgUtil.h"
SvgFilterHelper::SvgFilterHelper()
- : m_filterUnits(ObjectBoundingBox) // default as per svg spec
- , m_primitiveUnits(UserSpaceOnUse) // default as per svg spec
+ : m_filterUnits(KoFlake::ObjectBoundingBox) // default as per svg spec
+ , m_primitiveUnits(KoFlake::UserSpaceOnUse) // default as per svg spec
, m_position(-0.1, -0.1) // default as per svg spec
, m_size(1.2, 1.2) // default as per svg spec
{
}
SvgFilterHelper::~SvgFilterHelper()
{
}
-void SvgFilterHelper::setFilterUnits(Units filterUnits)
+void SvgFilterHelper::setFilterUnits(KoFlake::CoordinateSystem filterUnits)
{
m_filterUnits = filterUnits;
}
-SvgFilterHelper::Units SvgFilterHelper::filterUnits() const
+KoFlake::CoordinateSystem SvgFilterHelper::filterUnits() const
{
return m_filterUnits;
}
-void SvgFilterHelper::setPrimitiveUnits(Units primitiveUnits)
+void SvgFilterHelper::setPrimitiveUnits(KoFlake::CoordinateSystem primitiveUnits)
{
m_primitiveUnits = primitiveUnits;
}
-SvgFilterHelper::Units SvgFilterHelper::primitiveUnits() const
+KoFlake::CoordinateSystem SvgFilterHelper::primitiveUnits() const
{
return m_primitiveUnits;
}
void SvgFilterHelper::setPosition(const QPointF & position)
{
m_position = position;
}
QPointF SvgFilterHelper::position(const QRectF & objectBound) const
{
- if (m_filterUnits == UserSpaceOnUse) {
+ if (m_filterUnits == KoFlake::UserSpaceOnUse) {
return m_position;
} else {
return SvgUtil::objectToUserSpace(m_position, objectBound);
}
}
void SvgFilterHelper::setSize(const QSizeF & size)
{
m_size = size;
}
QSizeF SvgFilterHelper::size(const QRectF & objectBound) const
{
- if (m_filterUnits == UserSpaceOnUse) {
+ if (m_filterUnits == KoFlake::UserSpaceOnUse) {
return m_size;
} else {
return SvgUtil::objectToUserSpace(m_size, objectBound);
}
}
void SvgFilterHelper::setContent(const KoXmlElement &content)
{
m_filterContent = content;
}
KoXmlElement SvgFilterHelper::content() const
{
return m_filterContent;
}
diff --git a/libs/flake/svg/SvgFilterHelper.h b/libs/flake/svg/SvgFilterHelper.h
index 3d3b7513e3..acfcc6f02b 100644
--- a/libs/flake/svg/SvgFilterHelper.h
+++ b/libs/flake/svg/SvgFilterHelper.h
@@ -1,73 +1,72 @@
/* 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.
*/
#ifndef SVGFILTERHELPER_H
#define SVGFILTERHELPER_H
#include <QSizeF>
#include <QPointF>
+#include <KoFlakeCoordinateSystem.h>
#include <KoXmlReader.h>
class QRectF;
class SvgFilterHelper
{
public:
- enum Units { UserSpaceOnUse, ObjectBoundingBox };
-
SvgFilterHelper();
~SvgFilterHelper();
/// Set the filter units type
- void setFilterUnits(Units filterUnits);
+ void setFilterUnits(KoFlake::CoordinateSystem filterUnits);
/// Returns the filter units type
- Units filterUnits() const;
+ KoFlake::CoordinateSystem filterUnits() const;
/// Set the filter primitive units type
- void setPrimitiveUnits(Units primitiveUnits);
+ void setPrimitiveUnits(KoFlake::CoordinateSystem primitiveUnits);
/// Returns the filter primitive units type
- Units primitiveUnits() const;
+ KoFlake::CoordinateSystem primitiveUnits() const;
/// Sets filter position
void setPosition(const QPointF & position);
/// Returns filter position (objectBound is used when filterUnits == ObjectBoundingBox)
QPointF position(const QRectF & objectBound) const;
/// Sets filter size
void setSize(const QSizeF & size);
/// Returns filter size (objectBound is used when filterUnits == ObjectBoundingBox)
QSizeF size(const QRectF & objectBound) const;
/// Sets the dom element containing the filter
void setContent(const KoXmlElement &content);
/// Return the filer element
KoXmlElement content() const;
static QPointF toUserSpace(const QPointF &position, const QRectF &objectBound);
static QSizeF toUserSpace(const QSizeF &size, const QRectF &objectBound);
private:
- Units m_filterUnits;
- Units m_primitiveUnits;
+ KoFlake::CoordinateSystem m_filterUnits;
+ KoFlake::CoordinateSystem m_primitiveUnits;
QPointF m_position;
QSizeF m_size;
KoXmlElement m_filterContent;
};
#endif // SVGFILTERHELPER_H
diff --git a/libs/flake/svg/SvgGradientHelper.cpp b/libs/flake/svg/SvgGradientHelper.cpp
index 630b38f239..dcc5cf0dad 100644
--- a/libs/flake/svg/SvgGradientHelper.cpp
+++ b/libs/flake/svg/SvgGradientHelper.cpp
@@ -1,213 +1,99 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2009 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 "SvgGradientHelper.h"
#include <QConicalGradient>
#include <QLinearGradient>
#include <QRadialGradient>
#include <cmath>
#include <KoFlake.h>
SvgGradientHelper::SvgGradientHelper()
- : m_gradient(0), m_gradientUnits(ObjectBoundingBox)
+ : m_gradient(new QGradient()), m_gradientUnits(KoFlake::ObjectBoundingBox)
{
}
SvgGradientHelper::~SvgGradientHelper()
{
delete m_gradient;
}
SvgGradientHelper::SvgGradientHelper(const SvgGradientHelper &other)
- : m_gradient(0), m_gradientUnits(ObjectBoundingBox)
+ : m_gradient(0), m_gradientUnits(KoFlake::ObjectBoundingBox)
{
m_gradientUnits = other.m_gradientUnits;
m_gradientTransform = other.m_gradientTransform;
- copyGradient(other.m_gradient);
+ m_gradient = KoFlake::cloneGradient(other.m_gradient);
}
SvgGradientHelper & SvgGradientHelper::operator = (const SvgGradientHelper & rhs)
{
if (this == &rhs)
return *this;
m_gradientUnits = rhs.m_gradientUnits;
m_gradientTransform = rhs.m_gradientTransform;
- copyGradient(rhs.m_gradient);
+ m_gradient = KoFlake::cloneGradient(rhs.m_gradient);
return *this;
}
-void SvgGradientHelper::setGradientUnits(Units units)
+void SvgGradientHelper::setGradientUnits(KoFlake::CoordinateSystem units)
{
m_gradientUnits = units;
}
-SvgGradientHelper::Units SvgGradientHelper::gradientUnits() const
+KoFlake::CoordinateSystem SvgGradientHelper::gradientUnits() const
{
return m_gradientUnits;
}
-QGradient * SvgGradientHelper::gradient()
+QGradient * SvgGradientHelper::gradient() const
{
return m_gradient;
}
void SvgGradientHelper::setGradient(QGradient * g)
{
delete m_gradient;
m_gradient = g;
}
-void SvgGradientHelper::copyGradient(QGradient * other)
-{
- delete m_gradient;
- m_gradient = duplicateGradient(other, QTransform());
-}
-
-QBrush SvgGradientHelper::adjustedFill(const QRectF &bound)
-{
- QBrush brush;
-
- QGradient * g = adjustedGradient(bound);
- if (g) {
- brush = QBrush(*g);
- delete g;
- }
-
- return brush;
-}
-
QTransform SvgGradientHelper::transform() const
{
return m_gradientTransform;
}
void SvgGradientHelper::setTransform(const QTransform &transform)
{
m_gradientTransform = transform;
}
-QGradient * SvgGradientHelper::adjustedGradient(const QRectF &bound) const
+QGradient::Spread SvgGradientHelper::spreadMode() const
{
- QTransform matrix;
- matrix.scale(0.01 * bound.width(), 0.01 * bound.height());
-
- return duplicateGradient(m_gradient, matrix);
+ return m_spreadMode;
}
-QGradient * SvgGradientHelper::duplicateGradient(const QGradient * originalGradient, const QTransform &transform)
+void SvgGradientHelper::setSpreadMode(const QGradient::Spread &spreadMode)
{
- if (! originalGradient)
- return 0;
-
- QGradient * duplicatedGradient = 0;
-
- switch (originalGradient->type()) {
- case QGradient::ConicalGradient: {
- const QConicalGradient * o = static_cast<const QConicalGradient*>(originalGradient);
- QConicalGradient * g = new QConicalGradient();
- g->setAngle(o->angle());
- g->setCenter(transform.map(o->center()));
- duplicatedGradient = g;
- }
- break;
- case QGradient::LinearGradient: {
- const QLinearGradient * o = static_cast<const QLinearGradient*>(originalGradient);
- QLinearGradient * g = new QLinearGradient();
- g->setStart(transform.map(o->start()));
- g->setFinalStop(transform.map(o->finalStop()));
- duplicatedGradient = g;
- }
- break;
- case QGradient::RadialGradient: {
- const QRadialGradient * o = static_cast<const QRadialGradient*>(originalGradient);
- QRadialGradient * g = new QRadialGradient();
- g->setCenter(transform.map(o->center()));
- g->setFocalPoint(transform.map(o->focalPoint()));
- g->setRadius(transform.map(QPointF(o->radius(), 0.0)).x());
- duplicatedGradient = g;
- }
- break;
- default:
- return 0;
- }
-
- duplicatedGradient->setCoordinateMode(originalGradient->coordinateMode());
- duplicatedGradient->setStops(originalGradient->stops());
- duplicatedGradient->setSpread(originalGradient->spread());
-
- return duplicatedGradient;
+ m_spreadMode = spreadMode;
}
-
-QGradient *SvgGradientHelper::convertGradient(const QGradient *originalGradient, const QSizeF &size)
-{
- if (! originalGradient)
- return 0;
-
- if (originalGradient->coordinateMode() != QGradient::LogicalMode) {
- return duplicateGradient(originalGradient, QTransform());
- }
-
- QGradient *duplicatedGradient = 0;
-
- switch (originalGradient->type()) {
- case QGradient::ConicalGradient:
- {
- const QConicalGradient *o = static_cast<const QConicalGradient*>(originalGradient);
- QConicalGradient *g = new QConicalGradient();
- g->setAngle(o->angle());
- g->setCenter(KoFlake::toRelative(o->center(),size));
- duplicatedGradient = g;
- }
- break;
- case QGradient::LinearGradient:
- {
- const QLinearGradient *o = static_cast<const QLinearGradient*>(originalGradient);
- QLinearGradient *g = new QLinearGradient();
- g->setStart(KoFlake::toRelative(o->start(),size));
- g->setFinalStop(KoFlake::toRelative(o->finalStop(),size));
- duplicatedGradient = g;
- }
- break;
- case QGradient::RadialGradient:
- {
- const QRadialGradient *o = static_cast<const QRadialGradient*>(originalGradient);
- QRadialGradient *g = new QRadialGradient();
- g->setCenter(KoFlake::toRelative(o->center(),size));
- g->setFocalPoint(KoFlake::toRelative(o->focalPoint(),size));
- g->setRadius(KoFlake::toRelative(QPointF(o->radius(), 0.0),
- QSizeF(sqrt(size.width() * size.width() + size.height() * size.height()), 0.0)).x());
- duplicatedGradient = g;
- }
- break;
- default:
- return 0;
- }
-
- duplicatedGradient->setCoordinateMode(QGradient::ObjectBoundingMode);
- duplicatedGradient->setStops(originalGradient->stops());
- duplicatedGradient->setSpread(originalGradient->spread());
-
- return duplicatedGradient;
-}
-
diff --git a/libs/flake/svg/SvgGradientHelper.h b/libs/flake/svg/SvgGradientHelper.h
index 897bf10004..0e4de09bd8 100644
--- a/libs/flake/svg/SvgGradientHelper.h
+++ b/libs/flake/svg/SvgGradientHelper.h
@@ -1,77 +1,70 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2009 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.
*/
#ifndef SVGGRADIENTHELPER_H
#define SVGGRADIENTHELPER_H
+#include <KoFlakeCoordinateSystem.h>
#include <QTransform>
-
-class QGradient;
+#include <QGradient>
class SvgGradientHelper
{
public:
- enum Units { UserSpaceOnUse, ObjectBoundingBox };
-
SvgGradientHelper();
~SvgGradientHelper();
/// Copy constructor
SvgGradientHelper(const SvgGradientHelper &other);
/// Sets the gradient units type
- void setGradientUnits(Units units);
+ void setGradientUnits(KoFlake::CoordinateSystem units);
/// Returns gradient units type
- Units gradientUnits() const;
+ KoFlake::CoordinateSystem gradientUnits() const;
/// Sets the gradient
void setGradient(QGradient * g);
/// Retrurns the gradient
- QGradient * gradient();
-
- /// Copies the given gradient
- void copyGradient(QGradient * g);
-
- /// Returns fill adjusted to the given bounding box
- QBrush adjustedFill(const QRectF &bound);
+ QGradient * gradient() const;
/// Returns the gradient transformation
QTransform transform() const;
/// Sets the gradient transformation
void setTransform(const QTransform &transform);
/// Assigment operator
SvgGradientHelper & operator = (const SvgGradientHelper & rhs);
QGradient * adjustedGradient(const QRectF &bound) const;
/// Converts a gradient from LogicalMode to ObjectBoundingMode
- static QGradient *convertGradient(const QGradient * originalGradient, const QSizeF &size);
+ static QGradient *convertGradient(const QGradient * originalGradient, const QTransform &userToRelativeTransform, const QRectF &size);
-private:
+ QGradient::Spread spreadMode() const;
+ void setSpreadMode(const QGradient::Spread &spreadMode);
- /// Duplicates the given gradient and applies the given transformation
- static QGradient *duplicateGradient(const QGradient * g, const QTransform &transform);
+private:
QGradient * m_gradient;
- Units m_gradientUnits;
+ KoFlake::CoordinateSystem m_gradientUnits;
QTransform m_gradientTransform;
+ QGradient::Spread m_spreadMode;
};
#endif // SVGGRADIENTHELPER_H
diff --git a/libs/flake/svg/SvgGraphicContext.cpp b/libs/flake/svg/SvgGraphicContext.cpp
index 6c4ab683d5..7dcc5b87ec 100644
--- a/libs/flake/svg/SvgGraphicContext.cpp
+++ b/libs/flake/svg/SvgGraphicContext.cpp
@@ -1,47 +1,84 @@
/* This file is part of the KDE project
* Copyright (C) 2003,2005 Rob Buis <buis@kde.org>
* 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 "SvgGraphicContext.h"
+#include "kis_pointer_utils.h"
+
+
SvgGraphicsContext::SvgGraphicsContext()
{
strokeType = None;
- stroke.setLineStyle(Qt::NoPen, QVector<qreal>()); // default is no stroke
- stroke.setLineWidth(1.0);
- stroke.setCapStyle(Qt::FlatCap);
- stroke.setJoinStyle(Qt::MiterJoin);
+
+ stroke = toQShared(new KoShapeStroke());
+ stroke->setLineStyle(Qt::NoPen, QVector<qreal>()); // default is no stroke
+ stroke->setLineWidth(1.0);
+ stroke->setCapStyle(Qt::FlatCap);
+ stroke->setJoinStyle(Qt::MiterJoin);
fillType = Solid;
fillRule = Qt::WindingFill;
fillColor = QColor(Qt::black); // default is black fill as per svg spec
opacity = 1.0;
currentColor = Qt::black;
forcePercentage = false;
display = true;
+ visible = true;
clipRule = Qt::WindingFill;
preserveWhitespace = false;
letterSpacing = 0.0;
wordSpacing = 0.0;
+ pixelsPerInch = 72.0;
+
+ autoFillMarkers = false;
+}
+
+void SvgGraphicsContext::workaroundClearInheritedFillProperties()
+{
+ /**
+ * HACK ALERT: according to SVG patterns, clip paths and clip masks
+ * must not inherit any properties from the referencing element.
+ * We still don't support it, therefore we reset only fill/stroke
+ * properties to avoid cyclic fill inheritance, which may cause
+ * infinite recursion.
+ */
+
+
+ strokeType = None;
+
+ stroke = toQShared(new KoShapeStroke());
+ stroke->setLineStyle(Qt::NoPen, QVector<qreal>()); // default is no stroke
+ stroke->setLineWidth(1.0);
+ stroke->setCapStyle(Qt::FlatCap);
+ stroke->setJoinStyle(Qt::MiterJoin);
+
+ fillType = Solid;
+ fillRule = Qt::WindingFill;
+ fillColor = QColor(Qt::black); // default is black fill as per svg spec
+
+ opacity = 1.0;
+
+ currentColor = Qt::black;
}
diff --git a/libs/flake/svg/SvgGraphicContext.h b/libs/flake/svg/SvgGraphicContext.h
index d83eb0080b..0a0dfc762e 100644
--- a/libs/flake/svg/SvgGraphicContext.h
+++ b/libs/flake/svg/SvgGraphicContext.h
@@ -1,72 +1,82 @@
/* This file is part of the KDE project
* Copyright (C) 2003,2005 Rob Buis <buis@kde.org>
* 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.
*/
#ifndef SVGGRAPHICCONTEXT_H
#define SVGGRAPHICCONTEXT_H
#include "kritaflake_export.h"
#include <KoShapeStroke.h>
#include <QFont>
#include <QTransform>
class KRITAFLAKE_EXPORT SvgGraphicsContext
{
public:
// Fill/stroke styles
enum StyleType {
None, ///< no style
Solid, ///< solid style
Complex ///< gradient or pattern style
};
SvgGraphicsContext();
+ void workaroundClearInheritedFillProperties();
StyleType fillType; ///< the current fill type
Qt::FillRule fillRule; ///< the current fill rule
QColor fillColor; ///< the current fill color
QString fillId; ///< the current fill id (used for gradient/pattern fills)
StyleType strokeType;///< the current stroke type
QString strokeId; ///< the current stroke id (used for gradient strokes)
- KoShapeStroke stroke; ///< the current stroke
+ KoShapeStrokeSP stroke; ///< the current stroke
QString filterId; ///< the current filter id
QString clipPathId; ///< the current clip path id
+ QString clipMaskId; ///< the current clip mask id
Qt::FillRule clipRule; ///< the current clip rule
qreal opacity; ///< the shapes opacity
QTransform matrix; ///< the current transformation matrix
QFont font; ///< the current font
QColor currentColor; ///< the current color
QString xmlBaseDir; ///< the current base directory (used for loading external content)
bool preserveWhitespace;///< preserve whitespace in element text
- QRectF currentBoundbox; ///< the current bound box used for bounding box units
+ QRectF currentBoundingBox; ///< the current bound box used for bounding box units
bool forcePercentage; ///< force parsing coordinates/length as percentages of currentBoundbox
QTransform viewboxTransform; ///< view box transformation
qreal letterSpacing; ///< additional spacing between characters of text elements
qreal wordSpacing; ///< additional spacing between words of text elements
QString baselineShift; ///< basline shift mode for text elements
bool display; ///< controls display of shape
+ bool visible; ///< controls visibility of the shape (inherited)
+ qreal pixelsPerInch; ///< controls the resolution of the image raster
+
+ QString markerStartId;
+ QString markerMidId;
+ QString markerEndId;
+
+ bool autoFillMarkers;
};
#endif // SVGGRAPHICCONTEXT_H
diff --git a/libs/flake/svg/SvgLoadingContext.cpp b/libs/flake/svg/SvgLoadingContext.cpp
index 2b1d771956..4835010322 100644
--- a/libs/flake/svg/SvgLoadingContext.cpp
+++ b/libs/flake/svg/SvgLoadingContext.cpp
@@ -1,209 +1,299 @@
/* 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 "SvgLoadingContext.h"
#include <QStack>
#include <QFileInfo>
#include <QDir>
+#include <KoColorSpaceRegistry.h>
+#include <KoColorSpaceEngine.h>
+#include <KoColorProfile.h>
#include <KoDocumentResourceManager.h>
#include <FlakeDebug.h>
#include "SvgGraphicContext.h"
#include "SvgUtil.h"
#include "SvgCssHelper.h"
#include "SvgStyleParser.h"
+#include "kis_debug.h"
class Q_DECL_HIDDEN SvgLoadingContext::Private
{
public:
Private()
: zIndex(0), styleParser(0)
{
}
~Private()
{
if (! gcStack.isEmpty())
warnFlake << "the context stack is not empty (current count" << gcStack.size() << ", expected 0)";
qDeleteAll(gcStack);
gcStack.clear();
delete styleParser;
}
QStack<SvgGraphicsContext*> gcStack;
QString initialXmlBaseDir;
int zIndex;
KoDocumentResourceManager *documentResourceManager;
QHash<QString, KoShape*> loadedShapes;
QHash<QString, KoXmlElement> definitions;
+ QHash<QString, const KoColorProfile*> profiles;
SvgCssHelper cssStyles;
SvgStyleParser *styleParser;
+ FileFetcherFunc fileFetcher;
};
SvgLoadingContext::SvgLoadingContext(KoDocumentResourceManager *documentResourceManager)
: d(new Private())
{
d->documentResourceManager = documentResourceManager;
d->styleParser = new SvgStyleParser(*this);
Q_ASSERT(d->documentResourceManager);
}
SvgLoadingContext::~SvgLoadingContext()
{
delete d;
}
SvgGraphicsContext *SvgLoadingContext::currentGC() const
{
if (d->gcStack.isEmpty())
return 0;
return d->gcStack.top();
}
+#include "parsers/SvgTransformParser.h"
+
SvgGraphicsContext *SvgLoadingContext::pushGraphicsContext(const KoXmlElement &element, bool inherit)
{
SvgGraphicsContext *gc = new SvgGraphicsContext;
// copy data from current context
if (! d->gcStack.isEmpty() && inherit)
*gc = *(d->gcStack.top());
gc->filterId.clear(); // filters are not inherited
gc->clipPathId.clear(); // clip paths are not inherited
+ gc->clipMaskId.clear(); // clip masks are not inherited
gc->display = true; // display is not inherited
gc->opacity = 1.0; // opacity is not inherited
gc->baselineShift.clear(); // baseline-shift is not inherited
if (!element.isNull()) {
if (element.hasAttribute("transform")) {
- QTransform mat = SvgUtil::parseTransform(element.attribute("transform"));
- gc->matrix = mat * gc->matrix;
+ SvgTransformParser p(element.attribute("transform"));
+ if (p.isValid()) {
+ QTransform mat = p.transform();
+ gc->matrix = mat * gc->matrix;
+ }
}
if (element.hasAttribute("xml:base"))
gc->xmlBaseDir = element.attribute("xml:base");
if (element.hasAttribute("xml:space"))
gc->preserveWhitespace = element.attribute("xml:space") == "preserve";
}
d->gcStack.push(gc);
return gc;
}
void SvgLoadingContext::popGraphicsContext()
{
delete(d->gcStack.pop());
}
void SvgLoadingContext::setInitialXmlBaseDir(const QString &baseDir)
{
d->initialXmlBaseDir = baseDir;
}
QString SvgLoadingContext::xmlBaseDir() const
{
SvgGraphicsContext *gc = currentGC();
return (gc && !gc->xmlBaseDir.isEmpty()) ? gc->xmlBaseDir : d->initialXmlBaseDir;
}
QString SvgLoadingContext::absoluteFilePath(const QString &href)
{
QFileInfo info(href);
if (! info.isRelative())
return href;
SvgGraphicsContext *gc = currentGC();
if (!gc)
return d->initialXmlBaseDir;
QString baseDir = d->initialXmlBaseDir;
if (! gc->xmlBaseDir.isEmpty())
baseDir = absoluteFilePath(gc->xmlBaseDir);
QFileInfo pathInfo(QFileInfo(baseDir).filePath());
QString relFile = href;
while (relFile.startsWith(QLatin1String("../"))) {
relFile.remove(0, 3);
pathInfo.setFile(pathInfo.dir(), QString());
}
QString absFile = pathInfo.absolutePath() + '/' + relFile;
return absFile;
}
+QString SvgLoadingContext::relativeFilePath(const QString &href)
+{
+ const SvgGraphicsContext *gc = currentGC();
+ if (!gc) return href;
+
+ QString result = href;
+
+ if (!gc->xmlBaseDir.isEmpty()) {
+ result = gc->xmlBaseDir + QDir::separator() + href;
+ } else if (!d->initialXmlBaseDir.isEmpty()) {
+ result = d->initialXmlBaseDir + QDir::separator() + href;
+ }
+
+ return QDir::cleanPath(result);
+}
+
int SvgLoadingContext::nextZIndex()
{
return d->zIndex++;
}
KoImageCollection* SvgLoadingContext::imageCollection()
{
return d->documentResourceManager->imageCollection();
}
void SvgLoadingContext::registerShape(const QString &id, KoShape *shape)
{
if (!id.isEmpty())
d->loadedShapes.insert(id, shape);
}
KoShape* SvgLoadingContext::shapeById(const QString &id)
{
return d->loadedShapes.value(id);
}
void SvgLoadingContext::addDefinition(const KoXmlElement &element)
{
const QString id = element.attribute("id");
if (id.isEmpty() || d->definitions.contains(id))
return;
d->definitions.insert(id, element);
}
KoXmlElement SvgLoadingContext::definition(const QString &id) const
{
return d->definitions.value(id);
}
bool SvgLoadingContext::hasDefinition(const QString &id) const
{
return d->definitions.contains(id);
}
void SvgLoadingContext::addStyleSheet(const KoXmlElement &styleSheet)
{
d->cssStyles.parseStylesheet(styleSheet);
}
-QStringList SvgLoadingContext::matchingStyles(const KoXmlElement &element) const
+QStringList SvgLoadingContext::matchingCssStyles(const KoXmlElement &element) const
{
return d->cssStyles.matchStyles(element);
}
SvgStyleParser &SvgLoadingContext::styleParser()
{
return *d->styleParser;
}
+
+void SvgLoadingContext::parseProfile(const KoXmlElement &element)
+{
+ const QString href = element.attribute("xlink:href");
+ const QByteArray uniqueId = QByteArray::fromHex(element.attribute("local").toLatin1());
+ const QString name = element.attribute("name");
+
+ if (element.attribute("rendering-intent", "auto") != "auto") {
+ // WARNING: Krita does *not* treat rendering intents attributes of the profile!
+ qDebug() << "WARNING: we do *not* treat rendering intents attributes of the profile!";
+ }
+
+ if (d->profiles.contains(name)) {
+ qDebug() << "Profile already in the map!" << ppVar(name);
+ return;
+ }
+
+ const KoColorProfile *profile =
+ KoColorSpaceRegistry::instance()->profileByUniqueId(uniqueId);
+
+ if (!profile && d->fileFetcher) {
+ KoColorSpaceEngine *engine = KoColorSpaceEngineRegistry::instance()->get("icc");
+ KIS_ASSERT(engine);
+ if (engine) {
+ const QString fileName = relativeFilePath(href);
+ const QByteArray profileData = d->fileFetcher(fileName);
+ if (!profileData.isEmpty()) {
+ profile = engine->addProfile(profileData);
+
+ if (profile->uniqueId() != uniqueId) {
+ qDebug() << "WARNING: ProfileID of the attached profile doesn't match the one mentioned in SVG element";
+ qDebug() << " " << ppVar(profile->uniqueId().toHex());
+ qDebug() << " " << ppVar(uniqueId.toHex());
+ }
+ } else {
+ qDebug() << "WARNING: couldn't fetch the ICCprofile file!" << fileName;
+ }
+ }
+ }
+
+ if (profile) {
+ d->profiles.insert(name, profile);
+ } else {
+ qDebug() << "WARNING: couldn't load SVG profile" << ppVar(name) << ppVar(href) << ppVar(uniqueId);
+ }
+}
+
+bool SvgLoadingContext::isRootContext() const
+{
+ KIS_ASSERT(!d->gcStack.isEmpty());
+ return d->gcStack.size() == 1;
+}
+
+void SvgLoadingContext::setFileFetcher(SvgLoadingContext::FileFetcherFunc func)
+{
+ d->fileFetcher = func;
+}
+
+QByteArray SvgLoadingContext::fetchExternalFile(const QString &url)
+{
+ return d->fileFetcher ? d->fileFetcher(url) : QByteArray();
+}
diff --git a/libs/flake/svg/SvgLoadingContext.h b/libs/flake/svg/SvgLoadingContext.h
index 3ec7aaa8d3..f411361536 100644
--- a/libs/flake/svg/SvgLoadingContext.h
+++ b/libs/flake/svg/SvgLoadingContext.h
@@ -1,93 +1,106 @@
/* 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 SVGLOADINGCONTEXT_H
#define SVGLOADINGCONTEXT_H
+#include <functional>
#include <QStringList>
#include <KoXmlReader.h>
#include "kritaflake_export.h"
class SvgGraphicsContext;
class SvgStyleParser;
class KoDocumentResourceManager;
class KoImageCollection;
class KoShape;
/// Contains data used for loading svg
class KRITAFLAKE_EXPORT SvgLoadingContext
{
public:
explicit SvgLoadingContext(KoDocumentResourceManager *documentResourceManager);
~SvgLoadingContext();
/// Returns the current graphics context
SvgGraphicsContext *currentGC() const;
/// Pushes a new graphics context to the stack
SvgGraphicsContext *pushGraphicsContext(const KoXmlElement &element = KoXmlElement(), bool inherit = true);
/// Pops the current graphics context from the stack
void popGraphicsContext();
/// Sets the initial xml base dir, i.e. the directory the svg file is read from
void setInitialXmlBaseDir(const QString &baseDir);
/// Returns the current xml base dir
QString xmlBaseDir() const;
/// Constructs an absolute file path from the given href and current xml base directory
QString absoluteFilePath(const QString &href);
+ QString relativeFilePath(const QString &href);
+
/// Returns the next z-index
int nextZIndex();
/// Returns the image collection used for managing images
KoImageCollection* imageCollection();
/// Registers a shape so it can be referenced later
void registerShape(const QString &id, KoShape *shape);
/// Returns shape with specified id
KoShape* shapeById(const QString &id);
/// Adds a definition for later use
void addDefinition(const KoXmlElement &element);
/// Returns the definition with the specified id
KoXmlElement definition(const QString &id) const;
/// Checks if a definition with the specified id exists
bool hasDefinition(const QString &id) const;
/// Adds a css style sheet
void addStyleSheet(const KoXmlElement &styleSheet);
/// Returns list of css styles matching to the specified element
- QStringList matchingStyles(const KoXmlElement &element) const;
+ QStringList matchingCssStyles(const KoXmlElement &element) const;
/// Returns a style parser to parse styles
SvgStyleParser &styleParser();
+ /// parses 'color-profile' tag and saves it in the context
+ void parseProfile(const KoXmlElement &element);
+
+ bool isRootContext() const;
+
+ typedef std::function<QByteArray(const QString&)> FileFetcherFunc;
+ void setFileFetcher(FileFetcherFunc func);
+
+ QByteArray fetchExternalFile(const QString &url);
+
private:
class Private;
Private * const d;
};
#endif // SVGLOADINGCONTEXT_H
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index 9e7412ef5b..970730f12f 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1,1329 +1,1544 @@
/* 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 <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 "SvgUtil.h"
#include "SvgShape.h"
#include "SvgGraphicContext.h"
-#include "SvgPatternHelper.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 "kis_debug.h"
+#include "kis_global.h"
SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager)
: m_context(documentResourceManager)
, m_documentResourceManager(documentResourceManager)
{
}
SvgParser::~SvgParser()
{
}
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);
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), 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()->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;
}
QList<KoShape*> SvgParser::shapes() const
{
return m_shapes;
}
// Helper functions
// ---------------------------------------------------------------------------------------
-SvgGradientHelper* SvgParser::findGradient(const QString &id, const QString &href)
+SvgGradientHelper* SvgParser::findGradient(const QString &id)
{
+ SvgGradientHelper *result = 0;
+
// check if gradient was already parsed, and return it
- if (m_gradients.contains(id))
- return &m_gradients[ id ];
+ if (m_gradients.contains(id)) {
+ result = &m_gradients[ id ];
+ }
// check if gradient was stored for later parsing
- if (!m_context.hasDefinition(id))
- return 0;
-
- const KoXmlElement &e = m_context.definition(id);
- if (!e.tagName().contains("Gradient"))
- return 0;
-
- if (e.childNodesCount() == 0) {
- QString mhref = e.attribute("xlink:href").mid(1);
-
- if (m_context.hasDefinition(mhref))
- return findGradient(mhref, id);
- else
- return 0;
- } else {
- // ok parse gradient now
- if (! parseGradient(m_context.definition(id), m_context.definition(href)))
- return 0;
+ 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 successfully parsed gradient or 0
- QString n;
- if (href.isEmpty())
- n = id;
- else
- n = href;
-
- if (m_gradients.contains(n))
- return &m_gradients[ n ];
- else
- return 0;
+ return result;
}
-SvgPatternHelper* SvgParser::findPattern(const QString &id)
+QSharedPointer<KoVectorPatternBackground> SvgParser::findPattern(const QString &id, const KoShape *shape)
{
- // check if pattern was already parsed, and return it
- if (m_patterns.contains(id))
- return &m_patterns[ id ];
-
- // check if pattern was stored for later parsing
- if (!m_context.hasDefinition(id))
- return 0;
-
- SvgPatternHelper pattern;
-
- const KoXmlElement &e = m_context.definition(id);
- if (e.tagName() != "pattern")
- return 0;
+ QSharedPointer<KoVectorPatternBackground> result;
- // are we referencing another pattern ?
- if (e.hasAttribute("xlink:href")) {
- QString mhref = e.attribute("xlink:href").mid(1);
- SvgPatternHelper *refPattern = findPattern(mhref);
- // inherit attributes of referenced pattern
- if (refPattern)
- pattern = *refPattern;
+ // 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);
+ }
}
- // ok parse pattern now
- parsePattern(pattern, m_context.definition(id));
- // add to parsed pattern list
- m_patterns.insert(id, pattern);
-
- return &m_patterns[ id ];
+ 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 (e.childNodesCount() == 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, const QString &href)
+SvgClipPathHelper* SvgParser::findClipPath(const QString &id)
{
- // check if clip path was already parsed, and return it
- if (m_clipPaths.contains(id))
- return &m_clipPaths[ id ];
-
- // check if clip path was stored for later parsing
- if (!m_context.hasDefinition(id))
- return 0;
-
- const KoXmlElement &e = m_context.definition(id);
- if (e.childNodesCount() == 0) {
- QString mhref = e.attribute("xlink:href").mid(1);
-
- if (m_context.hasDefinition(mhref))
- return findClipPath(mhref, id);
- else
- return 0;
- } else {
- // ok clip path filter now
- if (! parseClipPath(m_context.definition(id), m_context.definition(href)))
- return 0;
- }
-
- // return successfully parsed clip path or 0
- const QString n = href.isEmpty() ? id : href;
- if (m_clipPaths.contains(n))
- return &m_clipPaths[ n ];
- else
- return 0;
+ 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);
}
-bool SvgParser::parseGradient(const KoXmlElement &e, const KoXmlElement &referencedBy)
+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 false;
+ if (!gc) return 0;
+
+ SvgGradientHelper gradHelper;
- 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()) {
+
+ if (!href.isEmpty()) {
// copy the referenced gradient if found
SvgGradientHelper *pGrad = findGradient(href);
- if (pGrad)
- gradhelper = *pGrad;
- } else {
- //gc->fillType = SvgGraphicsContext::None; // <--- TODO Fill OR Stroke are none
- return false;
+ if (pGrad) {
+ gradHelper = *pGrad;
+ }
}
}
- // Use the gradient that is referencing, or if there isn't one, the original gradient.
- KoXmlElement b;
- if (!referencedBy.isNull())
- b = referencedBy;
- else
- b = e;
-
- QString gradientId = b.attribute("id");
+ const QGradientStops defaultStops = gradHelper.gradient()->stops();
- if (! gradientId.isEmpty()) {
- // check if we have this gradient already parsed
- // copy existing gradient if it exists
- if (m_gradients.find(gradientId) != m_gradients.end())
- gradhelper.copyGradient(m_gradients[ gradientId ].gradient());
+ if (e.attribute("gradientUnits") == "userSpaceOnUse") {
+ gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse);
}
- if (b.attribute("gradientUnits") == "userSpaceOnUse")
- gradhelper.setGradientUnits(SvgGradientHelper::UserSpaceOnUse);
+ m_context.pushGraphicsContext(e);
+ uploadStyleToContext(e);
- // parse color prop
- QColor c = gc->currentColor;
-
- if (!b.attribute("color").isEmpty()) {
- m_context.styleParser().parseColor(c, b.attribute("color"));
- } else {
- // try style attr
- QString style = b.attribute("style").simplified();
- QStringList substyles = style.split(';', QString::SkipEmptyParts);
- for (QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it) {
- QStringList substyle = it->split(':');
- QString command = substyle[0].trimmed();
- QString params = substyle[1].trimmed();
- if (command == "color")
- m_context.styleParser().parseColor(c, params);
- }
- }
- gc->currentColor = c;
-
- if (b.tagName() == "linearGradient") {
+ if (e.tagName() == "linearGradient") {
QLinearGradient *g = new QLinearGradient();
- if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) {
+ if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
- g->setStart(QPointF(SvgUtil::fromPercentage(b.attribute("x1", "0%")),
- SvgUtil::fromPercentage(b.attribute("y1", "0%"))));
- g->setFinalStop(QPointF(SvgUtil::fromPercentage(b.attribute("x2", "100%")),
- SvgUtil::fromPercentage(b.attribute("y2", "0%"))));
+ 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(SvgUtil::fromUserSpace(b.attribute("x1").toDouble()),
- SvgUtil::fromUserSpace(b.attribute("y1").toDouble())));
- g->setFinalStop(QPointF(SvgUtil::fromUserSpace(b.attribute("x2").toDouble()),
- SvgUtil::fromUserSpace(b.attribute("y2").toDouble())));
+ g->setStart(QPointF(parseUnitX(e.attribute("x1")),
+ parseUnitY(e.attribute("y1"))));
+ g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
+ parseUnitY(e.attribute("y2"))));
}
- // preserve color stops
- if (gradhelper.gradient())
- g->setStops(gradhelper.gradient()->stops());
- gradhelper.setGradient(g);
- } else if (b.tagName() == "radialGradient") {
+ gradHelper.setGradient(g);
+
+ } else if (e.tagName() == "radialGradient") {
QRadialGradient *g = new QRadialGradient();
- if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) {
+ if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
- g->setCenter(QPointF(SvgUtil::fromPercentage(b.attribute("cx", "50%")),
- SvgUtil::fromPercentage(b.attribute("cy", "50%"))));
- g->setRadius(SvgUtil::fromPercentage(b.attribute("r", "50%")));
- g->setFocalPoint(QPointF(SvgUtil::fromPercentage(b.attribute("fx", "50%")),
- SvgUtil::fromPercentage(b.attribute("fy", "50%"))));
+ 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(SvgUtil::fromUserSpace(b.attribute("cx").toDouble()),
- SvgUtil::fromUserSpace(b.attribute("cy").toDouble())));
- g->setFocalPoint(QPointF(SvgUtil::fromUserSpace(b.attribute("fx").toDouble()),
- SvgUtil::fromUserSpace(b.attribute("fy").toDouble())));
- g->setRadius(SvgUtil::fromUserSpace(b.attribute("r").toDouble()));
+ 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")));
}
- // preserve color stops
- if (gradhelper.gradient())
- g->setStops(gradhelper.gradient()->stops());
- gradhelper.setGradient(g);
+ gradHelper.setGradient(g);
} else {
- return false;
+ qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName();
}
// handle spread method
- QString spreadMethod = b.attribute("spreadMethod");
- if (!spreadMethod.isEmpty()) {
- if (spreadMethod == "reflect")
- gradhelper.gradient()->setSpread(QGradient::ReflectSpread);
- else if (spreadMethod == "repeat")
- gradhelper.gradient()->setSpread(QGradient::RepeatSpread);
- else
- gradhelper.gradient()->setSpread(QGradient::PadSpread);
- } else
- gradhelper.gradient()->setSpread(QGradient::PadSpread);
+ 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;
+ }
+ }
- // Parse the color stops. The referencing gradient does not have colorstops,
- // so use the stops from the gradient it references to (e in this case and not b)
- m_context.styleParser().parseColorStops(gradhelper.gradient(), e);
- gradhelper.setTransform(SvgUtil::parseTransform(b.attribute("gradientTransform")));
- m_gradients.insert(gradientId, gradhelper);
+ gradHelper.setSpreadMode(spreadMethod);
- return true;
+ // 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];
}
-void SvgParser::parsePattern(SvgPatternHelper &pattern, const KoXmlElement &e)
+inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
{
- if (e.attribute("patternUnits") == "userSpaceOnUse") {
- pattern.setPatternUnits(SvgPatternHelper::UserSpaceOnUse);
+ 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;
+ }
+ }
}
- if (e.attribute("patternContentUnits") == "objectBoundingBox") {
- pattern.setPatternContentUnits(SvgPatternHelper::ObjectBoundingBox);
+
+ 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());
+ }
}
- const QString viewBox = e.attribute("viewBox");
- if (!viewBox.isEmpty()) {
- pattern.setPatternContentViewbox(SvgUtil::parseViewBox(viewBox));
+
+ 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);
}
- const QString transform = e.attribute("patternTransform");
- if (!transform.isEmpty()) {
- pattern.setTransform(SvgUtil::parseTransform(transform));
+
+ /**
+ * In Krita shapes X,Y coordinates are baked into the the shape global transform, but
+ * the pattern should be painted in "user" coordinates. Therefore, we should handle
+ * this offfset separately.
+ *
+ * TODO: Please also not 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 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;
}
- const QString x = e.attribute("x");
- const QString y = e.attribute("y");
- const QString w = e.attribute("width");
- const QString h = e.attribute("height");
- // parse tile reference rectangle
- if (pattern.patternUnits() == SvgPatternHelper::UserSpaceOnUse) {
- if (!x.isEmpty() && !y.isEmpty()) {
- pattern.setPosition(QPointF(parseUnitX(x), parseUnitY(y)));
- }
- if (!w.isEmpty() && !h.isEmpty()) {
- pattern.setSize(QSizeF(parseUnitX(w), parseUnitY(h)));
- }
- } else {
- // x, y, width, height are in percentages of the object referencing the pattern
- // so we just parse the percentages
- if (!x.isEmpty() && !y.isEmpty()) {
- pattern.setPosition(QPointF(SvgUtil::fromPercentage(x), SvgUtil::fromPercentage(y)));
- }
- if (!w.isEmpty() && !h.isEmpty()) {
- pattern.setSize(QSizeF(SvgUtil::fromPercentage(w), SvgUtil::fromPercentage(h)));
+ // 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 (e.hasChildNodes()) {
- pattern.setContent(e);
+ 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(SvgFilterHelper::UserSpaceOnUse);
+ filter.setFilterUnits(KoFlake::UserSpaceOnUse);
if (b.attribute("primitiveUnits") == "objectBoundingBox")
- filter.setPrimitiveUnits(SvgFilterHelper::ObjectBoundingBox);
+ filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox);
// parse filter region rectangle
- if (filter.filterUnits() == SvgFilterHelper::UserSpaceOnUse) {
+ 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::parseClipPath(const KoXmlElement &e, const KoXmlElement &referencedBy)
+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::parseClipPath(const KoXmlElement &e)
{
SvgClipPathHelper clipPath;
- // 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;
+ const QString id = e.attribute("id");
+ if (id.isEmpty()) return false;
- // check if we are referencing another clip path
- if (e.hasAttribute("xlink:href")) {
- QString href = e.attribute("xlink:href").mid(1);
- if (! href.isEmpty()) {
- // copy the referenced clip path if found
- SvgClipPathHelper *refClipPath = findClipPath(href);
- if (refClipPath)
- clipPath = *refClipPath;
- }
+ 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 {
- clipPath.setContent(b);
+ 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%")));
}
- if (b.attribute("clipPathUnits") == "objectBoundingBox")
- clipPath.setClipPathUnits(SvgClipPathHelper::ObjectBoundingBox);
+ 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_clipPaths.insert(b.attribute("id"), clipPath);
+ m_context.popGraphicsContext();
+
+ if (!clipShape) return false;
+ clipMask->setShapes({clipShape});
+ m_clipMasks.insert(id, clipMask);
return true;
}
-void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e)
+void SvgParser::uploadStyleToContext(const KoXmlElement &e)
{
- applyStyle(obj, m_context.styleParser().collectStyles(e));
+ SvgStyles styles = m_context.styleParser().collectStyles(e);
+ m_context.styleParser().parseFont(styles);
+ m_context.styleParser().parseStyle(styles);
}
-void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles)
+void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
+{
+ if (!shape) return;
+
+ SvgGraphicsContext *gc = m_context.currentGC();
+ KIS_ASSERT(gc);
+
+ if (!dynamic_cast<KoShapeGroup*>(shape)) {
+ applyFillStyle(shape);
+ applyStrokeStyle(shape);
+ }
+
+ if (KoPathShape *pathShape = dynamic_cast<KoPathShape*>(shape)) {
+ applyMarkers(pathShape);
+ }
+
+ applyFilter(shape);
+ applyClipping(shape, shapeToOriginalUserCoordinates);
+ applyMaskClipping(shape, shapeToOriginalUserCoordinates);
+
+ 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);
+ applyClipping(obj, shapeToOriginalUserCoordinates);
+ applyMaskClipping(obj, shapeToOriginalUserCoordinates);
- if (! gc->display)
+ 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();
+ }
+ }
+
+ 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) {
- // great, we have a gradient fill
- QSharedPointer<KoGradientBackground> bg;
- if (gradient->gradientUnits() == SvgGradientHelper::ObjectBoundingBox) {
- bg = QSharedPointer<KoGradientBackground>(new KoGradientBackground(*gradient->gradient()));
- bg->setTransform(gradient->transform());
- } else {
- QGradient *convertedGradient = SvgGradientHelper::convertGradient(gradient->gradient(), shape->size());
- bg = QSharedPointer<KoGradientBackground>(new KoGradientBackground(convertedGradient));
- QTransform invShapematrix = shape->transformation().inverted();
- bg->setTransform(gradient->transform() * gc->matrix * invShapematrix);
+ 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);
}
- shape->setBackground(bg);
} else {
- // try to find referenced pattern
- SvgPatternHelper *pattern = findPattern(gc->fillId);
- KoImageCollection *imageCollection = m_documentResourceManager->imageCollection();
- if (pattern && imageCollection) {
- // great we have a pattern fill
- QRectF objectBound = QRectF(QPoint(), shape->size());
- QRectF currentBoundbox = gc->currentBoundbox;
-
- // properties from the object are not inherited
- // so we are creating a new context without copying
- SvgGraphicsContext *gc = m_context.pushGraphicsContext(pattern->content(), false);
-
- // the pattern establishes a new coordinate system with its
- // origin at the patterns x and y attributes
- gc->matrix = QTransform();
- // object bounding box units are relative to the object the pattern is applied
- if (pattern->patternContentUnits() == SvgPatternHelper::ObjectBoundingBox) {
- gc->currentBoundbox = objectBound;
- gc->forcePercentage = true;
- } else {
- // inherit the current bounding box
- gc->currentBoundbox = currentBoundbox;
- }
-
- applyStyle(0, pattern->content());
-
- // parse the pattern content elements
- QList<KoShape*> patternContent = parseContainer(pattern->content());
-
- // generate the pattern image from the shapes and the object bounding rect
- QImage image = pattern->generateImage(objectBound, patternContent);
-
- m_context.popGraphicsContext();
-
- // delete the shapes created from the pattern content
- qDeleteAll(patternContent);
+ QSharedPointer<KoVectorPatternBackground> pattern =
+ findPattern(gc->fillId, shape);
- if (!image.isNull()) {
- QSharedPointer<KoPatternBackground> bg(new KoPatternBackground(imageCollection));
- bg->setPattern(image);
-
- QPointF refPoint = shape->documentToShape(pattern->position(objectBound));
- QSizeF tileSize = pattern->size(objectBound);
-
- bg->setPatternDisplaySize(tileSize);
- if (pattern->patternUnits() == SvgPatternHelper::ObjectBoundingBox) {
- if (tileSize == objectBound.size())
- bg->setRepeat(KoPatternBackground::Stretched);
- }
-
- // calculate pattern reference point offset in percent of tileSize
- // and relative to the topleft corner of the shape
- qreal fx = refPoint.x() / tileSize.width();
- qreal fy = refPoint.y() / tileSize.height();
- if (fx < 0.0)
- fx = std::floor(fx);
- else if (fx > 1.0)
- fx = std::ceil(fx);
- else
- fx = 0.0;
- if (fy < 0.0)
- fy = std::floor(fy);
- else if (fx > 1.0)
- fy = std::ceil(fy);
- else
- fy = 0.0;
- qreal offsetX = 100.0 * (refPoint.x() - fx * tileSize.width()) / tileSize.width();
- qreal offsetY = 100.0 * (refPoint.y() - fy * tileSize.height()) / tileSize.height();
- bg->setReferencePointOffset(QPointF(offsetX, offsetY));
-
- shape->setBackground(bg);
- }
+ 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(0);
+ shape->setStroke(KoShapeStrokeModelSP());
} else if (gc->strokeType == SvgGraphicsContext::Solid) {
- double lineWidth = gc->stroke.lineWidth();
- QVector<qreal> dashes = gc->stroke.lineDashes();
-
- KoShapeStroke *stroke = new KoShapeStroke(gc->stroke);
-
- // apply line width to dashes and dash offset
- if (dashes.count() && lineWidth > 0.0) {
- QVector<qreal> dashes = stroke->lineDashes();
- for (int i = 0; i < dashes.count(); ++i)
- dashes[i] /= lineWidth;
- double dashOffset = stroke->dashOffset();
- stroke->setLineStyle(Qt::CustomDashLine, dashes);
- stroke->setDashOffset(dashOffset / lineWidth);
- } else {
- stroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
- }
+ 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) {
- // great, we have a gradient stroke
- QBrush brush;
- if (gradient->gradientUnits() == SvgGradientHelper::ObjectBoundingBox) {
- brush = *gradient->gradient();
- brush.setTransform(gradient->transform());
- } else {
- QGradient *convertedGradient(SvgGradientHelper::convertGradient(gradient->gradient(), shape->size()));
- brush = *convertedGradient;
- delete convertedGradient;
- brush.setTransform(gradient->transform() * gc->matrix * shape->transformation().inverted());
+ 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);
}
- KoShapeStroke *stroke = new KoShapeStroke(gc->stroke);
- stroke->setLineBrush(brush);
- stroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
- shape->setStroke(stroke);
} else {
// no referenced stroke found, use fallback color
- KoShapeStroke *stroke = new KoShapeStroke(gc->stroke);
- stroke->setLineStyle(Qt::SolidLine, QVector<qreal>());
+ 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 tranformation 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() == SvgFilterHelper::UserSpaceOnUse);
- context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == SvgFilterHelper::UserSpaceOnUse);
+ 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() == SvgFilterHelper::UserSpaceOnUse) {
+ 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::applyClipping(KoShape *shape)
+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)
+ if (!clipPath || clipPath->isEmpty())
return;
- debugFlake << "applying clip path" << gc->clipPathId << "clip rule" << gc->clipRule;
-
- const bool boundingBoxUnits = clipPath->clipPathUnits() == SvgClipPathHelper::ObjectBoundingBox;
- debugFlake << "using" << (boundingBoxUnits ? "boundingBoxUnits" : "userSpaceOnUse");
+ QList<KoShape*> shapes;
- QTransform shapeMatrix = shape->absoluteTransformation(0);
- // TODO:
- // clip path element can have a clip-path property
- // -> clip-path = intersection of children with referenced clip-path
- // any of its children can have a clip-path property
- // -> child element is clipped and the ORed with other children
- m_context.pushGraphicsContext();
+ Q_FOREACH (KoShape *item, clipPath->shapes()) {
+ KoShape *clonedShape = item->cloneShape();
+ KIS_ASSERT_RECOVER(clonedShape) { continue; }
- if (boundingBoxUnits) {
- SvgGraphicsContext *gc = m_context.currentGC();
- gc->matrix.reset();
- gc->viewboxTransform.reset();
- gc->currentBoundbox = shape->boundingRect();
- gc->forcePercentage = true;
+ shapes.append(clonedShape);
}
- QList<KoShape*> clipShapes = parseContainer(clipPath->content());
- QList<KoPathShape*> pathShapes;
- while (!clipShapes.isEmpty()) {
- KoShape *clipShape = clipShapes.first();
- clipShapes.removeFirst();
- // remove clip shape from list of all parsed shapes
- m_shapes.removeOne(clipShape);
- // check if we have a path shape
- KoPathShape *path = dynamic_cast<KoPathShape*>(clipShape);
- if (!path) {
- // if shape is a group, ungroup and add children to lits of clip shapes
- KoShapeGroup *group = dynamic_cast<KoShapeGroup*>(clipShape);
- if (group) {
- QList<KoShape*> groupedShapes = group->shapes();
- KoShapeUngroupCommand cmd(group, groupedShapes);
- cmd.redo();
- clipShapes.append(groupedShapes);
- } else {
- // shape is not a group shape, use its outline as clip path
- QPainterPath outline = clipShape->absoluteTransformation(0).map(clipShape->outline());
- path = KoPathShape::createShapeFromPainterPath(outline);
- }
- delete clipShape;
- }
- if (path) {
- debugFlake << "using shape" << path->name() << "as clip path";
- pathShapes.append(path);
- if (boundingBoxUnits)
- path->applyAbsoluteTransformation(shapeMatrix);
+ if (!shapeToOriginalUserCoordinates.isNull()) {
+ const QTransform t =
+ QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
+ shapeToOriginalUserCoordinates.y());
+
+ Q_FOREACH(KoShape *s, shapes) {
+ s->applyAbsoluteTransformation(t);
}
}
- m_context.popGraphicsContext();
-
- if (pathShapes.count()) {
- QTransform transformToShape;
- if (!boundingBoxUnits)
- transformToShape = shape->absoluteTransformation(0).inverted();
- KoClipData *clipData = new KoClipData(pathShapes);
- KoClipPath *clipPath = new KoClipPath(shape, clipData);
- clipPath->setClipRule(gc->clipRule);
- shape->setClipPath(clipPath);
- }
+ KoClipPath *clipPathObject = new KoClipPath(shapes,
+ clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ?
+ KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse);
+ shape->setClipPath(clipPathObject);
}
-QList<KoShape*> SvgParser::parseUse(const KoXmlElement &e)
+void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
- QList<KoShape*> shapes;
+ SvgGraphicsContext *gc = m_context.currentGC();
+ if (!gc)
+ return;
- QString id = e.attribute("xlink:href");
- //
- if (!id.isEmpty()) {
- SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
+ if (gc->clipMaskId.isEmpty())
+ return;
- // TODO: use width and height attributes too
- gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
- QString key = id.mid(1);
+ QSharedPointer<KoClipMask> originalClipMask = m_clipMasks.value(gc->clipMaskId);
+ if (!originalClipMask || originalClipMask->isEmpty()) return;
- if (m_context.hasDefinition(key)) {
- const KoXmlElement &a = m_context.definition(key);
- SvgStyles styles = m_context.styleParser().mergeStyles(e, a);
- if (a.tagName() == "g" || a.tagName() == "a" || a.tagName() == "symbol") {
- m_context.pushGraphicsContext(a);
+ KoClipMask *clipMask = originalClipMask->clone();
- KoShapeGroup *group = new KoShapeGroup();
- group->setZIndex(m_context.nextZIndex());
+ clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
- applyStyle(0, styles);
- m_context.styleParser().parseFont(styles);
+ shape->setClipMask(clipMask);
+}
- QList<KoShape*> childShapes = parseContainer(a);
+KoShape* SvgParser::parseUse(const KoXmlElement &e)
+{
+ KoShape *result = 0;
- // handle id
- applyId(a.attribute("id"), group);
+ QString id = e.attribute("xlink:href");
+ if (!id.isEmpty()) {
- addToGroup(childShapes, group);
- applyStyle(group, styles); // apply style to group after size is set
+ QString key = id.mid(1);
+ if (m_context.hasDefinition(key)) {
- shapes.append(group);
+ SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
- m_context.popGraphicsContext();
- } else {
- // Create the object with the merged styles.
- // The object inherits all style attributes from the use tag, but keeps it's own attributes.
- // So, not just use the style attributes of the use tag, but merge them first.
- KoShape *shape = createObject(a, styles);
- if (shape)
- shapes.append(shape);
- }
- } else {
- // TODO: any named object can be referenced too
+ // TODO: parse 'width' and 'hight' 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);
+
+ m_context.popGraphicsContext();
}
- m_context.popGraphicsContext();
}
- return shapes;
+ return result;
}
void SvgParser::addToGroup(QList<KoShape*> shapes, KoShapeGroup *group)
{
m_shapes += shapes;
- if (! group)
+ if (!group || shapes.isEmpty())
return;
- KoShapeGroupCommand cmd(group, shapes);
+ // not clipped, but inherit transform
+ KoShapeGroupCommand cmd(group, shapes, false, true, 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.currentGC();
-
- SvgGraphicsContext *gc = m_context.pushGraphicsContext();
+ const bool isRootSvg = m_context.isRootContext();
- applyStyle(0, e);
+ // parse 'transform' field if preset
+ SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
- QRectF viewBox;
-
- const QString viewBoxStr = e.attribute("viewBox");
- if (!viewBoxStr.isEmpty()) {
- viewBox = SvgUtil::parseViewBox(viewBoxStr);
- }
+ applyStyle(0, e, QPointF());
const QString w = e.attribute("width");
const QString h = e.attribute("height");
- const qreal width = w.isEmpty() ? 550.0 : parseUnit(w, true, false, viewBox);
- const qreal height = h.isEmpty() ? 841.0 : parseUnit(h, false, true, viewBox);
+ const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
+ const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
QSizeF svgFragmentSize(QSizeF(width, height));
- if (fragmentSize)
+ if (fragmentSize) {
*fragmentSize = svgFragmentSize;
+ }
- gc->currentBoundbox = QRectF(QPointF(0, 0), svgFragmentSize);
+ gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
- if (! isRootSvg) {
- QTransform move;
+ 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"));
- move.translate(x, y);
+
+ QTransform move = QTransform::fromTranslate(x, y);
gc->matrix = move * gc->matrix;
- gc->viewboxTransform = move *gc->viewboxTransform;
}
- if (!viewBoxStr.isEmpty()) {
- QTransform viewTransform;
- viewTransform.translate(viewBox.x(), viewBox.y());
- viewTransform.scale(width / viewBox.width() , height / viewBox.height());
+ applyViewBoxTransform(e);
+
+ QList<KoShape*> shapes;
+
+ // SVG 1.1: skip the rendering of the element if it has null viewBox
+ if (gc->currentBoundingBox.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(gc, element, gc->currentBoundingBox,
+ &viewRect, &viewTransform)) {
+
gc->matrix = viewTransform * gc->matrix;
- gc->viewboxTransform = viewTransform *gc->viewboxTransform;
- gc->currentBoundbox.setWidth(gc->currentBoundbox.width() * (viewBox.width() / width));
- gc->currentBoundbox.setHeight(gc->currentBoundbox.height() * (viewBox.height() / height));
+ gc->currentBoundingBox = viewRect;
+ }
+}
+
+QList<QExplicitlySharedDataPointer<KoMarker> > SvgParser::knownMarkers() const
+{
+ return m_markers.values();
+}
+
+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() *
+ 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)
+{
+ 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);
+ } else {
+ childShapes = parseContainer(b);
}
- QList<KoShape*> shapes = parseContainer(e);
+ // 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();
- return shapes;
+ return group;
}
QList<KoShape*> SvgParser::parseContainer(const KoXmlElement &e)
{
QList<KoShape*> shapes;
// are we parsing a switch container
bool isSwitch = e.tagName() == "switch";
for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement b = n.toElement();
if (b.isNull())
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 implemeted yet
}
}
- if (b.tagName() == "svg") {
- shapes += parseSvg(b);
- } 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
- m_context.pushGraphicsContext(b);
-
- KoShapeGroup *group = new KoShapeGroup();
- group->setZIndex(m_context.nextZIndex());
-
- SvgStyles styles = m_context.styleParser().collectStyles(b);
- m_context.styleParser().parseFont(styles);
- applyStyle(0, styles); // parse style for inheritance
-
- QList<KoShape*> childShapes = parseContainer(b);
-
- // handle id
- applyId(b.attribute("id"), group);
-
- addToGroup(childShapes, group);
-
- const QString viewBoxStr = b.attribute("viewBox");
- if (!viewBoxStr.isEmpty()) {
- QRectF viewBox = SvgUtil::parseViewBox(viewBoxStr);
- QTransform viewTransform;
- viewTransform.translate(viewBox.x(), viewBox.y());
- viewTransform.scale(group->size().width() / viewBox.width() , group->size().height() / viewBox.height());
- group->applyAbsoluteTransformation(viewTransform);
- }
- applyStyle(group, styles); // apply style to this group after size is set
-
- shapes.append(group);
-
- m_context.popGraphicsContext();
- } else if (b.tagName() == "switch") {
- m_context.pushGraphicsContext(b);
- shapes += parseContainer(b);
- m_context.popGraphicsContext();
- } else if (b.tagName() == "defs") {
- parseDefs(b);
- } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") {
- parseGradient(b);
- } else if (b.tagName() == "pattern") {
- m_context.addDefinition(b);
- } else if (b.tagName() == "filter") {
- parseFilter(b);
- } else if (b.tagName() == "clipPath") {
- parseClipPath(b);
- } else if (b.tagName() == "style") {
- m_context.addStyleSheet(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" ||
- b.tagName() == "text") {
- KoShape *shape = createObject(b);
- if (shape)
- shapes.append(shape);
- } else if (b.tagName() == "use") {
- shapes += parseUse(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);
- } else {
- continue;
- }
- }
+ QList<KoShape*> currentShapes = parseSingleElement(b);
+ shapes.append(currentShapes);
// if we are parsing a switch, stop after the first supported element
- if (isSwitch)
+ if (isSwitch && !currentShapes.isEmpty())
break;
}
return shapes;
}
-void SvgParser::parseDefs(const KoXmlElement &e)
+QList<KoShape*> SvgParser::parseSingleElement(const KoXmlElement &b)
{
- for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
- KoXmlElement b = n.toElement();
- if (b.isNull())
- continue;
+ QList<KoShape*> shapes;
- if (b.tagName() == "style") {
- m_context.addStyleSheet(b);
- } else if (b.tagName() == "defs") {
- parseDefs(b);
- } else {
- m_context.addDefinition(b);
+ // save definition for later instantiation with 'use'
+ m_context.addDefinition(b);
+
+ if (b.tagName() == "svg") {
+ shapes += parseSvg(b);
+ } 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);
+ } else if (b.tagName() == "switch") {
+ m_context.pushGraphicsContext(b);
+ shapes += parseContainer(b);
+ m_context.popGraphicsContext();
+ } else if (b.tagName() == "defs") {
+ if (b.childNodesCount() > 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);
+ }
+ } 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() == "style") {
+ m_context.addStyleSheet(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" ||
+ b.tagName() == "text") {
+ KoShape *shape = createObjectDirect(b);
+ if (shape)
+ shapes.append(shape);
+ } else if (b.tagName() == "use") {
+ shapes += parseUse(b);
+ } 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;
QString points = element.attribute("points").simplified();
points.replace(',', ' ');
points.remove('\r');
points.remove('\n');
QStringList pointList = points.split(' ', QString::SkipEmptyParts);
for (QStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) {
QPointF point;
point.setX(SvgUtil::fromUserSpace((*it).toDouble()));
++it;
if (it == pointList.end())
break;
point.setY(SvgUtil::fromUserSpace((*it).toDouble()));
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);
+ 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;
- QList<KoShapeFactoryBase*> factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, element.tagName());
+
+ 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
- KoShapeStrokeModel *oldStroke = shape->stroke();
- shape->setStroke(0);
- delete oldStroke;
+ 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 tranformation that might come from the default shape
shape->setTransformation(QTransform());
// reset border
- KoShapeStrokeModel *oldStroke = shape->stroke();
- shape->setStroke(0);
- delete oldStroke;
+ 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 21407386de..8cf5367143 100644
--- a/libs/flake/svg/SvgParser.h
+++ b/libs/flake/svg/SvgParser.h
@@ -1,142 +1,171 @@
/* 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 "SvgPatternHelper.h"
#include "SvgFilterHelper.h"
#include "SvgClipPathHelper.h"
#include "SvgLoadingContext.h"
#include "SvgStyleParser.h"
+#include "KoClipMask.h"
class KoShape;
class KoShapeGroup;
class KoDocumentResourceManager;
+class KoVectorPatternBackground;
+class KoMarker;
+class KoPathShape;
class KRITAFLAKE_EXPORT SvgParser
{
public:
explicit SvgParser(KoDocumentResourceManager *documentResourceManager);
virtual ~SvgParser();
/// 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);
+
/// Returns the list of all shapes of the svg document
QList<KoShape*> shapes() const;
+ typedef std::function<QByteArray(const QString&)> FileFetcherFunc;
+ void setFileFetcher(FileFetcherFunc func);
+
+ QList<QExplicitlySharedDataPointer<KoMarker>> knownMarkers() const;
+
protected:
+ /// Parses a group-like element element, saving all its topmost properties
+ KoShape* parseGroup(const KoXmlElement &e, const KoXmlElement &overrideChildrenFrom = KoXmlElement());
/// Parses a container element, returning a list of child shapes
QList<KoShape*> parseContainer(const KoXmlElement &);
+ QList<KoShape*> parseSingleElement(const KoXmlElement &b);
/// Parses a use element, returning a list of child shapes
- QList<KoShape*> parseUse(const KoXmlElement &);
- /// Parses definitions for later use
- void parseDefs(const KoXmlElement &);
+ KoShape* parseUse(const KoXmlElement &);
/// Parses a gradient element
- bool parseGradient(const KoXmlElement &, const KoXmlElement &referencedBy = KoXmlElement());
+ SvgGradientHelper *parseGradient(const KoXmlElement &);
/// Parses a pattern element
- void parsePattern(SvgPatternHelper &pattern, const KoXmlElement &);
+ 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 &, const KoXmlElement &referencedBy = KoXmlElement());
+ bool parseClipPath(const KoXmlElement &);
+ bool parseClipMask(const KoXmlElement &e);
+ bool parseMarker(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, const QString &href = QString());
+ SvgGradientHelper* findGradient(const QString &id);
/// find pattern with given id in pattern map
- SvgPatternHelper* findPattern(const QString &id);
+ 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, const QString &href = QString());
+ SvgClipPathHelper* findClipPath(const QString &id);
/// Adds list of shapes to the given group shape
void addToGroup(QList<KoShape*> shapes, KoShapeGroup * 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);
+
/// Applies styles to the given shape
- void applyStyle(KoShape *, const KoXmlElement &);
+ void applyStyle(KoShape *, const KoXmlElement &, const QPointF &shapeToOriginalUserCoordinates);
/// Applies styles to the given shape
- void applyStyle(KoShape *, const SvgStyles &);
+ 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);
+ 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 currectBoundingBox can become null!
+ void applyViewBoxTransform(const KoXmlElement &element);
+
private:
QSizeF m_documentSize;
SvgLoadingContext m_context;
QMap<QString, SvgGradientHelper> m_gradients;
- QMap<QString, SvgPatternHelper> m_patterns;
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;
QList<KoShape*> m_toplevelShapes;
};
#endif
diff --git a/libs/flake/svg/SvgPatternHelper.cpp b/libs/flake/svg/SvgPatternHelper.cpp
deleted file mode 100644
index 1235a7856e..0000000000
--- a/libs/flake/svg/SvgPatternHelper.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-/* 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 "SvgPatternHelper.h"
-#include "SvgUtil.h"
-
-#include <KoViewConverter.h>
-#include <KoShapePainter.h>
-#include <KoShape.h>
-
-#include <QPainter>
-
-SvgPatternHelper::SvgPatternHelper()
- : m_patternUnits(ObjectBoundingBox), m_patternContentUnits(UserSpaceOnUse)
-{
-}
-
-SvgPatternHelper::~SvgPatternHelper()
-{
-}
-
-void SvgPatternHelper::setPatternUnits(Units units)
-{
- m_patternUnits = units;
-}
-
-SvgPatternHelper::Units SvgPatternHelper::patternUnits() const
-{
- return m_patternUnits;
-}
-
-void SvgPatternHelper::setPatternContentUnits(Units units)
-{
- m_patternContentUnits = units;
-}
-
-SvgPatternHelper::Units SvgPatternHelper::patternContentUnits() const
-{
- return m_patternContentUnits;
-}
-
-void SvgPatternHelper::setTransform(const QTransform &transform)
-{
- m_transform = transform;
-}
-
-QTransform SvgPatternHelper::transform() const
-{
- return m_transform;
-}
-
-void SvgPatternHelper::setPosition(const QPointF & position)
-{
- m_position = position;
-}
-
-QPointF SvgPatternHelper::position(const QRectF & objectBound) const
-{
- if (m_patternUnits == UserSpaceOnUse) {
- return m_position;
- } else {
- return SvgUtil::objectToUserSpace(m_position, objectBound);
- }
-}
-
-void SvgPatternHelper::setSize(const QSizeF & size)
-{
- m_size = size;
-}
-
-QSizeF SvgPatternHelper::size(const QRectF & objectBound) const
-{
- if (m_patternUnits == UserSpaceOnUse) {
- return m_size;
- } else {
- return SvgUtil::objectToUserSpace(m_size, objectBound);
- }
-}
-
-void SvgPatternHelper::setContent(const KoXmlElement &content)
-{
- m_patternContent = content;
-}
-
-KoXmlElement SvgPatternHelper::content() const
-{
- return m_patternContent;
-}
-
-void SvgPatternHelper::copyContent(const SvgPatternHelper &other)
-{
- m_patternContent = other.m_patternContent;
-}
-
-void SvgPatternHelper::setPatternContentViewbox(const QRectF &viewBox)
-{
- m_patternContentViewbox = viewBox;
-}
-
-QImage SvgPatternHelper::generateImage(const QRectF &objectBound, const QList<KoShape*> content)
-{
- KoViewConverter zoomHandler;
-
- QSizeF patternSize = size(objectBound);
- if (patternSize.isEmpty())
- return QImage();
-
- QSizeF tileSize = zoomHandler.documentToView(patternSize);
- if (tileSize.isEmpty())
- return QImage();
-
- QTransform viewMatrix;
-
- if (! m_patternContentViewbox.isNull()) {
- viewMatrix.translate(-m_patternContentViewbox.x(), -m_patternContentViewbox.y());
- const qreal xScale = patternSize.width() / m_patternContentViewbox.width();
- const qreal yScale = patternSize.height() / m_patternContentViewbox.height();
- viewMatrix.scale(xScale, yScale);
- }
-
- // setup the tile image
- QImage tile(tileSize.toSize(), QImage::Format_ARGB32);
- tile.fill(QColor(Qt::transparent).rgba());
-
- // setup the painter to paint the tile content
- QPainter tilePainter(&tile);
- tilePainter.setClipRect(tile.rect());
- tilePainter.setWorldTransform(viewMatrix);
- //tilePainter.setRenderHint(QPainter::Antialiasing);
-
- // paint the content into the tile image
- KoShapePainter shapePainter;
- shapePainter.setShapes(content);
- shapePainter.paint(tilePainter, zoomHandler);
-
- return tile;
-}
diff --git a/libs/flake/svg/SvgPatternHelper.h b/libs/flake/svg/SvgPatternHelper.h
deleted file mode 100644
index bca886155f..0000000000
--- a/libs/flake/svg/SvgPatternHelper.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/* 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.
- */
-
-#ifndef SVGPATTERNHELPER_H
-#define SVGPATTERNHELPER_H
-
-#include <KoXmlReader.h>
-#include <QImage>
-#include <QTransform>
-
-class KoShape;
-
-class SvgPatternHelper
-{
-public:
- enum Units { UserSpaceOnUse, ObjectBoundingBox };
-
- SvgPatternHelper();
- ~SvgPatternHelper();
-
- /// Set pattern units type (affects pattern x, y, width and height)
- void setPatternUnits(Units units);
- /// Return pattern units type
- Units patternUnits() const;
-
- /// Set pattern content units type (affects coordinates/length of pattern child shapes)
- void setPatternContentUnits(Units units);
- /// Returns pattern content units type
- Units patternContentUnits() const;
-
- /// Sets the pattern transformation found in attribute "patternTransform"
- void setTransform(const QTransform &transform);
- /// Returns the pattern transform
- QTransform transform() const;
-
- /// Sets pattern tile position
- void setPosition(const QPointF & position);
- /// Returns pattern tile position (objectBound is used when patternUnits == ObjectBoundingBox)
- QPointF position(const QRectF & objectBound) const;
-
- /// Sets pattern tile size
- void setSize(const QSizeF & size);
- /// Returns pattern tile size (objectBound is used when patternUnits == ObjectBoundingBox)
- QSizeF size(const QRectF & objectBound) const;
-
- /// Sets the dom element containing the pattern content
- void setContent(const KoXmlElement &content);
- /// Return the pattern content element
- KoXmlElement content() const;
-
- /// copies the content from the given pattern helper
- void copyContent(const SvgPatternHelper &other);
-
- /// Sets the pattern view box (the view box content is fitted into the pattern tile)
- void setPatternContentViewbox(const QRectF &viewBox);
-
- /// generates the pattern image from the given shapes and using the specified bounding box
- QImage generateImage(const QRectF &objectBound, const QList<KoShape*> content);
-
-private:
-
- Units m_patternUnits;
- Units m_patternContentUnits;
- QTransform m_transform;
- QPointF m_position;
- QSizeF m_size;
- KoXmlElement m_patternContent;
- QRectF m_patternContentViewbox;
-};
-
-#endif // SVGPATTERNHELPER_H
diff --git a/libs/flake/svg/SvgStyleParser.cpp b/libs/flake/svg/SvgStyleParser.cpp
index c2d40380e3..b5b7a241b9 100644
--- a/libs/flake/svg/SvgStyleParser.cpp
+++ b/libs/flake/svg/SvgStyleParser.cpp
@@ -1,470 +1,558 @@
/* 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 "SvgStyleParser.h"
#include "SvgLoadingContext.h"
#include "SvgGraphicContext.h"
#include "SvgUtil.h"
#include <QStringList>
#include <QColor>
#include <QGradientStops>
class Q_DECL_HIDDEN SvgStyleParser::Private
{
public:
Private(SvgLoadingContext &loadingContext)
: context(loadingContext)
{
// the order of the font attributes is important, don't change without reason !!!
fontAttributes << "font-family" << "font-size" << "font-weight";
fontAttributes << "text-decoration" << "letter-spacing" << "word-spacing" << "baseline-shift";
// the order of the style attributes is important, don't change without reason !!!
- styleAttributes << "color" << "display";
+ styleAttributes << "color" << "display" << "visibility";
styleAttributes << "fill" << "fill-rule" << "fill-opacity";
styleAttributes << "stroke" << "stroke-width" << "stroke-linejoin" << "stroke-linecap";
styleAttributes << "stroke-dasharray" << "stroke-dashoffset" << "stroke-opacity" << "stroke-miterlimit";
- styleAttributes << "opacity" << "filter" << "clip-path" << "clip-rule";
+ styleAttributes << "opacity" << "filter" << "clip-path" << "clip-rule" << "mask";
+ styleAttributes << "marker" << "marker-start" << "marker-mid" << "marker-end" << "krita:marker-fill-method";
}
SvgLoadingContext &context;
QStringList fontAttributes; ///< font related attributes
QStringList styleAttributes; ///< style related attributes
};
SvgStyleParser::SvgStyleParser(SvgLoadingContext &context)
: d(new Private(context))
{
}
SvgStyleParser::~SvgStyleParser()
{
delete d;
}
void SvgStyleParser::parseStyle(const SvgStyles &styles)
{
SvgGraphicsContext *gc = d->context.currentGC();
if (!gc)
return;
// make sure we parse the style attributes in the right order
Q_FOREACH (const QString & command, d->styleAttributes) {
const QString &params = styles.value(command);
if (params.isEmpty())
continue;
parsePA(gc, command, params);
}
}
void SvgStyleParser::parseFont(const SvgStyles &styles)
{
SvgGraphicsContext *gc = d->context.currentGC();
if (!gc)
return;
// make sure to only parse font attributes here
Q_FOREACH (const QString & command, d->fontAttributes) {
const QString &params = styles.value(command);
if (params.isEmpty())
continue;
parsePA(gc, command, params);
}
}
void SvgStyleParser::parsePA(SvgGraphicsContext *gc, const QString &command, const QString &params)
{
QColor fillcolor = gc->fillColor;
- QColor strokecolor = gc->stroke.color();
+ QColor strokecolor = gc->stroke->color();
if (params == "inherit")
return;
if (command == "fill") {
if (params == "none") {
gc->fillType = SvgGraphicsContext::None;
} else if (params.startsWith(QLatin1String("url("))) {
unsigned int start = params.indexOf('#') + 1;
unsigned int end = params.indexOf(')', start);
gc->fillId = params.mid(start, end - start);
gc->fillType = SvgGraphicsContext::Complex;
// check if there is a fallback color
parseColor(fillcolor, params.mid(end + 1).trimmed());
} else {
// great we have a solid fill
gc->fillType = SvgGraphicsContext::Solid;
parseColor(fillcolor, params);
}
} else if (command == "fill-rule") {
if (params == "nonzero")
gc->fillRule = Qt::WindingFill;
else if (params == "evenodd")
gc->fillRule = Qt::OddEvenFill;
} else if (command == "stroke") {
if (params == "none") {
gc->strokeType = SvgGraphicsContext::None;
} else if (params.startsWith(QLatin1String("url("))) {
unsigned int start = params.indexOf('#') + 1;
unsigned int end = params.indexOf(')', start);
gc->strokeId = params.mid(start, end - start);
gc->strokeType = SvgGraphicsContext::Complex;
// check if there is a fallback color
parseColor(strokecolor, params.mid(end + 1).trimmed());
} else {
// great we have a solid stroke
gc->strokeType = SvgGraphicsContext::Solid;
parseColor(strokecolor, params);
}
} else if (command == "stroke-width") {
- gc->stroke.setLineWidth(SvgUtil::parseUnitXY(gc, params));
+ gc->stroke->setLineWidth(SvgUtil::parseUnitXY(gc, params));
} else if (command == "stroke-linejoin") {
if (params == "miter")
- gc->stroke.setJoinStyle(Qt::MiterJoin);
+ gc->stroke->setJoinStyle(Qt::MiterJoin);
else if (params == "round")
- gc->stroke.setJoinStyle(Qt::RoundJoin);
+ gc->stroke->setJoinStyle(Qt::RoundJoin);
else if (params == "bevel")
- gc->stroke.setJoinStyle(Qt::BevelJoin);
+ gc->stroke->setJoinStyle(Qt::BevelJoin);
} else if (command == "stroke-linecap") {
if (params == "butt")
- gc->stroke.setCapStyle(Qt::FlatCap);
+ gc->stroke->setCapStyle(Qt::FlatCap);
else if (params == "round")
- gc->stroke.setCapStyle(Qt::RoundCap);
+ gc->stroke->setCapStyle(Qt::RoundCap);
else if (params == "square")
- gc->stroke.setCapStyle(Qt::SquareCap);
+ gc->stroke->setCapStyle(Qt::SquareCap);
} else if (command == "stroke-miterlimit") {
- gc->stroke.setMiterLimit(params.toFloat());
+ gc->stroke->setMiterLimit(params.toFloat());
} else if (command == "stroke-dasharray") {
QVector<qreal> array;
if (params != "none") {
QString dashString = params;
QStringList dashes = dashString.replace(',', ' ').simplified().split(' ');
- for (QStringList::Iterator it = dashes.begin(); it != dashes.end(); ++it)
- array.append((*it).toFloat());
+ for (QStringList::Iterator it = dashes.begin(); it != dashes.end(); ++it) {
+ array.append(SvgUtil::parseUnitXY(gc, *it));
+ }
+
+ // if the array is odd repeat it according to the standard
+ if (array.size() & 1) {
+ array << array;
+ }
}
- gc->stroke.setLineStyle(Qt::CustomDashLine, array);
+ gc->stroke->setLineStyle(Qt::CustomDashLine, array);
} else if (command == "stroke-dashoffset") {
- gc->stroke.setDashOffset(params.toFloat());
+ gc->stroke->setDashOffset(params.toFloat());
}
// handle opacity
else if (command == "stroke-opacity")
strokecolor.setAlphaF(SvgUtil::fromPercentage(params));
else if (command == "fill-opacity") {
float opacity = SvgUtil::fromPercentage(params);
if (opacity < 0.0)
opacity = 0.0;
if (opacity > 1.0)
opacity = 1.0;
fillcolor.setAlphaF(opacity);
} else if (command == "opacity") {
gc->opacity = SvgUtil::fromPercentage(params);
} else if (command == "font-family") {
QString family = params;
family.replace('\'' , ' ');
gc->font.setFamily(family);
} else if (command == "font-size") {
float pointSize = SvgUtil::parseUnitY(gc, params);
if (pointSize > 0.0f)
gc->font.setPointSizeF(pointSize);
} else if (command == "font-weight") {
int weight = QFont::Normal;
// map svg weight to qt weight
// svg value qt value
// 100,200,300 1, 17, 33
// 400 50 (normal)
// 500,600 58,66
// 700 75 (bold)
// 800,900 87,99
if (params == "bold")
weight = QFont::Bold;
else if (params == "lighter") {
weight = gc->font.weight();
if (weight <= 17)
weight = 1;
else if (weight <= 33)
weight = 17;
else if (weight <= 50)
weight = 33;
else if (weight <= 58)
weight = 50;
else if (weight <= 66)
weight = 58;
else if (weight <= 75)
weight = 66;
else if (weight <= 87)
weight = 75;
else if (weight <= 99)
weight = 87;
} else if (params == "bolder") {
weight = gc->font.weight();
if (weight >= 87)
weight = 99;
else if (weight >= 75)
weight = 87;
else if (weight >= 66)
weight = 75;
else if (weight >= 58)
weight = 66;
else if (weight >= 50)
weight = 58;
else if (weight >= 33)
weight = 50;
else if (weight >= 17)
weight = 50;
else if (weight >= 1)
weight = 17;
} else {
bool ok;
// try to read numerical weight value
weight = params.toInt(&ok, 10);
if (!ok)
return;
switch (weight) {
case 100: weight = 1; break;
case 200: weight = 17; break;
case 300: weight = 33; break;
case 400: weight = 50; break;
case 500: weight = 58; break;
case 600: weight = 66; break;
case 700: weight = 75; break;
case 800: weight = 87; break;
case 900: weight = 99; break;
}
}
gc->font.setWeight(weight);
} else if (command == "text-decoration") {
if (params == "line-through")
gc->font.setStrikeOut(true);
else if (params == "underline")
gc->font.setUnderline(true);
} else if (command == "letter-spacing") {
gc->letterSpacing = SvgUtil::parseUnitX(gc, params);
} else if (command == "baseline-shift") {
gc->baselineShift = params;
} else if (command == "word-spacing") {
gc->wordSpacing = SvgUtil::parseUnitX(gc, params);
} else if (command == "color") {
QColor color;
parseColor(color, params);
gc->currentColor = color;
} else if (command == "display") {
if (params == "none")
gc->display = false;
+ } else if (command == "visibility") {
+ // visible is inherited!
+ gc->visible = params == "visible";
} else if (command == "filter") {
if (params != "none" && params.startsWith("url(")) {
unsigned int start = params.indexOf('#') + 1;
unsigned int end = params.indexOf(')', start);
gc->filterId = params.mid(start, end - start);
}
} else if (command == "clip-path") {
if (params != "none" && params.startsWith("url(")) {
unsigned int start = params.indexOf('#') + 1;
unsigned int end = params.indexOf(')', start);
gc->clipPathId = params.mid(start, end - start);
}
} else if (command == "clip-rule") {
if (params == "nonzero")
gc->clipRule = Qt::WindingFill;
else if (params == "evenodd")
gc->clipRule = Qt::OddEvenFill;
+ } else if (command == "mask") {
+ if (params != "none" && params.startsWith("url(")) {
+ unsigned int start = params.indexOf('#') + 1;
+ unsigned int end = params.indexOf(')', start);
+ gc->clipMaskId = params.mid(start, end - start);
+ }
+ } else if (command == "marker-start") {
+ if (params != "none" && params.startsWith("url(")) {
+ unsigned int start = params.indexOf('#') + 1;
+ unsigned int end = params.indexOf(')', start);
+ gc->markerStartId = params.mid(start, end - start);
+ }
+ } else if (command == "marker-end") {
+ if (params != "none" && params.startsWith("url(")) {
+ unsigned int start = params.indexOf('#') + 1;
+ unsigned int end = params.indexOf(')', start);
+ gc->markerEndId = params.mid(start, end - start);
+ }
+ } else if (command == "marker-mid") {
+ if (params != "none" && params.startsWith("url(")) {
+ unsigned int start = params.indexOf('#') + 1;
+ unsigned int end = params.indexOf(')', start);
+ gc->markerMidId = params.mid(start, end - start);
+ }
+ } else if (command == "marker") {
+ if (params != "none" && params.startsWith("url(")) {
+ unsigned int start = params.indexOf('#') + 1;
+ unsigned int end = params.indexOf(')', start);
+ gc->markerStartId = params.mid(start, end - start);
+ gc->markerMidId = gc->markerStartId;
+ gc->markerEndId = gc->markerStartId;
+ }
+ } else if (command == "krita:marker-fill-method") {
+ gc->autoFillMarkers = params == "auto";
}
gc->fillColor = fillcolor;
- gc->stroke.setColor(strokecolor);
+ gc->stroke->setColor(strokecolor);
}
bool SvgStyleParser::parseColor(QColor &color, const QString &s)
{
if (s.isEmpty() || s == "none")
return false;
if (s.startsWith(QLatin1String("rgb("))) {
QString parse = s.trimmed();
QStringList colors = parse.split(',');
QString r = colors[0].right((colors[0].length() - 4));
QString g = colors[1];
QString b = colors[2].left((colors[2].length() - 1));
if (r.contains('%')) {
r = r.left(r.length() - 1);
r = QString::number(int((double(255 * r.toDouble()) / 100.0)));
}
if (g.contains('%')) {
g = g.left(g.length() - 1);
g = QString::number(int((double(255 * g.toDouble()) / 100.0)));
}
if (b.contains('%')) {
b = b.left(b.length() - 1);
b = QString::number(int((double(255 * b.toDouble()) / 100.0)));
}
color = QColor(r.toInt(), g.toInt(), b.toInt());
} else if (s == "currentColor") {
color = d->context.currentGC()->currentColor;
} else {
// QColor understands #RRGGBB and svg color names
color.setNamedColor(s.trimmed());
}
return true;
}
-void SvgStyleParser::parseColorStops(QGradient *gradient, const KoXmlElement &e)
+void SvgStyleParser::parseColorStops(QGradient *gradient,
+ const KoXmlElement &e,
+ SvgGraphicsContext *context,
+ const QGradientStops &defaultStops)
{
QGradientStops stops;
- QColor c;
- for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
- KoXmlElement stop = n.toElement();
+ qreal previousOffset = 0.0;
+
+ KoXmlElement stop;
+ forEachElement(stop, e) {
if (stop.tagName() == "stop") {
- float offset;
- QString temp = stop.attribute("offset");
- if (temp.contains('%')) {
- temp = temp.left(temp.length() - 1);
- offset = temp.toFloat() / 100.0;
- } else
- offset = temp.toFloat();
+ qreal offset = 0.0;
+ QString offsetStr = stop.attribute("offset").trimmed();
+ if (offsetStr.endsWith('%')) {
+ offsetStr = offsetStr.left(offsetStr.length() - 1);
+ offset = offsetStr.toFloat() / 100.0;
+ } else {
+ offset = offsetStr.toFloat();
+ }
+
+ // according to SVG the value must be within [0; 1] interval
+ offset = qBound(0.0, offset, 1.0);
+
+ // according to SVG the stops' offset must be non-decreasing
+ offset = qMax(offset, previousOffset);
+ previousOffset = offset;
+
+ QColor color;
QString stopColorStr = stop.attribute("stop-color");
- if (!stopColorStr.isEmpty()) {
- if (stopColorStr == "inherit") {
- stopColorStr = inheritedAttribute("stop-color", stop);
- }
- parseColor(c, stopColorStr);
+ QString stopOpacityStr = stop.attribute("stop-opacity");
+
+ const QStringList attributes({"stop-color", "stop-opacity"});
+ SvgStyles styles = parseOneCssStyle(stop.attribute("style"), attributes);
+
+ // SVG: CSS values have precedence over presentation attributes!
+ if (styles.contains("stop-color")) {
+ stopColorStr = styles.value("stop-color");
}
- else {
- // try style attr
- QString style = stop.attribute("style").simplified();
- QStringList substyles = style.split(';', QString::SkipEmptyParts);
- for (QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it) {
- QStringList substyle = it->split(':');
- QString command = substyle[0].trimmed();
- QString params = substyle[1].trimmed();
- if (command == "stop-color")
- parseColor(c, params);
- if (command == "stop-opacity")
- c.setAlphaF(params.toDouble());
- }
+ if (styles.contains("stop-opacity")) {
+ stopOpacityStr = styles.value("stop-opacity");
}
- QString opacityStr = stop.attribute("stop-opacity");
- if (!opacityStr.isEmpty()) {
- if (opacityStr == "inherit") {
- opacityStr = inheritedAttribute("stop-opacity", stop);
- }
- c.setAlphaF(opacityStr.toDouble());
+
+ if (stopColorStr.isEmpty() && stopColorStr == "inherit") {
+ color = context->currentColor;
+ } else {
+ parseColor(color, stopColorStr);
+ }
+
+ if (!stopOpacityStr.isEmpty() && stopOpacityStr != "inherit") {
+ color.setAlphaF(qBound(0.0, stopOpacityStr.toDouble(), 1.0));
}
- stops.append(QPair<qreal, QColor>(offset, c));
+
+ stops.append(QPair<qreal, QColor>(offset, color));
}
}
- if (stops.count())
+
+ if (!stops.isEmpty()) {
gradient->setStops(stops);
+ } else {
+ gradient->setStops(defaultStops);
+ }
+}
+
+SvgStyles SvgStyleParser::parseOneCssStyle(const QString &style, const QStringList &interestingAttributes)
+{
+ SvgStyles parsedStyles;
+ if (style.isEmpty()) return parsedStyles;
+
+ QStringList substyles = style.simplified().split(';', QString::SkipEmptyParts);
+ if (!substyles.count()) return parsedStyles;
+
+ for (QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it) {
+ QStringList substyle = it->split(':');
+ if (substyle.count() != 2)
+ continue;
+ QString command = substyle[0].trimmed();
+ QString params = substyle[1].trimmed();
+
+ if (interestingAttributes.isEmpty() || interestingAttributes.contains(command)) {
+ parsedStyles[command] = params;
+ }
+ }
+
+ return parsedStyles;
}
SvgStyles SvgStyleParser::collectStyles(const KoXmlElement &e)
{
SvgStyles styleMap;
// collect individual presentation style attributes which have the priority 0
+ // according to SVG standard
Q_FOREACH (const QString &command, d->styleAttributes) {
const QString attribute = e.attribute(command);
if (!attribute.isEmpty())
styleMap[command] = attribute;
}
Q_FOREACH (const QString & command, d->fontAttributes) {
const QString attribute = e.attribute(command);
if (!attribute.isEmpty())
styleMap[command] = attribute;
}
// match css style rules to element
- QStringList cssStyles = d->context.matchingStyles(e);
+ QStringList cssStyles = d->context.matchingCssStyles(e);
// collect all css style attributes
Q_FOREACH (const QString &style, cssStyles) {
QStringList substyles = style.split(';', QString::SkipEmptyParts);
if (!substyles.count())
continue;
for (QStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it) {
QStringList substyle = it->split(':');
if (substyle.count() != 2)
continue;
QString command = substyle[0].trimmed();
QString params = substyle[1].trimmed();
+
+ // toggle the namespace selector into the xml-like one
+ command.replace("|", ":");
+
// only use style and font attributes
if (d->styleAttributes.contains(command) || d->fontAttributes.contains(command))
styleMap[command] = params;
}
}
+ // FIXME: if 'inherit' we should just remove the property and use the one from the context!
+
// replace keyword "inherit" for style values
QMutableMapIterator<QString, QString> it(styleMap);
while (it.hasNext()) {
it.next();
if (it.value() == "inherit") {
it.setValue(inheritedAttribute(it.key(), e));
}
}
return styleMap;
}
SvgStyles SvgStyleParser::mergeStyles(const SvgStyles &referencedBy, const SvgStyles &referencedStyles)
{
// 1. use all styles of the referencing styles
SvgStyles mergedStyles = referencedBy;
// 2. use all styles of the referenced style which are not in the referencing styles
SvgStyles::const_iterator it = referencedStyles.constBegin();
for (; it != referencedStyles.constEnd(); ++it) {
if (!referencedBy.contains(it.key())) {
mergedStyles.insert(it.key(), it.value());
}
}
return mergedStyles;
}
SvgStyles SvgStyleParser::mergeStyles(const KoXmlElement &e1, const KoXmlElement &e2)
{
return mergeStyles(collectStyles(e1), collectStyles(e2));
}
QString SvgStyleParser::inheritedAttribute(const QString &attributeName, const KoXmlElement &e)
{
KoXmlNode parent = e.parentNode();
while (!parent.isNull()) {
KoXmlElement currentElement = parent.toElement();
if (currentElement.hasAttribute(attributeName)) {
return currentElement.attribute(attributeName);
}
parent = currentElement.parentNode();
}
return QString();
}
diff --git a/libs/flake/svg/SvgStyleParser.h b/libs/flake/svg/SvgStyleParser.h
index 8f6111c75b..c5439e3294 100644
--- a/libs/flake/svg/SvgStyleParser.h
+++ b/libs/flake/svg/SvgStyleParser.h
@@ -1,75 +1,78 @@
/* 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 SVGSTYLEPARSER_H
#define SVGSTYLEPARSER_H
#include "kritaflake_export.h"
#include <QMap>
+#include <QGradient>
typedef QMap<QString, QString> SvgStyles;
class SvgLoadingContext;
class SvgGraphicsContext;
class KoXmlElement;
class QColor;
class QGradient;
+
class KRITAFLAKE_EXPORT SvgStyleParser
{
public:
explicit SvgStyleParser(SvgLoadingContext &context);
~SvgStyleParser();
/// Parses specified style attributes
void parseStyle(const SvgStyles &styles);
/// Parses font attributes
void parseFont(const SvgStyles &styles);
/// Parses a color attribute
bool parseColor(QColor &, const QString &);
/// Parses gradient color stops
- void parseColorStops(QGradient *, const KoXmlElement &);
+ void parseColorStops(QGradient *, const KoXmlElement &, SvgGraphicsContext *context, const QGradientStops &defaultStops);
/// Creates style map from given xml element
SvgStyles collectStyles(const KoXmlElement &);
/// Merges two style elements, returning the merged style
SvgStyles mergeStyles(const SvgStyles &, const SvgStyles &);
/// Merges two style elements, returning the merged style
SvgStyles mergeStyles(const KoXmlElement &, const KoXmlElement &);
+ SvgStyles parseOneCssStyle(const QString &style, const QStringList &interestingAttributes);
private:
/// Parses a single style attribute
void parsePA(SvgGraphicsContext *, const QString &, const QString &);
/// Returns inherited attribute value for specified element
QString inheritedAttribute(const QString &attributeName, const KoXmlElement &e);
class Private;
Private * const d;
};
#endif // SVGSTYLEPARSER_H
diff --git a/libs/flake/svg/SvgStyleWriter.cpp b/libs/flake/svg/SvgStyleWriter.cpp
index 126fa3c8df..2cb7c6773e 100644
--- a/libs/flake/svg/SvgStyleWriter.cpp
+++ b/libs/flake/svg/SvgStyleWriter.cpp
@@ -1,367 +1,546 @@
/* 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)
{
saveSvgFill(shape, context);
saveSvgStroke(shape, context);
saveSvgEffects(shape, context);
saveSvgClipping(shape, context);
+ saveSvgMasking(shape, context);
+ saveSvgMarkers(shape, context);
if (! shape->isVisible())
context.shapeWriter().addAttribute("display", "none");
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 KoShapeStroke * line = dynamic_cast<const KoShapeStroke*>(shape->stroke());
- if (! line)
+ const QSharedPointer<KoShapeStroke> lineBorder = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
+
+ if (! lineBorder)
return;
QString strokeStr("none");
- if (line->lineBrush().gradient()) {
- QString gradientId = saveSvgGradient(line->lineBrush().gradient(), line->lineBrush().transform(), context);
+ if (lineBorder->lineBrush().gradient()) {
+ QString gradientId = saveSvgGradient(lineBorder->lineBrush().gradient(), lineBorder->lineBrush().transform(), context);
strokeStr = "url(#" + gradientId + ")";
} else {
- strokeStr = line->color().name();
+ strokeStr = lineBorder->color().name();
}
if (!strokeStr.isEmpty())
context.shapeWriter().addAttribute("stroke", strokeStr);
- if (line->color().alphaF() < 1.0)
- context.shapeWriter().addAttribute("stroke-opacity", line->color().alphaF());
- context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(line->lineWidth()));
+ if (lineBorder->color().alphaF() < 1.0)
+ context.shapeWriter().addAttribute("stroke-opacity", lineBorder->color().alphaF());
+ context.shapeWriter().addAttribute("stroke-width", SvgUtil::toUserSpace(lineBorder->lineWidth()));
- if (line->capStyle() == Qt::FlatCap)
+ if (lineBorder->capStyle() == Qt::FlatCap)
context.shapeWriter().addAttribute("stroke-linecap", "butt");
- else if (line->capStyle() == Qt::RoundCap)
+ else if (lineBorder->capStyle() == Qt::RoundCap)
context.shapeWriter().addAttribute("stroke-linecap", "round");
- else if (line->capStyle() == Qt::SquareCap)
+ else if (lineBorder->capStyle() == Qt::SquareCap)
context.shapeWriter().addAttribute("stroke-linecap", "square");
- if (line->joinStyle() == Qt::MiterJoin) {
+ if (lineBorder->joinStyle() == Qt::MiterJoin) {
context.shapeWriter().addAttribute("stroke-linejoin", "miter");
- context.shapeWriter().addAttribute("stroke-miterlimit", line->miterLimit());
- } else if (line->joinStyle() == Qt::RoundJoin)
+ context.shapeWriter().addAttribute("stroke-miterlimit", lineBorder->miterLimit());
+ } else if (lineBorder->joinStyle() == Qt::RoundJoin)
context.shapeWriter().addAttribute("stroke-linejoin", "round");
- else if (line->joinStyle() == Qt::BevelJoin)
+ else if (lineBorder->joinStyle() == Qt::BevelJoin)
context.shapeWriter().addAttribute("stroke-linejoin", "bevel");
// dash
- if (line->lineStyle() > Qt::SolidLine) {
- qreal dashFactor = line->lineWidth();
+ if (lineBorder->lineStyle() > Qt::SolidLine) {
+ qreal dashFactor = lineBorder->lineWidth();
- if (line->dashOffset() != 0)
- context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * line->dashOffset());
+ if (lineBorder->dashOffset() != 0)
+ context.shapeWriter().addAttribute("stroke-dashoffset", dashFactor * lineBorder->dashOffset());
QString dashStr;
- const QVector<qreal> dashes = line->lineDashes();
+ 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(dashes[i] * dashFactor);
+ 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, QSizeF(666, 666));
+ shapesWriter.saveDetached(buffer);
+ }
+ buffer.close();
+ outWriter.addCompleteElement(&buffer);
+}
+
+
void SvgStyleWriter::saveSvgClipping(KoShape *shape, SvgSavingContext &context)
{
KoClipPath *clipPath = shape->clipPath();
if (!clipPath)
return;
- const QSizeF shapeSize = shape->outlineRect().size();
- KoPathShape *path = KoPathShape::createShapeFromPainterPath(clipPath->pathForSize(shapeSize));
- if (!path)
- return;
-
- path->close();
-
const QString uid = context.createUID("clippath");
context.styleWriter().startElement("clipPath");
context.styleWriter().addAttribute("id", uid);
- context.styleWriter().addAttribute("clipPathUnits", "userSpaceOnUse");
+ context.styleWriter().addAttribute("clipPathUnits", KoFlake::coordinateToString(clipPath->coordinates()));
- context.styleWriter().startElement("path");
- context.styleWriter().addAttribute("d", path->toString(path->absoluteTransformation(0)*context.userSpaceTransform()));
- context.styleWriter().endElement(); // path
+ 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();
+
+ // think funny duplication? please note the 'pt' suffix! :)
+ if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
+ context.styleWriter().addAttribute("x", rect.x());
+ context.styleWriter().addAttribute("y", rect.y());
+ context.styleWriter().addAttribute("width", rect.width());
+ context.styleWriter().addAttribute("height", rect.height());
+ } else {
+ context.styleWriter().addAttributePt("x", rect.x());
+ context.styleWriter().addAttributePt("y", rect.y());
+ context.styleWriter().addAttributePt("width", rect.width());
+ context.styleWriter().addAttributePt("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();
Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode);
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);
context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform));
- context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox");
+ 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);
context.styleWriter().addAttribute("gradientTransform", SvgUtil::transformToString(gradientTransform));
- context.styleWriter().addAttribute("gradientUnits", "objectBoundingBox");
+ 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);
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(imageSize.width()).arg(imageSize.height()));
+ 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(imageSize.width()));
- context.styleWriter().addAttribute("height", QString("%1px").arg(imageSize.height()));
+ 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();
+
+ if (pattern->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
+ context.styleWriter().addAttribute("x", rect.x());
+ context.styleWriter().addAttribute("y", rect.y());
+ context.styleWriter().addAttribute("width", rect.width());
+ context.styleWriter().addAttribute("height", rect.height());
+ } else {
+ context.styleWriter().addAttributePt("x", rect.x());
+ context.styleWriter().addAttributePt("y", rect.y());
+ context.styleWriter().addAttributePt("width", rect.width());
+ context.styleWriter().addAttributePt("height", rect.height());
+ }
+
+ context.styleWriter().addAttribute("patternTransform", SvgUtil::transformToString(pattern->patternTransform()));
+
+ 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/SvgStyleWriter.h b/libs/flake/svg/SvgStyleWriter.h
index e0806f4315..17c2d3be26 100644
--- a/libs/flake/svg/SvgStyleWriter.h
+++ b/libs/flake/svg/SvgStyleWriter.h
@@ -1,66 +1,72 @@
/* This file is part of the KDE project
Copyright (C) 2002 Lars Siebold <khandha5@gmx.net>
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
Copyright (C) 2002 Lennart Kudling <kudling@kde.org>
Copyright (C) 2002-2003,2005 Rob Buis <buis@kde.org>
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,2008 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006 Inge Wallin <inge@lysator.liu.se>
Copyright (C) 2006 Laurent Montel <montel@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 SVGSTYLEWRITER_H
#define SVGSTYLEWRITER_H
#include "kritaflake_export.h"
#include <QGradientStops>
#include <QSharedPointer>
class SvgSavingContext;
class KoShape;
class KoPatternBackground;
+class KoVectorPatternBackground;
class QTransform;
class QGradient;
/// Helper class to save svg styles
class KRITAFLAKE_EXPORT SvgStyleWriter
{
public:
/// Saves the style of the specified shape
static void saveSvgStyle(KoShape *shape, SvgSavingContext &context);
protected:
/// Saves fill style of specified shape
static void saveSvgFill(KoShape *shape, SvgSavingContext &context);
/// Saves stroke style of specified shape
static void saveSvgStroke(KoShape *shape, SvgSavingContext &context);
/// Saves effects of specified shape
static void saveSvgEffects(KoShape *shape, SvgSavingContext &context);
/// Saves clipping of specified shape
static void saveSvgClipping(KoShape *shape, SvgSavingContext &context);
+ /// Saves masking of specified shape
+ static void saveSvgMasking(KoShape *shape, SvgSavingContext &context);
+ /// Saves markers of the path shape if present
+ static void saveSvgMarkers(KoShape *shape, SvgSavingContext &context);
/// Saves gradient color stops
static void saveSvgColorStops(const QGradientStops &colorStops, SvgSavingContext &context);
/// Saves gradient
static QString saveSvgGradient(const QGradient *gradient, const QTransform &gradientTransform, SvgSavingContext &context);
/// Saves pattern
static QString saveSvgPattern(QSharedPointer<KoPatternBackground> pattern, KoShape *shape, SvgSavingContext &context);
+ static QString saveSvgVectorPattern(QSharedPointer<KoVectorPatternBackground> pattern, KoShape *shape, SvgSavingContext &context);
};
#endif // SVGSTYLEWRITER_H
diff --git a/libs/flake/svg/SvgUtil.cpp b/libs/flake/svg/SvgUtil.cpp
index 21291278ac..454cc1968c 100644
--- a/libs/flake/svg/SvgUtil.cpp
+++ b/libs/flake/svg/SvgUtil.cpp
@@ -1,345 +1,513 @@
/* 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 "kis_dom_utils.h"
#define DPI 72.0
#define DEG2RAD(degree) degree/180.0*M_PI
double SvgUtil::fromUserSpace(double value)
{
- return (value * DPI) / 90.0;
+ return value;
}
double SvgUtil::toUserSpace(double value)
{
- return (value * 90.0) / DPI;
+ 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()));
}
double SvgUtil::toPercentage(QString s)
{
if (s.endsWith('%'))
return s.remove('%').toDouble();
else
return s.toDouble() * 100.0;
}
double SvgUtil::fromPercentage(QString s)
{
if (s.endsWith('%'))
return s.remove('%').toDouble() / 100.0;
else
return s.toDouble();
}
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);
}
-QTransform SvgUtil::parseTransform(const QString &transform)
-{
- QTransform result;
-
- // 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).simplified().split('(', QString::SkipEmptyParts);
- if (subtransform.count() < 2)
- continue;
-
- 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);
-
- if (subtransform[0] == "rotate") {
- if (params.count() == 3) {
- double x = params[1].toDouble();
- double y = params[2].toDouble();
-
- result.translate(x, y);
- result.rotate(params[0].toDouble());
- result.translate(-x, -y);
- } else {
- result.rotate(params[0].toDouble());
- }
- } else if (subtransform[0] == "translate") {
- if (params.count() == 2) {
- result.translate(SvgUtil::fromUserSpace(params[0].toDouble()),
- SvgUtil::fromUserSpace(params[1].toDouble()));
- } else { // Spec : if only one param given, assume 2nd param to be 0
- result.translate(SvgUtil::fromUserSpace(params[0].toDouble()) , 0);
- }
- } else if (subtransform[0] == "scale") {
- if (params.count() == 2) {
- result.scale(params[0].toDouble(), params[1].toDouble());
- } else { // Spec : if only one param given, assume uniform scaling
- result.scale(params[0].toDouble(), params[0].toDouble());
- }
- } else if (subtransform[0].toLower() == "skewx") {
- result.shear(tan(DEG2RAD(params[0].toDouble())), 0.0F);
- } else if (subtransform[0].toLower() == "skewy") {
- result.shear(0.0F, tan(DEG2RAD(params[0].toDouble())));
- } else if (subtransform[0] == "matrix") {
- if (params.count() >= 6) {
- result.setMatrix(params[0].toDouble(), params[1].toDouble(), 0,
- params[2].toDouble(), params[3].toDouble(), 0,
- SvgUtil::fromUserSpace(params[4].toDouble()),
- SvgUtil::fromUserSpace(params[5].toDouble()), 1);
- }
- }
- }
-
- return result;
-}
-
QString SvgUtil::transformToString(const QTransform &transform)
{
if (transform.isIdentity())
return QString();
if (transform.type() == QTransform::TxTranslate) {
return QString("translate(%1, %2)")
- .arg(toUserSpace(transform.dx()))
- .arg(toUserSpace(transform.dy()));
+ .arg(KisDomUtils::toString(toUserSpace(transform.dx())))
+ .arg(KisDomUtils::toString(toUserSpace(transform.dy())));
} else {
return QString("matrix(%1 %2 %3 %4 %5 %6)")
- .arg(transform.m11()).arg(transform.m12())
- .arg(transform.m21()).arg(transform.m22())
- .arg(toUserSpace(transform.dx()))
- .arg(toUserSpace(transform.dy()));
+ .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())));
}
}
-QRectF SvgUtil::parseViewBox(QString viewbox)
+bool SvgUtil::parseViewBox(SvgGraphicsContext *gc, const KoXmlElement &e,
+ const QRectF &elementBounds,
+ QRectF *_viewRect, QTransform *_viewTransform)
{
- QRectF viewboxRect;
+ Q_UNUSED(gc)
+
+ 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
- viewbox.remove("px");
+ viewBoxStr.remove("px");
- QStringList points = viewbox.replace(',', ' ').simplified().split(' ');
+ 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()));
+ 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");
+ if (!aspectString.isEmpty()) {
+ PreserveAspectRatioParser p(aspectString);
+ parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform);
}
- return viewboxRect;
+ *_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 = CM_TO_POINT(value);
+ value = ptToPx(gc, CM_TO_POINT(value));
else if (unit.right(2) == "pc")
- value = PI_TO_POINT(value);
+ value = ptToPx(gc, PI_TO_POINT(value));
else if (unit.right(2) == "mm")
- value = MM_TO_POINT(value);
+ value = ptToPx(gc, MM_TO_POINT(value));
else if (unit.right(2) == "in")
- value = INCH_TO_POINT(value);
+ value = ptToPx(gc, INCH_TO_POINT(value));
else if (unit.right(2) == "em")
- value = value * gc->font.pointSize();
+ value = ptToPx(gc, value * gc->font.pointSize());
else if (unit.right(2) == "ex") {
QFontMetrics metrics(gc->font);
- value = value * metrics.xHeight();
+ 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->currentBoundbox.width();
+ return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width();
} else {
- return SvgUtil::parseUnit(gc, unit, true, false, gc->currentBoundbox);
+ 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->currentBoundbox.height();
+ return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height();
} else {
- return SvgUtil::parseUnit(gc, unit, false, true, gc->currentBoundbox);
+ 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->currentBoundbox.width(), 2) + pow(gc->currentBoundbox.height(), 2)) / sqrt(2.0);
+ 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->currentBoundbox);
+ 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;
+}
+
+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/SvgUtil.h b/libs/flake/svg/SvgUtil.h
index 1b474fbc1d..5b16cb3c25 100644
--- a/libs/flake/svg/SvgUtil.h
+++ b/libs/flake/svg/SvgUtil.h
@@ -1,115 +1,141 @@
/* 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.
*/
#ifndef SVGUTIL_H
#define SVGUTIL_H
#include "kritaflake_export.h"
#include <QRectF>
class QString;
class SvgGraphicsContext;
class QTransform;
+class KoXmlElement;
class KRITAFLAKE_EXPORT SvgUtil
{
public:
- /**
- * Converts given value from userspace units to points.
- */
- static double fromUserSpace(double value);
- /**
- * Converts given value from points to userspace units.
- */
+ // remove later! pixels *are* user coordinates
+ static double fromUserSpace(double value);
static double toUserSpace(double value);
+ static double ptToPx(SvgGraphicsContext *gc, double value);
+
/// Converts given point from points to userspace units.
static QPointF toUserSpace(const QPointF &point);
/// Converts given rectangle from points to userspace units.
static QRectF toUserSpace(const QRectF &rect);
/// Converts given rectangle from points to userspace units.
static QSizeF toUserSpace(const QSizeF &size);
/**
* Parses the given string containing a percentage number.
* @param s the input string containing the percentage
* @return the percentage number normalized to 0..100
*/
static double toPercentage(QString s);
/**
* Parses the given string containing a percentage number.
* @param s the input string containing the percentage
* @return the percentage number normalized to 0..1
*/
static double fromPercentage(QString s);
/**
* Converts position from objectBoundingBox units to userSpace units.
*/
static QPointF objectToUserSpace(const QPointF &position, const QRectF &objectBound);
/**
* Converts size from objectBoundingBox units to userSpace units.
*/
static QSizeF objectToUserSpace(const QSizeF &size, const QRectF &objectBound);
/**
* Converts position from userSpace units to objectBoundingBox units.
*/
static QPointF userSpaceToObject(const QPointF &position, const QRectF &objectBound);
/**
* Converts size from userSpace units to objectBoundingBox units.
*/
static QSizeF userSpaceToObject(const QSizeF &size, const QRectF &objectBound);
- /**
- * Parses transform attribute value into a matrix.
- * @param transform the transform attribute value
- * @return the resulting transformation matrix
- */
- static QTransform parseTransform(const QString &transform);
-
/// Converts specified transformation to a string
static QString transformToString(const QTransform &transform);
/// Parses a viewbox attribute into an rectangle
- static QRectF parseViewBox(QString viewbox);
+ static bool parseViewBox(SvgGraphicsContext *gc, const KoXmlElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform);
+
+ struct PreserveAspectRatioParser;
+ static void parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewRect, QTransform *_viewTransform);
/// Parses a length attribute
static qreal parseUnit(SvgGraphicsContext *gc, const QString &, bool horiz = false, bool vert = false, const QRectF &bbox = QRectF());
/// parses a length attribute in x-direction
static qreal parseUnitX(SvgGraphicsContext *gc, const QString &unit);
/// parses a length attribute in y-direction
static qreal parseUnitY(SvgGraphicsContext *gc, const QString &unit);
/// parses a length attribute in xy-direction
static qreal parseUnitXY(SvgGraphicsContext *gc, const QString &unit);
+ /// parses angle, result in *radians*!
+ static qreal parseUnitAngular(SvgGraphicsContext *gc, const QString &unit);
+
/// parses the number into parameter number
static const char * parseNumber(const char *ptr, qreal &number);
+
+ static qreal parseNumber(const QString &string);
+
+ static QString mapExtendedShapeTag(const QString &tagName, const KoXmlElement &element);
+
+ struct PreserveAspectRatioParser
+ {
+ PreserveAspectRatioParser(const QString &str);
+
+ enum Alignment {
+ Min,
+ Middle,
+ Max
+ };
+
+ bool defer = false;
+ Qt::AspectRatioMode mode = Qt::IgnoreAspectRatio;
+ Alignment xAlignment = Min;
+ Alignment yAlignment = Min;
+
+ QPointF rectAnchorPoint(const QRectF &rc) const;
+
+ QString toString() const;
+
+ private:
+ Alignment alignmentFromString(const QString &str) const;
+ QString alignmentToString(Alignment alignment) const;
+ static qreal alignedValue(qreal min, qreal max, Alignment alignment);
+ };
};
#endif // SVGUTIL_H
diff --git a/libs/flake/svg/SvgWriter.cpp b/libs/flake/svg/SvgWriter.cpp
index 332a4a3d45..a63dfb4caa 100644
--- a/libs/flake/svg/SvgWriter.cpp
+++ b/libs/flake/svg/SvgWriter.cpp
@@ -1,251 +1,274 @@
/* 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>
SvgWriter::SvgWriter(const QList<KoShapeLayer*> &layers, const QSizeF &pageSize)
: m_pageSize(pageSize)
, m_writeInlineImages(true)
{
Q_FOREACH (KoShapeLayer *layer, layers)
m_toplevelShapes.append(layer);
}
SvgWriter::SvgWriter(const QList<KoShape*> &toplevelShapes, const QSizeF &pageSize)
: m_toplevelShapes(toplevelShapes)
, m_pageSize(pageSize)
, m_writeInlineImages(true)
{
}
SvgWriter::~SvgWriter()
{
}
bool SvgWriter::save(const QString &filename, bool writeInlineImages)
{
QFile fileOut(filename);
if (!fileOut.open(QIODevice::WriteOnly))
return false;
m_writeInlineImages = writeInlineImages;
const bool success = save(fileOut);
m_writeInlineImages = true;
fileOut.close();
return success;
}
bool SvgWriter::save(QIODevice &outputDevice)
{
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 Karbon, part of Calligra: http://www.calligra.org/karbon -->" << endl;
-
- svgStream << "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
- svgStream << " width=\"" << m_pageSize.width() << "pt\"";
- svgStream << " height=\"" << m_pageSize.height() << "pt\">" << endl;
+ svgStream << "<!-- Created using Krita: http://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=\"" << m_pageSize.width() << "pt\"\n";
+ svgStream << " height=\"" << m_pageSize.height() << "pt\"\n";
+ svgStream << " viewBox=\"0 0 "
+ << m_pageSize.width() << " " << m_pageSize.height()
+ << "\"";
+ svgStream << ">" << endl;
{
SvgSavingContext savingContext(outputDevice, m_writeInlineImages);
-
- // top level shapes
- Q_FOREACH (KoShape *shape, m_toplevelShapes) {
- 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);
- }
- }
+ 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;
+}
+
+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();
qSort(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));
context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(group->transformation()));
SvgStyleWriter::saveSvgStyle(group, context);
QList<KoShape*> sortedShapes = group->shapes();
qSort(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));
context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(path->transformation()));
SvgStyleWriter::saveSvgStyle(path, context);
context.shapeWriter().addAttribute("d", path->toString(context.userSpaceTransform()));
context.shapeWriter().endElement();
}
void SvgWriter::saveGeneric(KoShape *shape, SvgSavingContext &context)
{
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);
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().addAttributePt("x", bbox.x());
context.shapeWriter().addAttributePt("y", bbox.y());
context.shapeWriter().addAttributePt("width", bbox.width());
context.shapeWriter().addAttributePt("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
}
diff --git a/libs/flake/svg/SvgWriter.h b/libs/flake/svg/SvgWriter.h
index 10db54750b..43f59b4d64 100644
--- a/libs/flake/svg/SvgWriter.h
+++ b/libs/flake/svg/SvgWriter.h
@@ -1,75 +1,79 @@
/* This file is part of the KDE project
Copyright (C) 2002 Lars Siebold <khandha5@gmx.net>
Copyright (C) 2002 Werner Trobin <trobin@kde.org>
Copyright (C) 2002 Lennart Kudling <kudling@kde.org>
Copyright (C) 2002-2003,2005 Rob Buis <buis@kde.org>
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,2008 Jan Hambrecht <jaham@gmx.net>
Copyright (C) 2006 Inge Wallin <inge@lysator.liu.se>
Copyright (C) 2006 Laurent Montel <montel@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 SVGWRITER_H
#define SVGWRITER_H
#include "kritaflake_export.h"
#include <QList>
#include <QSizeF>
class SvgSavingContext;
class KoShapeLayer;
class KoShapeGroup;
class KoShape;
class KoPathShape;
class QIODevice;
class QString;
/// Implements exporting shapes to SVG
class KRITAFLAKE_EXPORT SvgWriter
{
public:
/// Creates svg writer to export specified layers
SvgWriter(const QList<KoShapeLayer*> &layers, const QSizeF &pageSize);
/// Creates svg writer to export specified shapes
SvgWriter(const QList<KoShape*> &toplevelShapes, const QSizeF &pageSize);
/// Destroys the svg writer
virtual ~SvgWriter();
/// Writes svg to specified output device
bool save(QIODevice &outputDevice);
/// Writes svg to the specified file
bool save(const QString &filename, bool writeInlineImages);
+ bool saveDetached(QIODevice &outputDevice);
+
private:
+ void saveShapes(const QList<KoShape*> shapes, SvgSavingContext &savingContext);
+
void saveLayer(KoShapeLayer *layer, SvgSavingContext &context);
void saveGroup(KoShapeGroup *group, SvgSavingContext &context);
void saveShape(KoShape *shape, SvgSavingContext &context);
void savePath(KoPathShape *path, SvgSavingContext &context);
void saveGeneric(KoShape *shape, SvgSavingContext &context);
QList<KoShape*> m_toplevelShapes;
QSizeF m_pageSize;
bool m_writeInlineImages;
};
#endif // SVGWRITER_H
diff --git a/libs/flake/svg/parsers/SvgTransformParser.cpp b/libs/flake/svg/parsers/SvgTransformParser.cpp
new file mode 100644
index 0000000000..f49e2f4cf6
--- /dev/null
+++ b/libs/flake/svg/parsers/SvgTransformParser.cpp
@@ -0,0 +1,278 @@
+/*
+ * 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 "SvgTransformParser.h"
+
+#include <QtGlobal>
+
+//#include "kis_debug.h"
+
+#include <boost/config/warning_disable.hpp>
+#include <boost/spirit/include/qi.hpp>
+#include <boost/spirit/include/phoenix_stl.hpp>
+#include <boost/spirit/include/phoenix_operator.hpp>
+#include <boost/spirit/include/phoenix_fusion.hpp>
+
+
+namespace Private
+{
+
+struct matrix
+{
+ qreal a = 0;
+ qreal b = 0;
+ qreal c = 0;
+ qreal d = 0;
+ qreal e = 0;
+ qreal f = 0;
+};
+
+struct translate
+{
+ qreal tx = 0.0;
+ qreal ty = 0.0;
+};
+
+struct scale
+{
+ qreal sx = 0;
+ qreal sy = 0;
+ bool syPresent = false;
+};
+
+struct rotate
+{
+ qreal angle = 0;
+ qreal cx = 0;
+ qreal cy = 0;
+};
+
+struct skewX
+{
+ qreal angle = 0;
+};
+
+struct skewY
+{
+ qreal angle = 0;
+};
+
+struct transform_unit
+{
+ transform_unit() {}
+
+ transform_unit(const matrix &m) {
+ transform = QTransform(m.a, m.b, m.c, m.d, m.e, m.f);
+ }
+
+ transform_unit(const translate &t) {
+ transform = QTransform::fromTranslate(t.tx, t.ty);
+ }
+
+ transform_unit(const scale &sc) {
+ transform =
+ QTransform::fromScale(sc.sx,
+ sc.syPresent ? sc.sy : sc.sx);
+ }
+
+ transform_unit(const rotate &r) {
+ transform.rotate(r.angle);
+ if (r.cx != 0.0 || r.cy != 0.0) {
+ transform =
+ QTransform::fromTranslate(-r.cx, -r.cy) *
+ transform *
+ QTransform::fromTranslate(r.cx, r.cy);
+ }
+ }
+
+ transform_unit(const skewX &sx) {
+ const qreal deg2rad = qreal(0.017453292519943295769);
+ const qreal value = tan(deg2rad * sx.angle);
+ transform.shear(value, 0);
+ }
+
+ transform_unit(const skewY &sy) {
+ const qreal deg2rad = qreal(0.017453292519943295769);
+ const qreal value = tan(deg2rad * sy.angle);
+ transform.shear(0, value);
+ }
+
+ QTransform transform;
+};
+}
+
+// We need to tell fusion about our transform_unit struct
+// to make it a first-class fusion citizen. This has to
+// be in global scope.
+
+BOOST_FUSION_ADAPT_STRUCT(
+ Private::matrix,
+ (qreal, a)
+ (qreal, b)
+ (qreal, c)
+ (qreal, d)
+ (qreal, e)
+ (qreal, f)
+ )
+
+BOOST_FUSION_ADAPT_STRUCT(
+ Private::translate,
+ (qreal, tx)
+ (qreal, ty)
+ )
+
+BOOST_FUSION_ADAPT_STRUCT(
+ Private::scale,
+ (qreal, sx)
+ (qreal, sy)
+ (bool, syPresent)
+ )
+
+BOOST_FUSION_ADAPT_STRUCT(
+ Private::rotate,
+ (qreal, angle)
+ (qreal, cx)
+ (qreal, cy)
+ )
+
+BOOST_FUSION_ADAPT_STRUCT(
+ Private::skewX,
+ (qreal, angle)
+ )
+
+BOOST_FUSION_ADAPT_STRUCT(
+ Private::skewY,
+ (qreal, angle)
+ )
+
+#define BOOST_SPIRIT_DEBUG 1
+
+namespace Private
+{
+ // Define our grammar
+
+ namespace qi = boost::spirit::qi;
+ namespace ascii = boost::spirit::ascii;
+
+ template <typename Iterator>
+ struct transform_unit_parser : qi::grammar<Iterator, std::vector<transform_unit>(), ascii::space_type>
+ {
+ transform_unit_parser() : transform_unit_parser::base_type(start)
+ {
+ namespace phoenix = boost::phoenix;
+ using qi::lit;
+ using qi::double_;
+ using ascii::char_;
+ using qi::cntrl;
+ using phoenix::at_c;
+ using phoenix::push_back;
+ using namespace qi::labels;
+
+
+ comma %= -char_(',');
+
+ matrix_rule %=
+ lit("matrix")
+ >> '('
+ >> double_ >> comma
+ >> double_ >> comma
+ >> double_ >> comma
+ >> double_ >> comma
+ >> double_ >> comma
+ >> double_ >> comma
+ >> ')';
+
+ translate_rule %=
+ lit("translate")
+ >> '(' >> double_ >> comma >> -double_ >> ')';
+
+ scale_rule %=
+ lit("scale")
+ >> '('
+ >> double_ >> comma
+ >> -double_ [at_c<2>(_val) = true]
+ >> ')';
+
+
+ // due to braces "-(...)" we cannot use automated
+ // semantic actions without relayouting the structure
+ rotate_rule =
+ lit("rotate")
+ >> '('
+ >> double_ [at_c<0>(_val) = _1]
+ >> comma
+ >> -(double_ [at_c<1>(_val) = _1]
+ >> comma
+ >> double_ [at_c<2>(_val) = _1])
+ >> ')';
+
+ skewX_rule %= lit("skewX") >> '(' >> double_ >> ')';
+ skewY_rule %= lit("skewY") >> '(' >> double_ >> ')';
+
+ start %=
+ (matrix_rule | translate_rule | scale_rule |
+ rotate_rule | skewX_rule | skewY_rule) %
+ (cntrl | comma);
+ }
+
+ qi::rule<Iterator, std::vector<transform_unit>(), ascii::space_type> start;
+ qi::rule<Iterator, translate(), ascii::space_type> translate_rule;
+ qi::rule<Iterator, matrix(), ascii::space_type> matrix_rule;
+ qi::rule<Iterator, scale(), ascii::space_type> scale_rule;
+ qi::rule<Iterator, rotate(), ascii::space_type> rotate_rule;
+ qi::rule<Iterator, skewX(), ascii::space_type> skewX_rule;
+ qi::rule<Iterator, skewY(), ascii::space_type> skewY_rule;
+ qi::rule<Iterator> comma;
+ };
+}
+
+
+SvgTransformParser::SvgTransformParser(const QString &_str)
+ : m_isValid(false)
+{
+ using boost::spirit::ascii::space;
+ typedef std::string::const_iterator iterator_type;
+ typedef Private::transform_unit_parser<iterator_type> transform_unit_parser;
+
+ transform_unit_parser g; // Our grammar
+ const std::string str = _str.toStdString();
+
+ std::vector<Private::transform_unit> transforms;
+ iterator_type iter = str.begin();
+ iterator_type end = str.end();
+ bool r = phrase_parse(iter, end, g, space, transforms);
+
+ if (r && iter == end) {
+ m_isValid = true;
+
+ for (const Private::transform_unit &t : transforms) {
+ m_transform = t.transform * m_transform;
+ }
+ }
+}
+bool SvgTransformParser::isValid() const
+{
+ return m_isValid;
+}
+
+QTransform SvgTransformParser::transform() const
+{
+ return m_transform;
+}
+
+
diff --git a/libs/flake/svg/parsers/SvgTransformParser.h b/libs/flake/svg/parsers/SvgTransformParser.h
new file mode 100644
index 0000000000..84bf56fcce
--- /dev/null
+++ b/libs/flake/svg/parsers/SvgTransformParser.h
@@ -0,0 +1,38 @@
+/*
+ * 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 SVGTRANSFORMPARSER_H
+#define SVGTRANSFORMPARSER_H
+
+#include <QTransform>
+#include "kritaflake_export.h"
+
+
+class KRITAFLAKE_EXPORT SvgTransformParser
+{
+public:
+ SvgTransformParser(const QString &str);
+ bool isValid() const;
+ QTransform transform() const;
+
+private:
+ bool m_isValid;
+ QTransform m_transform;
+};
+
+#endif // SVGTRANSFORMPARSER_H
diff --git a/libs/flake/tests/CMakeLists.txt b/libs/flake/tests/CMakeLists.txt
index 25d0e1f3ed..edb3516a4f 100644
--- a/libs/flake/tests/CMakeLists.txt
+++ b/libs/flake/tests/CMakeLists.txt
@@ -1,59 +1,94 @@
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
NAME_PREFIX "libs-kritaflake-"
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestPathShape.cpp
TEST_NAME libs-kritaflake-TestPathShape
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestControlPointMoveCommand.cpp
TEST_NAME libs-kritaflake-TestControlPointMoveCommand
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestPointTypeCommand.cpp
TEST_NAME libs-kritaflake-TestPointTypeCommand
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestPointRemoveCommand.cpp
TEST_NAME libs-kritaflake-TestPointRemoveCommand
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestRemoveSubpathCommand.cpp
TEST_NAME libs-kritaflake-TestRemoveSubpathCommand
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestPathSegment.cpp
TEST_NAME libs-kritaflake-TestPathSegment
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestSegmentTypeCommand.cpp
TEST_NAME libs-kritaflake-TestSegmentTypeCommand
LINK_LIBRARIES kritaflake Qt5::Test)
krita_add_broken_unit_test(TestPointMergeCommand.cpp
TEST_NAME libs-kritaflake-TestPointMergeCommand
LINK_LIBRARIES kritaflake Qt5::Test)
+
+ecm_add_test(
+ TestKoDrag.cpp
+ TEST_NAME libs-kritaflake-TestKoDrag
+ LINK_LIBRARIES kritaflake Qt5::Test
+)
+
+ecm_add_test(
+ TestKoMarkerCollection.cpp
+ TEST_NAME libs-kritaflake-TestKoMarkerCollection
+ LINK_LIBRARIES kritaflake Qt5::Test
+)
+
+ecm_add_test(
+ TestSvgParser.cpp
+ TEST_NAME libs-kritaflake-TestSvgParser
+ LINK_LIBRARIES kritaflake Qt5::Test
+)
+
+ecm_add_test(
+ TestSvgParser.cpp
+ TEST_NAME libs-kritaflake-TestSvgParserCloned
+ LINK_LIBRARIES kritaflake Qt5::Test
+)
+set_property(TARGET libs-kritaflake-TestSvgParserCloned
+ PROPERTY COMPILE_DEFINITIONS USE_CLONED_SHAPES)
+
+ecm_add_test(
+ TestSvgParser.cpp
+ TEST_NAME libs-kritaflake-TestSvgParserRoundTrip
+ LINK_LIBRARIES kritaflake Qt5::Test
+)
+set_property(TARGET libs-kritaflake-TestSvgParserRoundTrip
+ PROPERTY COMPILE_DEFINITIONS USE_ROUND_TRIP)
diff --git a/libs/flake/tests/MockShapes.h b/libs/flake/tests/MockShapes.h
index c4878b8a59..be291ee737 100644
--- a/libs/flake/tests/MockShapes.h
+++ b/libs/flake/tests/MockShapes.h
@@ -1,220 +1,255 @@
/*
* 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 <KoShapeBasedDocumentBase.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 &) {
Q_UNUSED(painter);
Q_UNUSED(converter);
//qDebug() << "Shape" << kBacktrace( 10 );
paintedCount++;
}
virtual void saveOdf(KoShapeSavingContext &) const {}
virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) {
return true;
}
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 &) {
Q_UNUSED(painter);
Q_UNUSED(converter);
//qDebug() << "Container:" << kBacktrace( 10 );
paintedCount++;
}
virtual void saveOdf(KoShapeSavingContext &) const {}
virtual bool loadOdf(const KoXmlElement &, KoShapeLoadingContext &) {
return true;
}
int paintedCount;
};
class KRITAFLAKE_EXPORT MockGroup : public KoShapeGroup
{
void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &) {
Q_UNUSED(painter);
Q_UNUSED(converter);
}
};
class KoToolProxy;
+class KRITAFLAKE_EXPORT MockShapeController : public KoShapeBasedDocumentBase
+{
+public:
+ void addShapes(const QList<KoShape*> shapes) {
+ Q_FOREACH (KoShape *shape, shapes) {
+ m_shapes.insert(shape);
+ if (m_shapeManager) {
+ m_shapeManager->addShape(shape);
+ }
+ }
+ }
+ void removeShape(KoShape* shape) {
+ 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 {
+ return QRectF(0,0,100,100);
+ }
+
+ qreal pixelsPerInch() const {
+ return 72.0;
+ }
+
+private:
+ QSet<KoShape * > m_shapes;
+ KoShapeManager *m_shapeManager = 0;
+};
+
class KRITAFLAKE_EXPORT MockCanvas : public KoCanvasBase
{
Q_OBJECT
public:
MockCanvas(KoShapeBasedDocumentBase *aKoShapeBasedDocumentBase =0)//made for TestSnapStrategy.cpp
- : KoCanvasBase(aKoShapeBasedDocumentBase), m_shapeManager(new KoShapeManager(this)) {}
+ : KoCanvasBase(aKoShapeBasedDocumentBase),
+ m_shapeManager(new KoShapeManager(this)),
+ m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
+ {
+ if (MockShapeController *controller = dynamic_cast<MockShapeController*>(aKoShapeBasedDocumentBase)) {
+ controller->setShapeManager(m_shapeManager.data());
+ }
+ }
+
~MockCanvas() {}
void setHorz(qreal pHorz){
m_horz = pHorz;
}
void setVert(qreal pVert){
m_vert = pVert;
}
void gridSize(QPointF *offset, QSizeF *spacing) const {
Q_UNUSED(offset);
spacing->setWidth(m_horz);
spacing->setHeight(m_vert);
}
bool snapToGrid() const {
return true;
}
void addCommand(KUndo2Command*) { }
KoShapeManager *shapeManager() const {
- return m_shapeManager;
+ return m_shapeManager.data();
+ }
+ KoSelectedShapesProxy *selectedShapesProxy() const {
+ return m_selectedShapesProxy.data();
}
void updateCanvas(const QRectF&) {}
KoToolProxy * toolProxy() const {
return 0;
}
KoViewConverter *viewConverter() const {
return 0;
}
QWidget* canvasWidget() {
return 0;
}
const QWidget* canvasWidget() const {
return 0;
}
KoUnit unit() const {
return KoUnit(KoUnit::Millimeter);
}
void updateInputMethodInfo() {}
void setCursor(const QCursor &) {}
private:
- KoShapeManager *m_shapeManager;
+ QScopedPointer<KoShapeManager> m_shapeManager;
+ QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
qreal m_horz;
qreal m_vert;
};
-class KRITAFLAKE_EXPORT MockShapeController : public KoShapeBasedDocumentBase
-{
-public:
- void addShape(KoShape* shape) {
- m_shapes.insert(shape);
- }
- void removeShape(KoShape* shape) {
- m_shapes.remove(shape);
- }
- bool contains(KoShape* shape) {
- return m_shapes.contains(shape);
- }
-private:
- QSet<KoShape * > m_shapes;
-};
-
-class MockContainerModel : public KoShapeContainerModel
+class KRITAFLAKE_EXPORT MockContainerModel : public KoShapeContainerModel
{
public:
MockContainerModel() {
resetCounts();
}
/// reimplemented
void add(KoShape *child) {
m_children.append(child); // note that we explicitly do not check for duplicates here!
}
/// reimplemented
void remove(KoShape *child) {
m_children.removeAll(child);
}
/// reimplemented
void setClipped(const KoShape *, bool) { } // ignored
/// reimplemented
bool isClipped(const KoShape *) const {
return false;
}// ignored
/// reimplemented
bool isChildLocked(const KoShape *child) const {
return child->isGeometryProtected();
}
/// reimplemented
int count() const {
return m_children.count();
}
/// reimplemented
QList<KoShape*> shapes() const {
return m_children;
}
/// reimplemented
void containerChanged(KoShapeContainer *, KoShape::ChangeType) {
m_containerChangedCalled++;
}
/// reimplemented
void proposeMove(KoShape *, QPointF &) {
m_proposeMoveCalled++;
}
/// reimplemented
void childChanged(KoShape *, KoShape::ChangeType) {
m_childChangedCalled++;
}
void setInheritsTransform(const KoShape *, bool) {
}
bool inheritsTransform(const KoShape *) const {
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/TestKoClipMaskPainter.cpp b/libs/flake/tests/TestKoClipMaskPainter.cpp
new file mode 100644
index 0000000000..49c9873f3c
--- /dev/null
+++ b/libs/flake/tests/TestKoClipMaskPainter.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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 "TestKoClipMaskPainter.h"
+
+#include <QTest>
+#include <KoClipMaskPainter.h>
+#include <QPainter>
+
+#include <kis_debug.h>
+
+#include "../../sdk/tests/qimage_test_util.h"
+
+
+void drawRect(QPainter *gc, const QRect &rc, const QColor &color)
+{
+ gc->save();
+
+ gc->setBrush(color);
+ gc->drawRect(rc);
+
+ gc->restore();
+}
+
+
+void TestKoClipMaskPainter::test()
+{
+ QImage canvas(30, 30, QImage::Format_ARGB32);
+ canvas.fill(0);
+
+ const QRect rect1(4,4,15,15);
+ const QRect clipRect1(4,4,13,15);
+ const QRect rect2(10,10,15,15);
+
+ QTransform t;
+ t.rotate(90);
+ t = t * QTransform::fromTranslate(30, 0);
+
+ QPainter painter(&canvas);
+ painter.setPen(Qt::NoPen);
+ painter.setTransform(t);
+
+ painter.setClipRect(clipRect1);
+
+ KoClipMaskPainter clipPainter(&painter, painter.transform().mapRect(rect1));
+
+ // please debug using:
+ //drawRect(&painter, rect1, Qt::blue);
+ //drawRect(&painter, rect2, Qt::red);
+
+ drawRect(clipPainter.shapePainter(), rect1, Qt::blue);
+ drawRect(clipPainter.maskPainter(), rect2, Qt::red);
+
+ clipPainter.renderOnGlobalPainter();
+
+ QVERIFY(TestUtil::checkQImage(canvas, "", "clip_mask", "render_mask"));
+}
+
+QTEST_MAIN(TestKoClipMaskPainter)
diff --git a/libs/flake/tests/TestKoClipMaskPainter.h b/libs/flake/tests/TestKoClipMaskPainter.h
new file mode 100644
index 0000000000..7c064dbc9a
--- /dev/null
+++ b/libs/flake/tests/TestKoClipMaskPainter.h
@@ -0,0 +1,33 @@
+/*
+ * 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 TESTKOCLIPMASKPAINTER_H
+#define TESTKOCLIPMASKPAINTER_H
+
+#include <QtTest>
+
+class TestKoClipMaskPainter : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void test();
+};
+
+
+
+#endif // TESTKOCLIPMASKPAINTER_H
diff --git a/libs/flake/tests/TestKoDrag.cpp b/libs/flake/tests/TestKoDrag.cpp
new file mode 100644
index 0000000000..543f40bf83
--- /dev/null
+++ b/libs/flake/tests/TestKoDrag.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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;
+ doc.setContent(testShapes.readAll());
+
+ 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(4,3,24,24));
+ }
+
+ 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(54, 53));
+ QCOMPARE(KoShape::boundingRect(newShapes).toAlignedRect(), QRect(4,3,24,24));
+ }
+
+
+ qDeleteAll(shapes);
+ qDeleteAll(newShapes);
+}
+
+
+QTEST_MAIN(TestKoDrag)
diff --git a/libs/flake/tests/TestKoDrag.h b/libs/flake/tests/TestKoDrag.h
new file mode 100644
index 0000000000..edb6a58083
--- /dev/null
+++ b/libs/flake/tests/TestKoDrag.h
@@ -0,0 +1,32 @@
+/*
+ * 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 TESTKODRAG_H
+#define TESTKODRAG_H
+
+#include <QObject>
+#include <QTest>
+
+class TestKoDrag : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void test();
+};
+
+#endif // TESTKODRAG_H
diff --git a/libs/flake/tests/TestKoMarkerCollection.cpp b/libs/flake/tests/TestKoMarkerCollection.cpp
new file mode 100644
index 0000000000..fe9bd06f43
--- /dev/null
+++ b/libs/flake/tests/TestKoMarkerCollection.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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 <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->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");
+}
+
+QTEST_MAIN(TestKoMarkerCollection)
diff --git a/libs/flake/tests/TestKoMarkerCollection.h b/libs/flake/tests/TestKoMarkerCollection.h
new file mode 100644
index 0000000000..9a8254c6c1
--- /dev/null
+++ b/libs/flake/tests/TestKoMarkerCollection.h
@@ -0,0 +1,33 @@
+/*
+ * 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 TESTKOMARKERCOLLECTION_H
+#define TESTKOMARKERCOLLECTION_H
+
+#include <QtTest>
+
+class TestKoMarkerCollection : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testLoadMarkersFromFile();
+ void testDeduplication();
+ void testMarkerBounds();
+};
+
+#endif // TESTKOMARKERCOLLECTION_H
diff --git a/libs/flake/tests/TestPointMergeCommand.cpp b/libs/flake/tests/TestPointMergeCommand.cpp
index a4e6b122c8..85f6f0f7c8 100644
--- a/libs/flake/tests/TestPointMergeCommand.cpp
+++ b/libs/flake/tests/TestPointMergeCommand.cpp
@@ -1,249 +1,564 @@
/* 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 <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(30,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(30,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(30,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(30,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"
+#include <KoPathPointData.h>
+
+void TestPointMergeCommand::testCombineShapes()
+{
+ 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.addRect(rect);
+
+ KoPathShape *shape = KoPathShape::createShapeFromPainterPath(p);
+ QCOMPARE(shape->absoluteOutlineRect(), rect);
+
+ shapes << shape;
+ mockController.addShape(shape);
+ }
+
+ KoPathCombineCommand cmd(&mockController, shapes);
+ 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(shapes[0], KoPathPointIndex(0,1));
+ expPoints << KoPathPointData(combinedShape, KoPathPointIndex(0,1));
+
+ tstPoints << KoPathPointData(shapes[1], KoPathPointIndex(0,2));
+ expPoints << KoPathPointData(combinedShape, KoPathPointIndex(1,2));
+
+ tstPoints << KoPathPointData(shapes[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]);
+ }
+
+ qDeleteAll(canvas.shapeManager()->shapes());
+}
+
+#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(
+ 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()));
+ 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);
+ }
+}
+
+
+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(15,15),
+ QPointF(10,5)
+ }, true);
+}
+
+void TestPointMergeCommand::testMultipathMergeShapesSingleShapeStartToEnd()
+{
+ // close start->end
+ testMultipathMergeShapesImpl(0, 2,
+ {
+ QPointF(15,15),
+ QPointF(10,5)
+ }, 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);
+}
+
+
+
QTEST_MAIN(TestPointMergeCommand)
diff --git a/libs/flake/tests/TestPointMergeCommand.h b/libs/flake/tests/TestPointMergeCommand.h
index 197230863a..fc58322ce8 100644
--- a/libs/flake/tests/TestPointMergeCommand.h
+++ b/libs/flake/tests/TestPointMergeCommand.h
@@ -1,35 +1,53 @@
/* 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.
*/
#ifndef TESTPOINTMERGECOMMAND_H
#define TESTPOINTMERGECOMMAND_H
#include <QObject>
class TestPointMergeCommand : public QObject
{
Q_OBJECT
private Q_SLOTS:
void closeSingleLinePath();
void closeSingleCurvePath();
void connectLineSubpaths();
void connectCurveSubpaths();
+
+ void testCombineShapes();
+ void testMultipathMergeShapesBothSequential();
+ void testMultipathMergeShapesFirstReversed();
+ void testMultipathMergeShapesSecondReversed();
+ void testMultipathMergeShapesBothReversed();
+
+ void testMultipathMergeShapesSingleShapeEndToStart();
+ void testMultipathMergeShapesSingleShapeStartToEnd();
+
+ void testMultipathJoinShapesBothSequential();
+ void testMultipathJoinShapesFirstReversed();
+ void testMultipathJoinShapesSecondReversed();
+ void testMultipathJoinShapesBothReversed();
+
+ void testMultipathJoinShapesSingleShapeEndToStart();
+ void testMultipathJoinShapesSingleShapeStartToEnd();
+
};
#endif // TESTPOINTMERGECOMMAND_H
diff --git a/libs/flake/tests/TestPosition.cpp b/libs/flake/tests/TestPosition.cpp
index 124fd481a5..7fc788445f 100644
--- a/libs/flake/tests/TestPosition.cpp
+++ b/libs/flake/tests/TestPosition.cpp
@@ -1,167 +1,167 @@
/*
* 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 "TestPosition.h"
#include <MockShapes.h>
#include <QPointF>
#include <QTest>
TestPosition::TestPosition()
: shape1(0),
shape2(0),
childShape1(0),
childShape2(0),
container(0),
container2(0)
{
}
void TestPosition::init()
{
shape1 = new MockShape();
shape1->setPosition(QPointF(50, 50));
shape1->setSize(QSize(50, 50));
shape2 = new MockShape();
shape2->setPosition(QPointF(20, 20));
shape2->setSize(QSize(50, 50));
childShape1 = new MockShape();
childShape1->setPosition(QPointF(20, 20));
childShape1->setSize(QSize(50, 50));
container = new MockContainer();
container->setPosition(QPointF(100, 100));
container->addShape(childShape1);
container->setInheritsTransform(childShape1, false);
childShape2 = new MockShape();
childShape2->setPosition(QPointF(25, 25));
childShape2->setSize(QSizeF(10, 15));
container2 = new MockContainer();
container2->setPosition(QPointF(100, 200));
container2->setSize(QSizeF(100, 100));
container2->rotate(90);
container2->addShape(childShape2);
}
void TestPosition::cleanup()
{
delete container;
delete container2;
delete shape1;
delete shape2;
}
void TestPosition::testBasePosition()
{
// internal consistency tests.
QCOMPARE(shape1->position(), QPointF(50, 50));
QCOMPARE(shape2->position(), QPointF(20, 20));
QCOMPARE(childShape1->position(), QPointF(20, 20));
QCOMPARE(container->position(), QPointF(100, 100));
}
void TestPosition::testAbsolutePosition()
{
QCOMPARE(shape1->absolutePosition(), QPointF(75, 75));
QCOMPARE(shape2->absolutePosition(), QPointF(45, 45));
// translated
QCOMPARE(childShape1->absolutePosition(), QPointF(100 + 20 + 25, 100 + 20 + 25));
// rotated
container2->setInheritsTransform(childShape2, false);
QCOMPARE(container2->absolutePosition(), QPointF(150, 250));
QCOMPARE(childShape2->absolutePosition(), QPointF(130, 232.5));
container2->setInheritsTransform(childShape2, true);
QCOMPARE(childShape2->absolutePosition(), QPointF(167.5, 230));
shape1->rotate(90);
shape1->setPosition(QPointF(10, 10));
QCOMPARE(shape1->absolutePosition(), QPointF(10 + 25, 10 + 25));
- QCOMPARE(shape1->absolutePosition(KoFlake::CenteredPosition), QPointF(10 + 25, 10 + 25));
- QCOMPARE(shape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(10 + 50, 10));
- QCOMPARE(shape1->absolutePosition(KoFlake::BottomRightCorner), QPointF(10, 10 + 50));
+ QCOMPARE(shape1->absolutePosition(KoFlake::Center), QPointF(10 + 25, 10 + 25));
+ QCOMPARE(shape1->absolutePosition(KoFlake::TopLeft), QPointF(10 + 50, 10));
+ QCOMPARE(shape1->absolutePosition(KoFlake::BottomRight), QPointF(10, 10 + 50));
- QCOMPARE(container2->absolutePosition(KoFlake::TopLeftCorner), QPointF(200, 200));
+ QCOMPARE(container2->absolutePosition(KoFlake::TopLeft), QPointF(200, 200));
}
void TestPosition::testSetAbsolutePosition()
{
shape1->rotate(-90); // rotate back as we are rotating relative
shape1->setPosition(QPointF(10, 10));
QCOMPARE(shape1->absolutePosition(), QPointF(10 + 25, 10 + 25));
shape1->setAbsolutePosition(QPointF(10, 10));
QCOMPARE(shape1->absolutePosition(), QPointF(10, 10));
shape1->rotate(45);
QCOMPARE(shape1->absolutePosition(), QPointF(10, 10));
childShape1->setAbsolutePosition(QPointF(0, 0));
QCOMPARE(childShape1->position(), QPointF(-125, -125));
QCOMPARE(childShape1->absolutePosition(), QPointF(0, 0));
QCOMPARE(container2->position(), QPointF(100, 200)); // make sure nobody changed it
container2->setInheritsTransform(childShape2, false);
childShape2->setAbsolutePosition(QPointF(0, 0));
QCOMPARE(childShape2->position(), QPointF(-100 - 5, -200 - 7.5));
QCOMPARE(childShape2->absolutePosition(), QPointF(0, 0));
container2->setInheritsTransform(childShape2, true);
childShape2->setAbsolutePosition(QPointF(0, 0));
QCOMPARE(childShape2->absolutePosition(), QPointF(0, 0));
QCOMPARE(childShape2->position(), QPointF(-200 - 5, 200 - 7.5));
}
void TestPosition::testSetAbsolutePosition2()
{
shape1->rotate(90);
shape1->setAbsolutePosition(QPointF(100, 100));
QCOMPARE(shape1->absolutePosition(), QPointF(100, 100));
- shape1->setAbsolutePosition(QPointF(100, 100), KoFlake::TopLeftCorner);
- QCOMPARE(shape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(100, 100));
+ shape1->setAbsolutePosition(QPointF(100, 100), KoFlake::TopLeft);
+ QCOMPARE(shape1->absolutePosition(KoFlake::TopLeft), QPointF(100, 100));
- childShape1->setAbsolutePosition(QPointF(0, 0), KoFlake::BottomRightCorner);
+ childShape1->setAbsolutePosition(QPointF(0, 0), KoFlake::BottomRight);
QCOMPARE(childShape1->position(), QPointF(-150, -150));
- childShape1->setAbsolutePosition(QPointF(0, 0), KoFlake::BottomLeftCorner);
+ childShape1->setAbsolutePosition(QPointF(0, 0), KoFlake::BottomLeft);
QCOMPARE(childShape1->position(), QPointF(-100, -150));
- childShape1->setAbsolutePosition(QPointF(0, 0), KoFlake::TopRightCorner);
+ childShape1->setAbsolutePosition(QPointF(0, 0), KoFlake::TopRight);
QCOMPARE(childShape1->position(), QPointF(-150, -100));
container2->setInheritsTransform(childShape2, true);
- childShape2->setAbsolutePosition(QPointF(0, 0), KoFlake::TopLeftCorner);
+ childShape2->setAbsolutePosition(QPointF(0, 0), KoFlake::TopLeft);
QCOMPARE(childShape2->position(), QPointF(-200, 200));
}
void TestPosition::testSetAndGetRotation()
{
shape1->rotate(180);
QCOMPARE(shape1->rotation(), 180.0);
shape1->rotate(2);
QCOMPARE(shape1->rotation(), 182.0);
shape1->rotate(4);
QCOMPARE(shape1->rotation(), 186.0);
shape1->rotate(358);
QCOMPARE(shape1->rotation(), 184.0);
}
QTEST_GUILESS_MAIN(TestPosition)
diff --git a/libs/flake/tests/TestResourceManager.cpp b/libs/flake/tests/TestResourceManager.cpp
index ef3122cb2c..cb6a7fbaf3 100644
--- a/libs/flake/tests/TestResourceManager.cpp
+++ b/libs/flake/tests/TestResourceManager.cpp
@@ -1,389 +1,389 @@
/* This file is part of the KDE project
Copyright (C) 2008 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 "TestResourceManager.h"
#include "KoCanvasResourceManager.h"
#include "KoResourceManager_p.h"
#include "KoPathShape.h"
#include "KoUnit.h"
#include <QSignalSpy>
#include <QTest>
#include "kis_debug.h"
void TestResourceManager::koShapeResource()
{
KoPathShape * shape = new KoPathShape();
int key = 9001;
KoCanvasResourceManager rp( 0 );
rp.setResource( key, shape );
QVERIFY( shape == rp.koShapeResource( key ) );
}
void TestResourceManager::testUnitChanged()
{
KoCanvasResourceManager rm(0);
QSignalSpy spy(&rm, SIGNAL(canvasResourceChanged(int, const QVariant &)));
KoUnit a;
rm.setResource(KoCanvasResourceManager::Unit, a);
QCOMPARE(spy.count(), 1);
KoUnit b(KoUnit::Millimeter);
rm.setResource(KoCanvasResourceManager::Unit, b);
QCOMPARE(spy.count(), 2);
}
-#include "kis_global.h"
+#include "kis_pointer_utils.h"
struct DerivedResource : public KoDerivedResourceConverter
{
DerivedResource(int key, int sourceKey) : KoDerivedResourceConverter(key, sourceKey) {}
QVariant fromSource(const QVariant &value) override {
return value.toInt() + 10;
}
QVariant toSource(const QVariant &value, const QVariant &sourceValue) override {
Q_UNUSED(sourceValue);
return value.toInt() - 10;
}
};
void TestResourceManager::testConverters()
{
KoResourceManager m;
const int key1 = 1;
const int key2 = 2;
const int derivedKey = 3;
m.setResource(key1, 1);
m.setResource(key2, 2);
QCOMPARE(m.resource(key1).toInt(), 1);
QCOMPARE(m.resource(key2).toInt(), 2);
QVERIFY(!m.hasResource(derivedKey));
m.addDerivedResourceConverter(toQShared(new DerivedResource(derivedKey, key2)));
QCOMPARE(m.resource(derivedKey).toInt(), 12);
QVERIFY(m.hasResource(derivedKey));
m.setResource(derivedKey, 15);
QCOMPARE(m.resource(key2).toInt(), 5);
QCOMPARE(m.resource(derivedKey).toInt(), 15);
QVERIFY(m.hasResource(derivedKey));
m.clearResource(derivedKey);
QVERIFY(m.hasResource(derivedKey));
m.clearResource(key2);
QVERIFY(!m.hasResource(derivedKey));
}
void TestResourceManager::testDerivedChanged()
{
// const int key1 = 1;
const int key2 = 2;
const int derivedKey = 3;
const int otherDerivedKey = 4;
KoCanvasResourceManager m(0);
m.addDerivedResourceConverter(toQShared(new DerivedResource(derivedKey, key2)));
m.addDerivedResourceConverter(toQShared(new DerivedResource(otherDerivedKey, key2)));
m.setResource(derivedKey, 15);
QCOMPARE(m.resource(key2).toInt(), 5);
QCOMPARE(m.resource(derivedKey).toInt(), 15);
QSignalSpy spy(&m, SIGNAL(canvasResourceChanged(int, const QVariant &)));
m.setResource(derivedKey, 16);
QCOMPARE(spy.count(), 3);
QList<QVariant> args;
args = spy[0];
QCOMPARE(args[0].toInt(), derivedKey);
QCOMPARE(args[1].toInt(), 16);
args = spy[1];
QCOMPARE(args[0].toInt(), key2);
QCOMPARE(args[1].toInt(), 6);
args = spy[2];
QCOMPARE(args[0].toInt(), otherDerivedKey);
QCOMPARE(args[1].toInt(), 16);
spy.clear();
m.setResource(key2, 7);
QCOMPARE(spy.count(), 3);
args = spy[0];
QCOMPARE(args[0].toInt(), key2);
QCOMPARE(args[1].toInt(), 7);
args = spy[1];
QCOMPARE(args[0].toInt(), otherDerivedKey);
QCOMPARE(args[1].toInt(), 17);
args = spy[2];
QCOMPARE(args[0].toInt(), derivedKey);
QCOMPARE(args[1].toInt(), 17);
}
struct ComplexResource {
QHash<int, QVariant> m_resources;
};
typedef QSharedPointer<ComplexResource> ComplexResourceSP;
Q_DECLARE_METATYPE(ComplexResourceSP);
struct ComplexConverter : public KoDerivedResourceConverter
{
ComplexConverter(int key, int sourceKey) : KoDerivedResourceConverter(key, sourceKey) {}
QVariant fromSource(const QVariant &value) override {
KIS_ASSERT(value.canConvert<ComplexResourceSP>());
ComplexResourceSP res = value.value<ComplexResourceSP>();
return res->m_resources[key()];
}
QVariant toSource(const QVariant &value, const QVariant &sourceValue) override {
KIS_ASSERT(sourceValue.canConvert<ComplexResourceSP>());
ComplexResourceSP res = sourceValue.value<ComplexResourceSP>();
res->m_resources[key()] = value;
return QVariant::fromValue(res);
}
};
struct ComplexMediator : public KoResourceUpdateMediator
{
ComplexMediator(int key) : KoResourceUpdateMediator(key) {}
void connectResource(QVariant sourceResource) override {
m_res = sourceResource;
}
void forceNotify() {
emit sigResourceChanged(key());
}
QVariant m_res;
};
typedef QSharedPointer<ComplexMediator> ComplexMediatorSP;
void TestResourceManager::testComplexResource()
{
const int key = 2;
const int complex1 = 3;
const int complex2 = 4;
KoCanvasResourceManager m(0);
m.addDerivedResourceConverter(toQShared(new ComplexConverter(complex1, key)));
m.addDerivedResourceConverter(toQShared(new ComplexConverter(complex2, key)));
ComplexMediatorSP mediator(new ComplexMediator(key));
m.addResourceUpdateMediator(mediator);
QSignalSpy spy(&m, SIGNAL(canvasResourceChanged(int, const QVariant &)));
ComplexResourceSP r1(new ComplexResource());
r1->m_resources[complex1] = 10;
r1->m_resources[complex2] = 20;
ComplexResourceSP r2(new ComplexResource());
r2->m_resources[complex1] = 15;
r2->m_resources[complex2] = 25;
// ####################################################
// Initial assignment
// ####################################################
m.setResource(key, QVariant::fromValue(r1));
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(complex1).toInt(), 10);
QCOMPARE(m.resource(complex2).toInt(), 20);
QCOMPARE(spy[0][0].toInt(), key);
QCOMPARE(spy[0][1].value<ComplexResourceSP>(), r1);
QCOMPARE(spy[1][0].toInt(), complex2);
QCOMPARE(spy[1][1].toInt(), 20);
QCOMPARE(spy[2][0].toInt(), complex1);
QCOMPARE(spy[2][1].toInt(), 10);
spy.clear();
// ####################################################
// Change the whole resource
// ####################################################
m.setResource(key, QVariant::fromValue(r2));
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(complex1).toInt(), 15);
QCOMPARE(m.resource(complex2).toInt(), 25);
QCOMPARE(spy[0][0].toInt(), key);
QCOMPARE(spy[0][1].value<ComplexResourceSP>(), r2);
QCOMPARE(spy[1][0].toInt(), complex2);
QCOMPARE(spy[1][1].toInt(), 25);
QCOMPARE(spy[2][0].toInt(), complex1);
QCOMPARE(spy[2][1].toInt(), 15);
spy.clear();
// ####################################################
// Change a derived resource
// ####################################################
m.setResource(complex1, 16);
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(complex1).toInt(), 16);
QCOMPARE(m.resource(complex2).toInt(), 25);
QCOMPARE(spy[0][0].toInt(), complex1);
QCOMPARE(spy[0][1].toInt(), 16);
spy.clear();
// ####################################################
// Change another derived resource
// ####################################################
m.setResource(complex2, 26);
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(complex1).toInt(), 16);
QCOMPARE(m.resource(complex2).toInt(), 26);
QCOMPARE(spy[0][0].toInt(), complex2);
QCOMPARE(spy[0][1].toInt(), 26);
spy.clear();
// ####################################################
// Switch back the whole source resource
// ####################################################
m.setResource(key, QVariant::fromValue(r1));
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(complex1).toInt(), 10);
QCOMPARE(m.resource(complex2).toInt(), 20);
QCOMPARE(spy[0][0].toInt(), key);
QCOMPARE(spy[0][1].value<ComplexResourceSP>(), r1);
QCOMPARE(spy[1][0].toInt(), complex2);
QCOMPARE(spy[1][1].toInt(), 20);
QCOMPARE(spy[2][0].toInt(), complex1);
QCOMPARE(spy[2][1].toInt(), 10);
spy.clear();
// ####################################################
// The value keep unchanged case!
// ####################################################
m.setResource(complex1, 10);
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(complex1).toInt(), 10);
QCOMPARE(m.resource(complex2).toInt(), 20);
QCOMPARE(spy.size(), 0);
spy.clear();
// ####################################################
// While switching a complex resource one derived value
// is kept unchanged
// ####################################################
r2->m_resources[complex1] = 10;
m.setResource(key, QVariant::fromValue(r2));
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r2);
QCOMPARE(m.resource(complex1).toInt(), 10);
QCOMPARE(m.resource(complex2).toInt(), 26);
QCOMPARE(spy[0][0].toInt(), key);
QCOMPARE(spy[0][1].value<ComplexResourceSP>(), r2);
QCOMPARE(spy[1][0].toInt(), complex2);
QCOMPARE(spy[1][1].toInt(), 26);
spy.clear();
// ####################################################
// No devived values are changed!
// ####################################################
*r1 = *r2;
m.setResource(key, QVariant::fromValue(r1));
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(complex1).toInt(), 10);
QCOMPARE(m.resource(complex2).toInt(), 26);
QCOMPARE(spy[0][0].toInt(), key);
QCOMPARE(spy[0][1].value<ComplexResourceSP>(), r1);
spy.clear();
// ####################################################
// Try to set the same pointer. No signals emitted!
// ####################################################
m.setResource(key, QVariant::fromValue(r1));
QCOMPARE(mediator->m_res.value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(key).value<ComplexResourceSP>(), r1);
QCOMPARE(m.resource(complex1).toInt(), 10);
QCOMPARE(m.resource(complex2).toInt(), 26);
QCOMPARE(spy.size(), 0);
spy.clear();
// ####################################################
// Internals 'officially' changed, but the values not
// ####################################################
mediator->forceNotify();
QCOMPARE(spy.size(), 0);
spy.clear();
// ####################################################
// We changed the values, but didn't notify anyone :)
// ####################################################
r1->m_resources[complex1] = 11;
r1->m_resources[complex2] = 21;
mediator->forceNotify();
QCOMPARE(spy[0][0].toInt(), complex2);
QCOMPARE(spy[0][1].toInt(), 21);
QCOMPARE(spy[1][0].toInt(), complex1);
QCOMPARE(spy[1][1].toInt(), 11);
spy.clear();
}
QTEST_MAIN(TestResourceManager)
diff --git a/libs/flake/tests/TestSelection.cpp b/libs/flake/tests/TestSelection.cpp
index b7848e2725..0b7a5bdc77 100644
--- a/libs/flake/tests/TestSelection.cpp
+++ b/libs/flake/tests/TestSelection.cpp
@@ -1,131 +1,111 @@
/*
* 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 "TestSelection.h"
#include <MockShapes.h>
#include <KoSelection.h>
#include <FlakeDebug.h>
#include <QTest>
void TestSelection::testSelectedShapes()
{
KoSelection selection;
MockShape *shape1 = new MockShape();
MockShape *shape2 = new MockShape();
MockShape *shape3 = new MockShape();
QCOMPARE(selection.count(), 0);
QCOMPARE(selection.selectedShapes().count(), 0);
selection.select(shape1);
QCOMPARE(selection.count(), 1);
- QCOMPARE(selection.selectedShapes(KoFlake::FullSelection).count(), 1);
- QCOMPARE(selection.selectedShapes(KoFlake::StrippedSelection).count(), 1);
- QCOMPARE(selection.selectedShapes(KoFlake::TopLevelSelection).count(), 1);
+ QCOMPARE(selection.selectedShapes().count(), 1);
selection.select(shape1); // same one.
QCOMPARE(selection.count(), 1);
- QCOMPARE(selection.selectedShapes(KoFlake::FullSelection).count(), 1);
- QCOMPARE(selection.selectedShapes(KoFlake::StrippedSelection).count(), 1);
- QCOMPARE(selection.selectedShapes(KoFlake::TopLevelSelection).count(), 1);
+ QCOMPARE(selection.selectedShapes().count(), 1);
selection.select(shape2);
selection.select(shape3);
QCOMPARE(selection.count(), 3);
- QCOMPARE(selection.selectedShapes(KoFlake::FullSelection).count(), 3);
- QCOMPARE(selection.selectedShapes(KoFlake::StrippedSelection).count(), 3);
- QCOMPARE(selection.selectedShapes(KoFlake::TopLevelSelection).count(), 3);
+ QCOMPARE(selection.selectedShapes().count(), 3);
+
+ selection.deselectAll();
MockGroup *group1 = new MockGroup();
group1->addShape(shape1);
group1->addShape(shape2);
selection.select(group1);
- QCOMPARE(selection.count(), 3); // don't return the grouping shape.
- // Stripped returns no groups, so simply all 3 shapes
- QCOMPARE(selection.selectedShapes(KoFlake::FullSelection).count(), 3);
- // stripped returns no groups; so simply all shapes.
- QCOMPARE(selection.selectedShapes(KoFlake::StrippedSelection).count(), 3);
- // toplevel returns shape3 and group1
- QCOMPARE(selection.selectedShapes(KoFlake::TopLevelSelection).count(), 2);
+ QCOMPARE(selection.count(), 1);
+ QCOMPARE(selection.selectedShapes().count(), 1);
MockGroup *group2 = new MockGroup();
group2->addShape(shape3);
group2->addShape(group1);
selection.select(group2);
- QCOMPARE(selection.count(), 3); // thats 5 minus 2 grouping shapes.
- // Stripped returns no groups, so simply all 3 shapes
- QCOMPARE(selection.selectedShapes(KoFlake::FullSelection).count(), 3);
- // Stripped returns no groups, so simply all 3 shapes
- QCOMPARE(selection.selectedShapes(KoFlake::StrippedSelection).count(), 3);
- // toplevel returns only group2
- QCOMPARE(selection.selectedShapes(KoFlake::TopLevelSelection).count(), 1);
-
+ QCOMPARE(selection.count(), 2);
+ QCOMPARE(selection.selectedShapes().count(), 2);
group1->removeShape(shape1);
group1->removeShape(shape2);
MockContainer *container = new MockContainer();
container->addShape(shape1);
container->addShape(shape2);
selection.select(container);
- QCOMPARE(selection.count(), 4); // thats 6 minus 2 grouping shapes.
- // Stripped returns no groups, so simply all 3 shapes + container
- QCOMPARE(selection.selectedShapes(KoFlake::FullSelection).count(), 4);
- // Stripped returns no groups, and no children of a container. So; container + shape3
- QCOMPARE(selection.selectedShapes(KoFlake::StrippedSelection).count(), 2);
- // toplevel returns only group2 + container
- QCOMPARE(selection.selectedShapes(KoFlake::TopLevelSelection).count(), 2);
+ QCOMPARE(selection.count(), 3);
+ QCOMPARE(selection.selectedShapes().count(), 3);
delete group2;
delete container;
}
void TestSelection::testSize()
{
KoSelection selection;
MockShape shape1;
shape1.setSize( QSizeF( 100, 100 ) );
shape1.setPosition( QPointF( 0, 0 ) );
selection.select( &shape1 );
QCOMPARE(selection.size(), QSizeF( 100, 100 ));
MockShape shape2;
shape2.setSize( QSizeF( 100, 100 ) );
shape2.setPosition( QPointF( 100, 100 ) );
selection.select( &shape2 );
QCOMPARE(selection.size(), QSizeF( 200, 200 ));
MockShape shape3;
shape3.setSize( QSizeF( 100, 100 ) );
shape3.setPosition( QPointF( 200, 200 ) );
selection.select( &shape3 );
QCOMPARE(selection.size(), QSizeF( 300, 300 ));
selection.deselect( &shape3 );
QCOMPARE(selection.size(), QSizeF( 200, 200 ));
selection.deselect( &shape2 );
QCOMPARE(selection.size(), QSizeF( 100, 100 ));
}
QTEST_GUILESS_MAIN(TestSelection)
diff --git a/libs/flake/tests/TestShapeAt.cpp b/libs/flake/tests/TestShapeAt.cpp
index 743261f1a2..f46a9fabe2 100644
--- a/libs/flake/tests/TestShapeAt.cpp
+++ b/libs/flake/tests/TestShapeAt.cpp
@@ -1,133 +1,133 @@
/*
* 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 "TestShapeAt.h"
#include <MockShapes.h>
#include <FlakeDebug.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoShapeStroke.h>
#include <KoShapeShadow.h>
#include <KoInsets.h>
#include <QTest>
void TestShapeAt::test()
{
MockShape shape1;
MockShape shape2;
MockShape shape3;
MockCanvas canvas;
KoShapeManager manager(&canvas);
shape1.setPosition(QPointF(100, 100));
shape1.setSize(QSizeF(50, 50));
shape1.setZIndex(0);
manager.addShape(&shape1);
QVERIFY(manager.shapeAt(QPointF(90, 90)) == 0);
QVERIFY(manager.shapeAt(QPointF(110, 140)) != 0);
QVERIFY(manager.shapeAt(QPointF(100, 100)) != 0);
QVERIFY(manager.shapeAt(QPointF(100, 100), KoFlake::Selected) == 0);
QVERIFY(manager.shapeAt(QPointF(100, 100), KoFlake::Unselected) != 0);
QVERIFY(manager.shapeAt(QPointF(100, 100), KoFlake::NextUnselected) != 0);
shape2.setPosition(QPointF(80, 80));
shape2.setSize(QSizeF(50, 50));
shape2.setZIndex(1);
manager.addShape(&shape2);
QVERIFY(manager.shapeAt(QPointF(200, 200)) == 0);
QCOMPARE(manager.shapeAt(QPointF(90, 90)), &shape2);
QCOMPARE(manager.shapeAt(QPointF(105, 105)), &shape2); // the one on top
KoShape *dummy = 0;
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::Selected), dummy);
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::Unselected), &shape2); // the one on top
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::NextUnselected), &shape2);
manager.selection()->select(&shape2);
QVERIFY(manager.shapeAt(QPointF(200, 200)) == 0);
QCOMPARE(manager.shapeAt(QPointF(90, 90)), &shape2);
QCOMPARE(manager.shapeAt(QPointF(105, 105)), &shape2); // the one on top
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::Selected), &shape2);
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::Unselected), &shape1);
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::NextUnselected), &shape1);
shape3.setPosition(QPointF(120, 80));
shape3.setSize(QSizeF(50, 50));
shape3.setZIndex(2);
manager.addShape(&shape3);
QVERIFY(manager.shapeAt(QPointF(200, 200)) == 0);
QCOMPARE(manager.shapeAt(QPointF(90, 90)), &shape2);
QVERIFY(manager.shapeAt(QPointF(200, 200)) == 0);
QCOMPARE(manager.shapeAt(QPointF(90, 90)), &shape2);
QCOMPARE(manager.shapeAt(QPointF(105, 145)), &shape1);
QCOMPARE(manager.shapeAt(QPointF(165, 90)), &shape3);
QCOMPARE(manager.shapeAt(QPointF(125, 105)), &shape3); // the one on top
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::Selected), &shape2);
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::Unselected), &shape1);
QCOMPARE(manager.shapeAt(QPointF(105, 105), KoFlake::NextUnselected), &shape1);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::Selected), &shape2);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::Unselected), &shape3);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::NextUnselected), &shape1);
// test omitHiddenShapes
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::Selected, true), &shape2);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::Unselected, true), &shape3);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::NextUnselected, true), &shape1);
shape3.setVisible(false);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::Selected, true), &shape2);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::Unselected, true), &shape1);
QCOMPARE(manager.shapeAt(QPointF(125, 105), KoFlake::NextUnselected, true), &shape1);
}
void TestShapeAt::testShadow()
{
QRectF bbox(20, 30, 50, 70);
MockShape shape;
shape.setPosition(bbox.topLeft());
shape.setSize(bbox.size());
QCOMPARE(shape.boundingRect(), bbox);
- KoShapeStroke *stroke = new KoShapeStroke();
+ KoShapeStrokeSP stroke(new KoShapeStroke());
stroke->setLineWidth(20); // which means the shape grows 10 in all directions.
shape.setStroke(stroke);
KoInsets strokeInsets;
stroke->strokeInsets(&shape, strokeInsets);
bbox.adjust(-strokeInsets.left, -strokeInsets.top, strokeInsets.right, strokeInsets.bottom);
QCOMPARE(shape.boundingRect(), bbox);
KoShapeShadow *shadow = new KoShapeShadow();
shadow->setOffset(QPointF(5, 9));
shape.setShadow(shadow);
KoInsets shadowInsets;
shadow->insets(shadowInsets);
bbox.adjust(-shadowInsets.left, -shadowInsets.top, shadowInsets.right, shadowInsets.bottom);
QCOMPARE(shape.boundingRect(), bbox);
}
QTEST_MAIN(TestShapeAt)
diff --git a/libs/flake/tests/TestShapeContainer.cpp b/libs/flake/tests/TestShapeContainer.cpp
index 7f9474e39d..2719e30e8e 100644
--- a/libs/flake/tests/TestShapeContainer.cpp
+++ b/libs/flake/tests/TestShapeContainer.cpp
@@ -1,224 +1,224 @@
/*
* 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);
}
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 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::TopLeftCorner));
+ 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::TopLeftCorner), oldPositions.at(i)*0.5);
+ 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::TopLeftCorner), oldPositions.at(i)*0.5);
+ 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);
KoShapeGroup *group = new KoShapeGroup();
KoShapeGroupCommand* groupCommand = KoShapeGroupCommand::createCommand(group, groupedShapes);
groupCommand->redo();
KoSelection* selection = new KoSelection();
- selection->select(shape1, true);
+ 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::TopLeftCorner));
+ oldPositions.append(transformShapes.at(i)->absolutePosition(KoFlake::TopLeft));
}
KoShapeTransformCommand* transformCommand;
transformCommand = new KoShapeTransformCommand(transformShapes, oldTransformations, newTransformations);
transformCommand->redo();
- QRectF r1(shape1->absolutePosition(KoFlake::TopLeftCorner), shape1->absolutePosition(KoFlake::BottomRightCorner));
- QRectF r2(shape2->absolutePosition(KoFlake::TopLeftCorner), shape2->absolutePosition(KoFlake::BottomRightCorner));
+ QRectF r1(shape1->absolutePosition(KoFlake::TopLeft), shape1->absolutePosition(KoFlake::BottomRight));
+ QRectF r2(shape2->absolutePosition(KoFlake::TopLeft), shape2->absolutePosition(KoFlake::BottomRight));
QSizeF shapeSize=r1.united(r2).size();
selection = new KoSelection();
- selection->select(shape1, true);
+ selection->select(shape1);
QSizeF selecSize = selection->size();
bool works=false;
if(qFuzzyCompare(selecSize.width(), shapeSize.width()) && qFuzzyCompare(selecSize.height(), shapeSize.height()))
works=true;
QCOMPARE(works, true);
}
QTEST_MAIN(TestShapeContainer)
diff --git a/libs/flake/tests/TestShapeGroupCommand.cpp b/libs/flake/tests/TestShapeGroupCommand.cpp
index 334323dd22..9411dea3af 100644
--- a/libs/flake/tests/TestShapeGroupCommand.cpp
+++ b/libs/flake/tests/TestShapeGroupCommand.cpp
@@ -1,279 +1,281 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "TestShapeGroupCommand.h"
#include "MockShapes.h"
#include <KoShapeGroup.h>
#include <KoShapeGroupCommand.h>
#include <KoShapeStroke.h>
#include <KoShapeShadow.h>
#include <kundo2command.h>
+#include "kis_pointer_utils.h"
+
#include <QTest>
TestShapeGroupCommand::TestShapeGroupCommand()
: toplevelGroup(0), sublevelGroup(0), strokeGroup(0)
, cmd1(0), cmd2(0), strokeCmd(0)
, toplevelShape1(0), toplevelShape2(0)
, sublevelShape1(0), sublevelShape2(0)
, extraShape1(0), extraShape2(0)
, strokeShape1(0), strokeShape2(0)
{
}
TestShapeGroupCommand::~TestShapeGroupCommand()
{
}
void TestShapeGroupCommand::init()
{
toplevelShape1 = new MockShape();
toplevelShape1->setPosition(QPointF(50, 50));
toplevelShape1->setSize(QSize(50, 50));
toplevelShape2 = new MockShape();
toplevelShape2->setPosition(QPointF(50, 150));
toplevelShape2->setSize(QSize(50, 50));
sublevelShape1 = new MockShape();
sublevelShape1->setPosition(QPointF(150, 150));
sublevelShape1->setSize(QSize(50, 50));
sublevelShape2 = new MockShape();
sublevelShape2->setPosition(QPointF(250, 150));
sublevelShape2->setSize(QSize(50, 50));
extraShape1 = new MockShape();
extraShape1->setPosition(QPointF(150, 50));
extraShape1->setSize(QSize(50, 50));
extraShape2 = new MockShape();
extraShape2->setPosition(QPointF(250, 50));
extraShape2->setSize(QSize(50, 50));
toplevelGroup = new KoShapeGroup();
sublevelGroup = new KoShapeGroup();
strokeShape1 = new MockShape();
strokeShape1->setSize( QSizeF(50,50) );
strokeShape1->setPosition( QPointF(0,0) );
strokeShape2 = new MockShape();
strokeShape2->setSize( QSizeF(50,50) );
strokeShape2->setPosition( QPointF(25,25) );
strokeGroup = new KoShapeGroup();
- strokeGroup->setStroke( new KoShapeStroke( 2.0f ) );
+ strokeGroup->setStroke( toQShared(new KoShapeStroke( 2.0f )) );
strokeGroup->setShadow( new KoShapeShadow() );
}
void TestShapeGroupCommand::cleanup()
{
if (toplevelShape1->parent() == 0) {
delete toplevelShape1;
}
toplevelShape1 = 0;
if (toplevelShape2->parent() == 0) {
delete toplevelShape2;
}
toplevelShape2 = 0;
if (sublevelShape1->parent() == 0) {
delete sublevelShape1;
}
sublevelShape1 = 0;
if (sublevelShape2->parent() == 0) {
delete sublevelShape2;
}
sublevelShape2 = 0;
if (extraShape1->parent() == 0) {
delete extraShape1;
}
extraShape1 = 0;
if (extraShape2->parent() == 0) {
delete extraShape2;
}
extraShape2 = 0;
if (strokeShape1->parent() == 0) {
delete strokeShape1;
}
strokeShape1 = 0;
if (strokeShape2->parent() == 0) {
delete strokeShape2;
}
strokeShape2 = 0;
if (sublevelGroup->parent() == 0) {
delete sublevelGroup;
}
sublevelGroup = 0;
if (strokeGroup->parent() == 0) {
delete strokeGroup;
strokeGroup = 0;
}
delete toplevelGroup;
toplevelGroup = 0;
delete cmd1;
cmd1 = 0;
delete cmd2;
cmd2 = 0;
delete strokeCmd;
strokeCmd = 0;
}
void TestShapeGroupCommand::testToplevelGroup()
{
QList<KoShape*> toplevelShapes;
toplevelShapes << toplevelShape1 << toplevelShape2;
cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes);
cmd1->redo();
QCOMPARE(toplevelShape1->parent(), toplevelGroup);
- QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(50, 50));
+ QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50));
QCOMPARE(toplevelShape1->position(), QPointF(0, 0));
QCOMPARE(toplevelShape2->parent(), toplevelGroup);
- QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(50, 150));
+ QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150));
QCOMPARE(toplevelShape2->position(), QPointF(0, 100));
QCOMPARE(toplevelGroup->position(), QPointF(50, 50));
cmd1->undo();
QVERIFY(toplevelShape1->parent() == 0);
- QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(50, 50));
+ QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50));
QCOMPARE(toplevelShape1->position(), QPointF(50, 50));
QVERIFY(toplevelShape2->parent() == 0);
- QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(50, 150));
+ QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150));
QCOMPARE(toplevelShape2->position(), QPointF(50, 150));
}
void TestShapeGroupCommand::testSublevelGroup()
{
QList<KoShape*> toplevelShapes;
toplevelShapes << toplevelShape1 << toplevelShape2;
cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes);
QList<KoShape*> sublevelShapes;
sublevelShapes << sublevelShape1 << sublevelShape2;
sublevelShape1->setZIndex(1);
sublevelShape2->setZIndex(2);
sublevelShape2->setParent(strokeGroup);
strokeGroup->setZIndex(-1);
KoShapeGroupCommand::createCommand(sublevelGroup, sublevelShapes, cmd1);
KoShapeGroupCommand::createCommand(toplevelGroup, QList<KoShape*>() << sublevelGroup, cmd1);
cmd1->redo();
QCOMPARE(toplevelShape1->parent(), toplevelGroup);
- QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(50, 50));
+ QCOMPARE(toplevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(50, 50));
QCOMPARE(toplevelShape1->position(), QPointF(0, 0));
QCOMPARE(toplevelShape2->parent(), toplevelGroup);
- QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(50, 150));
+ QCOMPARE(toplevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(50, 150));
QCOMPARE(toplevelShape2->position(), QPointF(0, 100));
QCOMPARE(toplevelGroup->position(), QPointF(50, 50));
QCOMPARE(sublevelShape1->parent(), sublevelGroup);
- QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 150));
+ QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150));
QCOMPARE(sublevelShape1->position(), QPointF(0, 0));
QCOMPARE(sublevelShape2->parent(), sublevelGroup);
- QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(250, 150));
+ QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150));
QCOMPARE(sublevelShape2->position(), QPointF(100, 0));
- QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 150));
+ QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 150));
QCOMPARE(sublevelGroup->position(), QPointF(100, 100));
// check that the shapes are added in the correct order
QList<KoShape*> childOrder(sublevelGroup->shapes());
qSort(childOrder.begin(), childOrder.end(), KoShape::compareShapeZIndex);
QList<KoShape*> expectedOrder;
expectedOrder << sublevelShape2 << sublevelShape1;
QCOMPARE(childOrder, expectedOrder);
// check that the group has the zIndex/parent of its added top shape
QCOMPARE(toplevelGroup->parent(), static_cast<KoShapeContainer*>(0));
QCOMPARE(toplevelGroup->zIndex(), 1);
}
void TestShapeGroupCommand::testAddToToplevelGroup()
{
QList<KoShape*> toplevelShapes;
toplevelShapes << toplevelShape1 << toplevelShape2;
cmd1 = KoShapeGroupCommand::createCommand(toplevelGroup, toplevelShapes);
cmd1->redo();
cmd2 = KoShapeGroupCommand::createCommand(toplevelGroup, QList<KoShape*>() << extraShape1);
cmd2->redo();
QVERIFY(extraShape1->parent() == toplevelGroup);
- QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 50));
+ QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 50));
QCOMPARE(extraShape1->position(), QPointF(100, 0));
QCOMPARE(toplevelGroup->position(), QPointF(50, 50));
cmd2->undo();
QVERIFY(extraShape1->parent() == 0);
- QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 50));
+ QCOMPARE(extraShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 50));
QCOMPARE(extraShape1->position(), QPointF(150, 50));
QCOMPARE(toplevelGroup->position(), QPointF(50, 50));
}
void TestShapeGroupCommand::testAddToSublevelGroup()
{
QList<KoShape*> toplevelShapes;
toplevelShapes << toplevelShape1 << toplevelShape2;
cmd1 = new KoShapeGroupCommand(toplevelGroup, toplevelShapes);
QList<KoShape*> sublevelShapes;
sublevelShapes << sublevelShape1 << sublevelShape2;
new KoShapeGroupCommand(sublevelGroup, sublevelShapes, cmd1);
new KoShapeGroupCommand(toplevelGroup, QList<KoShape*>() << sublevelGroup, cmd1);
cmd1->redo();
cmd2 = new KoShapeGroupCommand(sublevelGroup, QList<KoShape*>() << extraShape2);
cmd2->redo();
QVERIFY(extraShape2->parent() == sublevelGroup);
- QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(250, 50));
+ QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 50));
QCOMPARE(extraShape2->position(), QPointF(100, 0));
- QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 150));
+ QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150));
QCOMPARE(sublevelShape1->position(), QPointF(0, 100));
- QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(250, 150));
+ QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150));
QCOMPARE(sublevelShape2->position(), QPointF(100, 100));
- QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 50));
+ QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 50));
QCOMPARE(sublevelGroup->position(), QPointF(100, 0));
cmd2->undo();
QVERIFY(extraShape2->parent() == 0);
- QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(250, 50));
+ QCOMPARE(extraShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 50));
QCOMPARE(extraShape2->position(), QPointF(250, 50));
- QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 150));
+ QCOMPARE(sublevelShape1->absolutePosition(KoFlake::TopLeft), QPointF(150, 150));
QCOMPARE(sublevelShape1->position(), QPointF(0, 0));
- QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeftCorner), QPointF(250, 150));
+ QCOMPARE(sublevelShape2->absolutePosition(KoFlake::TopLeft), QPointF(250, 150));
QCOMPARE(sublevelShape2->position(), QPointF(100, 0));
- QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeftCorner), QPointF(150, 150));
+ QCOMPARE(sublevelGroup->absolutePosition(KoFlake::TopLeft), QPointF(150, 150));
QCOMPARE(sublevelGroup->position(), QPointF(100, 100));
}
void TestShapeGroupCommand::testGroupStrokeShapes()
{
QList<KoShape*> strokeShapes;
strokeShapes << strokeShape2 << strokeShape1;
strokeCmd = new KoShapeGroupCommand(strokeGroup, strokeShapes);
strokeCmd->redo();
QCOMPARE(strokeShape1->size(), QSizeF(50, 50));
QCOMPARE(strokeShape2->size(), QSizeF(50, 50));
}
QTEST_MAIN(TestShapeGroupCommand)
diff --git a/libs/flake/tests/TestShapePainting.cpp b/libs/flake/tests/TestShapePainting.cpp
index 3c260027a1..b74c4e0a67 100644
--- a/libs/flake/tests/TestShapePainting.cpp
+++ b/libs/flake/tests/TestShapePainting.cpp
@@ -1,256 +1,325 @@
/*
* 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 <MockShapes.h>
#include <QTest>
void TestShapePainting::testPaintShape()
{
MockShape *shape1 = new MockShape();
MockShape *shape2 = new MockShape();
MockContainer *container = new MockContainer();
container->addShape(shape1);
container->addShape(shape2);
QCOMPARE(shape1->parent(), container);
QCOMPARE(shape2->parent(), container);
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);
QCOMPARE(manager.shapes().count(), 3);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
KoViewConverter vc;
manager.paint(painter, vc, 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);
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);
// 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);
delete container;
}
void TestShapePainting::testPaintHiddenShape()
{
MockShape *shape = new MockShape();
MockContainer *fourth = new MockContainer();
MockContainer *thirth = new MockContainer();
MockContainer *second = new MockContainer();
MockContainer *top = 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);
QCOMPARE(manager.shapes().count(), 5);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
KoViewConverter vc;
manager.paint(painter, vc, false);
QCOMPARE(top->paintedCount, 1);
QCOMPARE(second->paintedCount, 0);
QCOMPARE(thirth->paintedCount, 0);
QCOMPARE(fourth->paintedCount, 0);
QCOMPARE(shape->paintedCount, 0);
delete top;
}
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);
}
QList<MockShape*> &order;
};
QList<MockShape*> order;
MockContainer *top = new MockContainer();
top->setZIndex(2);
OrderedMockShape *shape1 = new OrderedMockShape(order);
shape1->setZIndex(5);
OrderedMockShape *shape2 = new OrderedMockShape(order);
shape2->setZIndex(0);
top->addShape(shape1);
top->addShape(shape2);
MockContainer *bottom = new MockContainer();
bottom->setZIndex(1);
OrderedMockShape *shape3 = new OrderedMockShape(order);
shape3->setZIndex(-1);
OrderedMockShape *shape4 = new OrderedMockShape(order);
shape4->setZIndex(9);
bottom->addShape(shape3);
bottom->addShape(shape4);
MockCanvas canvas;
KoShapeManager manager(&canvas);
manager.addShape(top);
manager.addShape(bottom);
QCOMPARE(manager.shapes().count(), 6);
QImage image(100, 100, QImage::Format_Mono);
QPainter painter(&image);
KoViewConverter vc;
manager.paint(painter, vc, 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);
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();
MockContainer *root = new MockContainer();
root->setZIndex(0);
MockContainer *branch1 = new MockContainer();
branch1->setZIndex(1);
OrderedMockShape *child1_1 = new OrderedMockShape(order);
child1_1->setZIndex(1);
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);
child2_1->setZIndex(1);
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);
sortedShapes.append(branch1);
sortedShapes.append(branch2);
sortedShapes.append(branch1->shapes());
sortedShapes.append(branch2->shapes());
qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(sortedShapes.count(), 7);
QVERIFY(sortedShapes[0] == root);
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);
delete top;
delete bottom;
delete root;
}
+#include <kundo2command.h>
+#include <KoShapeController.h>
+#include <KoShapeGroupCommand.h>
+#include <KoShapeUngroupCommand.h>
+#include "kis_debug.h"
+void TestShapePainting::testGroupUngroup()
+{
+ MockShape *shape1 = new MockShape();
+ MockShape *shape2 = new MockShape();
+ shape1->setName("shape1");
+ shape2->setName("shape2");
+
+
+ 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;
+
+ KoShapeGroup *group = 0;
+
+
+ for (int i = 0; i < 3; i++) {
+ {
+ group = new KoShapeGroup();
+ group->setName("group");
+
+ KUndo2Command groupingCommand;
+ canvas.shapeController()->addShapeDirect(group, &groupingCommand);
+ new KoShapeGroupCommand(group, groupedShapes, false, true, true, &groupingCommand);
+
+ groupingCommand.redo();
+
+ manager->paint(painter, vc, 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);
+
+ ungroupingCommand.redo();
+
+ manager->paint(painter, vc, false);
+
+ QCOMPARE(shape1->paintedCount, 2 * i + 2);
+ QCOMPARE(shape2->paintedCount, 2 * i + 2);
+ QCOMPARE(manager->shapes().size(), 2);
+
+ group = 0;
+ }
+
+ }
+}
+
QTEST_MAIN(TestShapePainting)
diff --git a/libs/flake/tests/TestShapePainting.h b/libs/flake/tests/TestShapePainting.h
index b3db8161d9..93f28a7674 100644
--- a/libs/flake/tests/TestShapePainting.h
+++ b/libs/flake/tests/TestShapePainting.h
@@ -1,35 +1,36 @@
/*
* 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 TESTSHAPEPAINT_H
#define TESTSHAPEPAINT_H
#include <QObject>
class TestShapePainting : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testPaintShape();
void testPaintHiddenShape();
void testPaintOrder();
+ void testGroupUngroup();
};
#endif
diff --git a/libs/flake/tests/TestShapeReorderCommand.cpp b/libs/flake/tests/TestShapeReorderCommand.cpp
index 02ffa467b4..b8d173a657 100644
--- a/libs/flake/tests/TestShapeReorderCommand.cpp
+++ b/libs/flake/tests/TestShapeReorderCommand.cpp
@@ -1,562 +1,608 @@
/* 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 "TestShapeReorderCommand.h"
#include <MockShapes.h>
#include <KoShapeReorderCommand.h>
#include <KoShapeManager.h>
#include <QTest>
TestShapeReorderCommand::TestShapeReorderCommand()
{
}
TestShapeReorderCommand::~TestShapeReorderCommand()
{
}
void TestShapeReorderCommand::testZIndexSorting()
{
MockShape shape1;
MockShape shape2;
MockShape shape3;
MockShape shape4;
MockShape shape5;
shape1.setZIndex(-2);
shape2.setZIndex(5);
shape3.setZIndex(0);
shape4.setZIndex(9999);
shape5.setZIndex(-9999);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
shapes.append(&shape4);
shapes.append(&shape5);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 1);
QCOMPARE(shapes.indexOf(&shape2), 3);
QCOMPARE(shapes.indexOf(&shape3), 2);
QCOMPARE(shapes.indexOf(&shape4), 4);
QCOMPARE(shapes.indexOf(&shape5), 0);
}
void TestShapeReorderCommand::testRunThroughSorting()
{
MockShape shape1;
MockShape shape2;
MockShape shape3;
MockShape shape4;
MockShape shape5;
shape1.setZIndex(-2);
shape2.setZIndex(5);
shape3.setZIndex(0);
shape4.setZIndex(9999);
shape5.setZIndex(-9999);
shape2.setTextRunAroundSide(KoShape::RunThrough, KoShape::Background);
shape3.setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
shapes.append(&shape4);
shapes.append(&shape5);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 2);
QCOMPARE(shapes.indexOf(&shape2), 0);
QCOMPARE(shapes.indexOf(&shape3), 4);
QCOMPARE(shapes.indexOf(&shape4), 3);
QCOMPARE(shapes.indexOf(&shape5), 1);
}
void TestShapeReorderCommand::testParentChildSorting()
{
MockShape *shape1 = new MockShape();
MockShape *shape2 = new MockShape();
MockShape *shape3 = new MockShape();
MockShape *shape4 = new MockShape();
MockShape *shape5 = new MockShape();
MockShape *shape6 = new MockShape();
MockShape *shape7 = new MockShape();
MockContainer *container1 = new MockContainer();
MockContainer *container2 = new MockContainer();
MockContainer *container3 = new MockContainer();
shape1->setZIndex(-2);
shape2->setZIndex(5);
shape3->setZIndex(0);
shape4->setZIndex(9999);
shape5->setZIndex(-9999);
shape6->setZIndex(3);
shape7->setZIndex(7);
container1->setZIndex(-55);
container2->setZIndex(57);
shape2->setTextRunAroundSide(KoShape::RunThrough, KoShape::Background);
shape3->setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground);
container1->setTextRunAroundSide(KoShape::RunThrough, KoShape::Foreground);
container1->addShape(shape1);
//container1.addShape(&shape2); //we shouldn't parent combine fg and bg
container2->addShape(shape4);
container2->addShape(shape5);
container1->addShape(container2);
container1->addShape(container3);
QList<KoShape*> shapes;
shapes.append(shape1);
shapes.append(shape2);
shapes.append(shape3);
shapes.append(shape4);
shapes.append(shape5);
shapes.append(shape6);
shapes.append(shape7);
shapes.append(container1);
shapes.append(container2);
shapes.append(container3);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
/* This is the expected result
s3 0 fg
s4 9999
s5 -9999
c2 57
c3 0
s1 -2
c1 -55 fg
s7 7
s6 3
s2 5 bg
*/
QCOMPARE(shapes.indexOf(shape1), 4);
QCOMPARE(shapes.indexOf(shape2), 0);
QCOMPARE(shapes.indexOf(shape3), 9);
QCOMPARE(shapes.indexOf(shape4), 8);
QCOMPARE(shapes.indexOf(shape5), 7);
QCOMPARE(shapes.indexOf(shape6), 1);
QCOMPARE(shapes.indexOf(shape7), 2);
QCOMPARE(shapes.indexOf(container1), 3);
QCOMPARE(shapes.indexOf(container2), 6);
QCOMPARE(shapes.indexOf(container3), 5);
delete container1;
delete shape2;
delete shape3;
delete shape6;
delete shape7;
}
void TestShapeReorderCommand::testBringToFront()
{
MockShape shape1, shape2, shape3;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(100, 100));
shape3.setZIndex(3);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 0);
QCOMPARE(shapes.indexOf(&shape2), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape1);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront);
cmd->redo();
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape2), 0);
QCOMPARE(shapes.indexOf(&shape3), 1);
QCOMPARE(shapes.indexOf(&shape1), 2);
delete cmd;
}
void TestShapeReorderCommand::testSendToBack()
{
MockShape shape1, shape2, shape3;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(100, 100));
shape3.setZIndex(3);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 0);
QCOMPARE(shapes.indexOf(&shape2), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape3);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack);
cmd->redo();
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape3), 0);
QCOMPARE(shapes.indexOf(&shape1), 1);
QCOMPARE(shapes.indexOf(&shape2), 2);
delete cmd;
}
void TestShapeReorderCommand::testMoveUp()
{
MockShape shape1, shape2, shape3;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(100, 100));
shape3.setZIndex(3);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 0);
QCOMPARE(shapes.indexOf(&shape2), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape1);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape);
cmd->redo();
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape2), 0);
QCOMPARE(shapes.indexOf(&shape1), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
delete cmd;
}
void TestShapeReorderCommand::testMoveDown()
{
MockShape shape1, shape2, shape3;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(100, 100));
shape3.setZIndex(3);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 0);
QCOMPARE(shapes.indexOf(&shape2), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape2);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape);
cmd->redo();
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape2), 0);
QCOMPARE(shapes.indexOf(&shape1), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
delete cmd;
}
void TestShapeReorderCommand::testMoveUpOverlapping()
{
MockShape shape1, shape2, shape3, shape4, shape5;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(300, 300));
shape3.setZIndex(3);
shape4.setSize(QSizeF(100, 100));
shape4.setPosition(QPointF(200,200));
shape4.setZIndex(4);
shape5.setSize(QSizeF(100, 100));
shape5.setPosition(QPointF(200,200));
shape5.setZIndex(5);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
shapes.append(&shape4);
shapes.append(&shape5);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
QVERIFY(shape1.zIndex() < shape2.zIndex());
QVERIFY(shape2.zIndex() < shape3.zIndex());
QVERIFY(shape3.zIndex() < shape4.zIndex());
QVERIFY(shape4.zIndex() < shape5.zIndex());
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape1);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape);
cmd->redo();
delete cmd;
QVERIFY(shape1.zIndex() > shape2.zIndex());
QVERIFY(shape2.zIndex() < shape3.zIndex());
QVERIFY(shape1.zIndex() < shape3.zIndex());
QVERIFY(shape3.zIndex() < shape4.zIndex());
QVERIFY(shape4.zIndex() < shape5.zIndex());
}
void TestShapeReorderCommand::testMoveDownOverlapping()
{
#if 0 // disable a current alogrithm does not yet support this
MockShape shape1, shape2, shape3, shape4, shape5;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(300, 300));
shape3.setZIndex(3);
shape4.setSize(QSizeF(100, 100));
shape4.setPosition(QPointF(200,200));
shape4.setZIndex(4);
shape5.setSize(QSizeF(100, 100));
shape5.setPosition(QPointF(200,200));
shape5.setZIndex(5);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
shapes.append(&shape4);
shapes.append(&shape5);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
QVERIFY(shape1.zIndex() < shape2.zIndex());
QVERIFY(shape2.zIndex() < shape3.zIndex());
QVERIFY(shape3.zIndex() < shape4.zIndex());
QVERIFY(shape4.zIndex() < shape5.zIndex());
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape5);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape);
cmd->redo();
delete cmd;
QVERIFY(shape1.zIndex() < shape2.zIndex());
QVERIFY(shape2.zIndex() < shape3.zIndex());
QVERIFY(shape3.zIndex() < shape4.zIndex());
QVERIFY(shape4.zIndex() > shape5.zIndex());
QVERIFY(shape3.zIndex() > shape5.zIndex());
#endif
}
void TestShapeReorderCommand::testSendToBackChildren()
{
MockShape *shape1 = new MockShape();
MockShape *shape2 = new MockShape();
MockShape *shape3 = new MockShape();
shape1->setSize(QSizeF(100, 100));
shape1->setZIndex(1);
shape2->setSize(QSizeF(100, 100));
shape2->setZIndex(2);
shape3->setSize(QSizeF(100, 100));
shape3->setZIndex(3);
MockContainer *container = new MockContainer();
container->addShape(shape1);
container->addShape(shape2);
container->addShape(shape3);
QList<KoShape*> shapes;
shapes.append(shape1);
shapes.append(shape2);
shapes.append(shape3);
shapes.append(container);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children
QCOMPARE(shapes.indexOf(shape1), 1);
QCOMPARE(shapes.indexOf(shape2), 2);
QCOMPARE(shapes.indexOf(shape3), 3);
QList<KoShape*> selectedShapes;
selectedShapes.append(shape3);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack);
cmd->redo();
delete cmd;
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children
QCOMPARE(shapes.indexOf(shape3), 1);
QVERIFY(shape3->zIndex() < shape1->zIndex());
QCOMPARE(shapes.indexOf(shape1), 2);
QVERIFY(shape1->zIndex() < shape2->zIndex());
QCOMPARE(shapes.indexOf(shape2), 3);
selectedShapes.clear();
selectedShapes.append(shape2);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack);
cmd->redo();
delete cmd;
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children
QCOMPARE(shapes.indexOf(shape2), 1);
QVERIFY(shape2->zIndex() < shape3->zIndex());
QCOMPARE(shapes.indexOf(shape3), 2);
QVERIFY(shape3->zIndex() < shape1->zIndex());
QCOMPARE(shapes.indexOf(shape1), 3);
selectedShapes.clear();
selectedShapes.append(shape1);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack);
cmd->redo();
delete cmd;
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(container), 0); // atm the parent is always lower than its children
QCOMPARE(shapes.indexOf(shape1), 1);
QVERIFY(shape1->zIndex() < shape2->zIndex());
QCOMPARE(shapes.indexOf(shape2), 2);
QVERIFY(shape2->zIndex() < shape3->zIndex());
QCOMPARE(shapes.indexOf(shape3), 3);
delete container;
}
void TestShapeReorderCommand::testNoCommand()
{
MockShape shape1, shape2, shape3;
shape1.setSize(QSizeF(100, 100));
shape1.setZIndex(1);
shape2.setSize(QSizeF(100, 100));
shape2.setZIndex(2);
shape3.setSize(QSizeF(100, 100));
shape3.setZIndex(3);
QList<KoShape*> shapes;
shapes.append(&shape1);
shapes.append(&shape2);
shapes.append(&shape3);
MockCanvas canvas;
KoShapeManager manager(&canvas, shapes);
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QCOMPARE(shapes.indexOf(&shape1), 0);
QCOMPARE(shapes.indexOf(&shape2), 1);
QCOMPARE(shapes.indexOf(&shape3), 2);
QList<KoShape*> selectedShapes;
selectedShapes.append(&shape3);
KUndo2Command * cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront);
QVERIFY(cmd == 0);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape);
QVERIFY(cmd == 0);
selectedShapes.append(&shape1);
selectedShapes.append(&shape2);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::BringToFront);
QVERIFY(cmd == 0);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::RaiseShape);
QVERIFY(cmd == 0);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape);
QVERIFY(cmd == 0);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack);
QVERIFY(cmd == 0);
selectedShapes.clear();
selectedShapes.append(&shape1);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::SendToBack);
QVERIFY(cmd == 0);
cmd = KoShapeReorderCommand::createCommand(selectedShapes, &manager, KoShapeReorderCommand::LowerShape);
QVERIFY(cmd == 0);
}
+#include <kis_assert.h>
+#include <kis_debug.h>
+void testMergeInShapeImpl(const QVector<int> indexesProfile,
+ int newShapeIndex,
+ const QVector<int> expectedIndexes)
+{
+ KIS_ASSERT(indexesProfile.size() == expectedIndexes.size());
+
+ QVector<MockShape> shapesStore(indexesProfile.size());
+
+ QList<KoShape*> managedShapes;
+
+ for (int i = 0; i < shapesStore.size(); i++) {
+ shapesStore[i].setSize(QSizeF(100,100));
+ shapesStore[i].setZIndex(indexesProfile[i]);
+
+ managedShapes << &shapesStore[i];
+ }
+
+ QScopedPointer<KUndo2Command> cmd(
+ KoShapeReorderCommand::mergeInShape(managedShapes, &shapesStore[newShapeIndex]));
+ cmd->redo();
+
+ for (int i = 0; i < shapesStore.size(); i++) {
+ //qDebug() << ppVar(i) << ppVar(shapesStore[i].zIndex());
+ QCOMPARE(shapesStore[i].zIndex(), expectedIndexes[i]);
+ }
+}
+
+void TestShapeReorderCommand::testMergeInShape()
+{
+ QVector<int> indexesProfile({1,1,2,2,2,3,3,4,5,6});
+ int newShapeIndex = 3;
+ QVector<int> expectedIndexes({1,1,2,3,2,4,4,5,6,7});
+
+ testMergeInShapeImpl(indexesProfile, newShapeIndex, expectedIndexes);
+}
+
+void TestShapeReorderCommand::testMergeInShapeDistant()
+{
+ QVector<int> indexesProfile({1,1,2,2,2,4,4,5,6,7});
+ int newShapeIndex = 3;
+ QVector<int> expectedIndexes({1,1,2,3,2,4,4,5,6,7});
+
+ testMergeInShapeImpl(indexesProfile, newShapeIndex, expectedIndexes);
+}
QTEST_MAIN(TestShapeReorderCommand)
diff --git a/libs/flake/tests/TestShapeReorderCommand.h b/libs/flake/tests/TestShapeReorderCommand.h
index ebc1e2dbd9..946229ef5b 100644
--- a/libs/flake/tests/TestShapeReorderCommand.h
+++ b/libs/flake/tests/TestShapeReorderCommand.h
@@ -1,46 +1,48 @@
/* 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 TESTSHAPEREORDERCOMMAND_H
#define TESTSHAPEREORDERCOMMAND_H
#include <QObject>
class TestShapeReorderCommand : public QObject
{
Q_OBJECT
public:
TestShapeReorderCommand();
~TestShapeReorderCommand();
private Q_SLOTS:
void testZIndexSorting();
void testRunThroughSorting();
void testParentChildSorting();
void testBringToFront();
void testSendToBack();
void testMoveUp();
void testMoveDown();
void testMoveUpOverlapping();
void testMoveDownOverlapping();
void testSendToBackChildren();
void testNoCommand();
+ void testMergeInShape();
+ void testMergeInShapeDistant();
};
#endif // TESTSHAPEREORDERCOMMAND_H
diff --git a/libs/flake/tests/TestShapeStrokeCommand.cpp b/libs/flake/tests/TestShapeStrokeCommand.cpp
index 2a1ea00433..ba9ab0afca 100644
--- a/libs/flake/tests/TestShapeStrokeCommand.cpp
+++ b/libs/flake/tests/TestShapeStrokeCommand.cpp
@@ -1,72 +1,69 @@
/* 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 "TestShapeStrokeCommand.h"
#include <MockShapes.h>
#include "KoShapeStrokeModel.h"
#include "KoShapeStroke.h"
#include "KoShapeStrokeCommand.h"
#include <KoInsets.h>
#include <QTest>
void TestShapeStrokeCommand::refCounting()
{
MockShape * shape1 = new MockShape();
- KoShapeStrokeModel *whiteStroke = new KoShapeStroke(1.0, QColor(Qt::white));
- KoShapeStrokeModel *blackStroke = new KoShapeStroke(1.0, QColor(Qt::black));
- KoShapeStrokeModel *redStroke = new KoShapeStroke(1.0, QColor(Qt::red));
+ KoShapeStrokeModelSP whiteStroke(new KoShapeStroke(1.0, QColor(Qt::white)));
+ KoShapeStrokeModelSP blackStroke(new KoShapeStroke(1.0, QColor(Qt::black)));
+ KoShapeStrokeModelSP redStroke(new KoShapeStroke(1.0, QColor(Qt::red)));
shape1->setStroke(whiteStroke);
QVERIFY(shape1->stroke() == whiteStroke);
QCOMPARE(whiteStroke->useCount(), 1);
// old stroke is white, new stroke is black
KUndo2Command *cmd1 = new KoShapeStrokeCommand(shape1, blackStroke);
cmd1->redo();
QVERIFY(shape1->stroke() == blackStroke);
// change stroke back to white stroke
cmd1->undo();
QVERIFY(shape1->stroke() == whiteStroke);
// old stroke is white, new stroke is red
KUndo2Command *cmd2 = new KoShapeStrokeCommand(shape1, redStroke);
cmd2->redo();
QVERIFY(shape1->stroke() == redStroke);
// this command has the white stroke as the old stroke
delete cmd1;
// set stroke back to white stroke
cmd2->undo();
QVERIFY(shape1->stroke() == whiteStroke);
// if white is deleted when deleting cmd1 this will crash
KoInsets insets;
whiteStroke->strokeInsets(shape1, insets);
delete cmd2;
- delete shape1; // This deletes whiteStroke
-
- delete blackStroke;
- delete redStroke;
+ delete shape1;
}
QTEST_MAIN(TestShapeStrokeCommand)
diff --git a/libs/flake/tests/TestSvgParser.cpp b/libs/flake/tests/TestSvgParser.cpp
new file mode 100644
index 0000000000..940ac2d20c
--- /dev/null
+++ b/libs/flake/tests/TestSvgParser.cpp
@@ -0,0 +1,3386 @@
+/*
+ * 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/SvgParser.h>
+#include <kis_debug.h>
+#include <kis_global.h>
+#include <KoDocumentResourceManager.h>
+#include <KoShape.h>
+#include <KoShapeGroup.h>
+#include <svg/SvgUtil.h>
+#include <KoViewConverter.h>
+#include <KoShapePaintingContext.h>
+#include <QPainter>
+#include <KoShapeStrokeModel.h>
+#include <KoShapePainter.h>
+
+#include "kis_algebra_2d.h"
+
+struct SvgTester
+{
+ SvgTester (const QString &data)
+ : parser(&resourceManager)
+ {
+ QVERIFY(doc.setContent(data.toLatin1()));
+ root = doc.documentElement();
+
+ parser.setXmlBaseDir("./");
+
+
+ savedData = data;
+ //printf("%s", savedData.toLatin1().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;
+};
+
+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->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->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->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->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->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->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->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->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->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->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->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->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->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 decendants!
+ */
+
+ 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->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->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->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->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->outlineRect(), QRectF(0,0,10,20));
+ QCOMPARE(shape->absolutePosition(KoFlake::TopLeft), QPointF(5,15));
+ QCOMPARE(shape->absolutePosition(KoFlake::BottomRight), QPointF(25,5));
+}
+
+#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);
+
+ 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)) {
+ parser.setResolution(QRectF(0, 0, 30, 30) /* px */, 72 /* 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
+
+ const QSizeF sizeInPt(30,30);
+ QBuffer writeBuf;
+ writeBuf.open(QIODevice::WriteOnly);
+
+ {
+ SvgWriter writer(shapes, sizeInPt);
+ writer.save(writeBuf);
+ }
+
+ 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;
+};
+
+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 heirarchy!", 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\""
+ " 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\""
+ " 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\""
+ " 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.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\""
+ " 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\""
+ " 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\""
+ " 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\""
+ " 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, true, 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, true, 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>"
+
+ " <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\">"
+ " <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_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.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::testGradientRecoveringTrasnform()
+{
+ // 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 gradientTrasnform;
+ gradientTrasnform.shear(0.2, 0);
+
+ {
+ QBrush brush(gradient);
+ brush.setTransform(gradientTrasnform);
+ 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(gradientTrasnform * 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
new file mode 100644
index 0000000000..34351c3422
--- /dev/null
+++ b/libs/flake/tests/TestSvgParser.h
@@ -0,0 +1,173 @@
+/*
+ * 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 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 testMarkersFillAsShape();
+
+ void testGradientRecoveringTrasnform();
+ void testMarkersOnClosedPath();
+ void testMarkersAngularUnits();
+
+ void testSodipodiArcShape();
+ void testSodipodiArcShapeOpen();
+ void testKritaChordShape();
+ void testSodipodiChordShape();
+};
+
+#endif // TESTSVGPARSER_H
diff --git a/libs/flake/tests/data/clip_mask/clip_mask_render_mask.png b/libs/flake/tests/data/clip_mask/clip_mask_render_mask.png
new file mode 100644
index 0000000000..43be2e8021
Binary files /dev/null and b/libs/flake/tests/data/clip_mask/clip_mask_render_mask.png differ
diff --git a/libs/flake/tests/data/icc/sRGB-elle-V4-srgbtrc.icc b/libs/flake/tests/data/icc/sRGB-elle-V4-srgbtrc.icc
new file mode 100644
index 0000000000..f5794dc1eb
Binary files /dev/null and b/libs/flake/tests/data/icc/sRGB-elle-V4-srgbtrc.icc differ
diff --git a/libs/flake/tests/data/marker_collection/preview_end_marker.png b/libs/flake/tests/data/marker_collection/preview_end_marker.png
new file mode 100644
index 0000000000..54a8407261
Binary files /dev/null and b/libs/flake/tests/data/marker_collection/preview_end_marker.png differ
diff --git a/libs/flake/tests/data/marker_collection/preview_mid_marker.png b/libs/flake/tests/data/marker_collection/preview_mid_marker.png
new file mode 100644
index 0000000000..ab482dba4e
Binary files /dev/null and b/libs/flake/tests/data/marker_collection/preview_mid_marker.png differ
diff --git a/libs/flake/tests/data/marker_collection/preview_start_marker.png b/libs/flake/tests/data/marker_collection/preview_start_marker.png
new file mode 100644
index 0000000000..5f2009dcf3
Binary files /dev/null and b/libs/flake/tests/data/marker_collection/preview_start_marker.png differ
diff --git a/libs/flake/tests/data/svg_render/load_circle.png b/libs/flake/tests/data/svg_render/load_circle.png
new file mode 100644
index 0000000000..7bd8815314
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_circle.png differ
diff --git a/libs/flake/tests/data/svg_render/load_clip_mask_obb.png b/libs/flake/tests/data/svg_render/load_clip_mask_obb.png
new file mode 100644
index 0000000000..9fe584aa68
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_clip_mask_obb.png differ
diff --git a/libs/flake/tests/data/svg_render/load_clip_render_test.png b/libs/flake/tests/data/svg_render/load_clip_render_test.png
new file mode 100644
index 0000000000..3722cf8bd2
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_clip_render_test.png differ
diff --git a/libs/flake/tests/data/svg_render/load_clip_render_test_rotated.png b/libs/flake/tests/data/svg_render/load_clip_render_test_rotated.png
new file mode 100644
index 0000000000..45da8681ab
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_clip_render_test_rotated.png differ
diff --git a/libs/flake/tests/data/svg_render/load_defs_use_inheritance.png b/libs/flake/tests/data/svg_render/load_defs_use_inheritance.png
new file mode 100644
index 0000000000..cd17caaf7c
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_defs_use_inheritance.png differ
diff --git a/libs/flake/tests/data/svg_render/load_ellipse.png b/libs/flake/tests/data/svg_render/load_ellipse.png
new file mode 100644
index 0000000000..9435891596
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_ellipse.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_black.png b/libs/flake/tests/data/svg_render/load_fill_black.png
new file mode 100644
index 0000000000..0869a3297f
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_black.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_even_odd.png b/libs/flake/tests/data/svg_render/load_fill_even_odd.png
new file mode 100644
index 0000000000..251a2e3301
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_even_odd.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient.png b/libs/flake/tests/data/svg_render/load_fill_gradient.png
new file mode 100644
index 0000000000..8e02d71a1a
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient_radial.png b/libs/flake/tests/data/svg_render/load_fill_gradient_radial.png
new file mode 100644
index 0000000000..e72d2a596f
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient_radial.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient_radial_in_user.png b/libs/flake/tests/data/svg_render/load_fill_gradient_radial_in_user.png
new file mode 100644
index 0000000000..a1d2245b72
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient_radial_in_user.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient_shape_rotated.png b/libs/flake/tests/data/svg_render/load_fill_gradient_shape_rotated.png
new file mode 100644
index 0000000000..478e8cf5dd
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient_shape_rotated.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient_shape_rotated_in_user.png b/libs/flake/tests/data/svg_render/load_fill_gradient_shape_rotated_in_user.png
new file mode 100644
index 0000000000..c580792cca
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient_shape_rotated_in_user.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient_vertical.png b/libs/flake/tests/data/svg_render/load_fill_gradient_vertical.png
new file mode 100644
index 0000000000..352b708c0e
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient_vertical.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_gradient_vertical_in_user.png b/libs/flake/tests/data/svg_render/load_fill_gradient_vertical_in_user.png
new file mode 100644
index 0000000000..bc2aa7833f
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_gradient_vertical_in_user.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_non_zero.png b/libs/flake/tests/data/svg_render/load_fill_non_zero.png
new file mode 100644
index 0000000000..c8441e4f84
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_non_zero.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_opacity_0_3.png b/libs/flake/tests/data/svg_render/load_fill_opacity_0_3.png
new file mode 100644
index 0000000000..0b9c7f4d91
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_opacity_0_3.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_pattern_base.png b/libs/flake/tests/data/svg_render/load_fill_pattern_base.png
new file mode 100644
index 0000000000..f987ee31cc
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_pattern_base.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_pattern_base_black.png b/libs/flake/tests/data/svg_render/load_fill_pattern_base_black.png
new file mode 100644
index 0000000000..a620281291
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_pattern_base_black.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_pattern_rotated.png b/libs/flake/tests/data/svg_render/load_fill_pattern_rotated.png
new file mode 100644
index 0000000000..87e747d27d
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_pattern_rotated.png differ
diff --git a/libs/flake/tests/data/svg_render/load_fill_pattern_rotated_odd.png b/libs/flake/tests/data/svg_render/load_fill_pattern_rotated_odd.png
new file mode 100644
index 0000000000..47988c27e2
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_fill_pattern_rotated_odd.png differ
diff --git a/libs/flake/tests/data/svg_render/load_image_aspect_default.png b/libs/flake/tests/data/svg_render/load_image_aspect_default.png
new file mode 100644
index 0000000000..c5749a5098
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_image_aspect_default.png differ
diff --git a/libs/flake/tests/data/svg_render/load_image_aspect_meet.png b/libs/flake/tests/data/svg_render/load_image_aspect_meet.png
new file mode 100644
index 0000000000..ed32891012
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_image_aspect_meet.png differ
diff --git a/libs/flake/tests/data/svg_render/load_image_aspect_none.png b/libs/flake/tests/data/svg_render/load_image_aspect_none.png
new file mode 100644
index 0000000000..e179e8b341
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_image_aspect_none.png differ
diff --git a/libs/flake/tests/data/svg_render/load_line.png b/libs/flake/tests/data/svg_render/load_line.png
new file mode 100644
index 0000000000..316a6f6fa4
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_line.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers.png b/libs/flake/tests/data/svg_render/load_markers.png
new file mode 100644
index 0000000000..78c0ba79b4
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_angular_units.png b/libs/flake/tests/data/svg_render/load_markers_angular_units.png
new file mode 100644
index 0000000000..5dec55438a
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_angular_units.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_custom_orientation.png b/libs/flake/tests/data/svg_render/load_markers_custom_orientation.png
new file mode 100644
index 0000000000..72499d8928
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_custom_orientation.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_different.png b/libs/flake/tests/data/svg_render/load_markers_different.png
new file mode 100644
index 0000000000..5d7b68adad
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_different.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_on_closed_path.png b/libs/flake/tests/data/svg_render/load_markers_on_closed_path.png
new file mode 100644
index 0000000000..5f18233952
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_on_closed_path.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_scaled.png b/libs/flake/tests/data/svg_render/load_markers_scaled.png
new file mode 100644
index 0000000000..96aedb5d10
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_scaled.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png b/libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png
new file mode 100644
index 0000000000..48fa548444
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_scaled_fill_as_shape.png differ
diff --git a/libs/flake/tests/data/svg_render/load_markers_user_coordinates.png b/libs/flake/tests/data/svg_render/load_markers_user_coordinates.png
new file mode 100644
index 0000000000..9fb91a46c9
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_markers_user_coordinates.png differ
diff --git a/libs/flake/tests/data/svg_render/load_polygon.png b/libs/flake/tests/data/svg_render/load_polygon.png
new file mode 100644
index 0000000000..27c7fa3592
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_polygon.png differ
diff --git a/libs/flake/tests/data/svg_render/load_polyline.png b/libs/flake/tests/data/svg_render/load_polyline.png
new file mode 100644
index 0000000000..3fe10bf103
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_polyline.png differ
diff --git a/libs/flake/tests/data/svg_render/load_rect_5_10.png b/libs/flake/tests/data/svg_render/load_rect_5_10.png
new file mode 100644
index 0000000000..126a740589
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_rect_5_10.png differ
diff --git a/libs/flake/tests/data/svg_render/load_rect_5_5.png b/libs/flake/tests/data/svg_render/load_rect_5_5.png
new file mode 100644
index 0000000000..36b7364e75
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_rect_5_5.png differ
diff --git a/libs/flake/tests/data/svg_render/load_relative_clip_render_test.png b/libs/flake/tests/data/svg_render/load_relative_clip_render_test.png
new file mode 100644
index 0000000000..cd9b807525
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_relative_clip_render_test.png differ
diff --git a/libs/flake/tests/data/svg_render/load_sodipodi_chord_arc.png b/libs/flake/tests/data/svg_render/load_sodipodi_chord_arc.png
new file mode 100644
index 0000000000..d9be37d44e
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_sodipodi_chord_arc.png differ
diff --git a/libs/flake/tests/data/svg_render/load_sodipodi_closed_arc.png b/libs/flake/tests/data/svg_render/load_sodipodi_closed_arc.png
new file mode 100644
index 0000000000..1e9e1ab761
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_sodipodi_closed_arc.png differ
diff --git a/libs/flake/tests/data/svg_render/load_sodipodi_open_arc.png b/libs/flake/tests/data/svg_render/load_sodipodi_open_arc.png
new file mode 100644
index 0000000000..31786370b9
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_sodipodi_open_arc.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue.png b/libs/flake/tests/data/svg_render/load_stroke_blue.png
new file mode 100644
index 0000000000..77a72d9dd9
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_0_3_opacity.png b/libs/flake/tests/data/svg_render/load_stroke_blue_0_3_opacity.png
new file mode 100644
index 0000000000..c05b2a6433
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_0_3_opacity.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_even.png b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_even.png
new file mode 100644
index 0000000000..0ba20a750b
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_even.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_even_offset.png b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_even_offset.png
new file mode 100644
index 0000000000..fdec613fed
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_even_offset.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_odd.png b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_odd.png
new file mode 100644
index 0000000000..d16dea69b5
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_odd.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_relative.png b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_relative.png
new file mode 100644
index 0000000000..d742bc0de7
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_dasharray_relative.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_join_round.png b/libs/flake/tests/data/svg_render/load_stroke_blue_join_round.png
new file mode 100644
index 0000000000..949eb79403
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_join_round.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_linecap_round.png b/libs/flake/tests/data/svg_render/load_stroke_blue_linecap_round.png
new file mode 100644
index 0000000000..1e703b4b9b
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_linecap_round.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_blue_width_2.png b/libs/flake/tests/data/svg_render/load_stroke_blue_width_2.png
new file mode 100644
index 0000000000..e1c2adc0a8
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_blue_width_2.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_gradient_dashed.png b/libs/flake/tests/data/svg_render/load_stroke_gradient_dashed.png
new file mode 100644
index 0000000000..abefd5174f
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_gradient_dashed.png differ
diff --git a/libs/flake/tests/data/svg_render/load_stroke_none.png b/libs/flake/tests/data/svg_render/load_stroke_none.png
new file mode 100644
index 0000000000..7e2abcbe0f
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_stroke_none.png differ
diff --git a/libs/flake/tests/data/svg_render/load_test_defs_hidden.png b/libs/flake/tests/data/svg_render/load_test_defs_hidden.png
new file mode 100644
index 0000000000..21ff0ba738
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_test_defs_hidden.png differ
diff --git a/libs/flake/tests/data/svg_render/load_use_without_defs.png b/libs/flake/tests/data/svg_render/load_use_without_defs.png
new file mode 100644
index 0000000000..89fb0a8e43
Binary files /dev/null and b/libs/flake/tests/data/svg_render/load_use_without_defs.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_fill1.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_fill1.png
new file mode 100644
index 0000000000..20354bbcff
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_fill1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_fill2.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_fill2.png
new file mode 100644
index 0000000000..3470da8c99
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_fill2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_patch1.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_patch1.png
new file mode 100644
index 0000000000..47533485d8
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_patch1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_patch2.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_patch2.png
new file mode 100644
index 0000000000..4ccc473841
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_patch2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_fill1.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_fill1.png
new file mode 100644
index 0000000000..d8eec21142
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_fill1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_fill2.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_fill2.png
new file mode 100644
index 0000000000..8bed1a09df
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_fill2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_patch1.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_patch1.png
new file mode 100644
index 0000000000..13ddf46df5
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_patch1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_patch2.png b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_patch2.png
new file mode 100644
index 0000000000..4eee060738
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_obb_r_obb_rotate_patch2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_fill1.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_fill1.png
new file mode 100644
index 0000000000..99d1bca8dc
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_fill1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_fill2.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_fill2.png
new file mode 100644
index 0000000000..5f7760ede0
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_fill2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_patch1.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_patch1.png
new file mode 100644
index 0000000000..6a7dc7c6a2
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_patch1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_patch2.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_patch2.png
new file mode 100644
index 0000000000..2fda4e60aa
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_obb_patch2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_fill1.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_fill1.png
new file mode 100644
index 0000000000..8538a683f8
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_fill1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_fill2.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_fill2.png
new file mode 100644
index 0000000000..03a1ca3d61
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_fill2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_patch1.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_patch1.png
new file mode 100644
index 0000000000..469888435b
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_patch1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_patch2.png b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_patch2.png
new file mode 100644
index 0000000000..469888435b
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_user_r_user_patch2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_fill1.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_fill1.png
new file mode 100644
index 0000000000..77a789446e
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_fill1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_fill2.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_fill2.png
new file mode 100644
index 0000000000..8248c6040d
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_fill2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_patch1.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_patch1.png
new file mode 100644
index 0000000000..067e5783a6
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_patch1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_patch2.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_patch2.png
new file mode 100644
index 0000000000..fcc9d098bf
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_obb_patch2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_fill1.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_fill1.png
new file mode 100644
index 0000000000..9bf4aa2aec
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_fill1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_fill2.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_fill2.png
new file mode 100644
index 0000000000..b87a2382a7
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_fill2.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_patch1.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_patch1.png
new file mode 100644
index 0000000000..509b7c36c3
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_patch1.png differ
diff --git a/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_patch2.png b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_patch2.png
new file mode 100644
index 0000000000..509b7c36c3
Binary files /dev/null and b/libs/flake/tests/data/svg_render/render_pattern_c_view_r_user_patch2.png differ
diff --git a/libs/flake/tests/data/svg_render/testing_ref_image.png b/libs/flake/tests/data/svg_render/testing_ref_image.png
new file mode 100644
index 0000000000..59c42f7364
Binary files /dev/null and b/libs/flake/tests/data/svg_render/testing_ref_image.png differ
diff --git a/libs/flake/tests/data/test_markers.svg b/libs/flake/tests/data/test_markers.svg
new file mode 100644
index 0000000000..89fef6a9bf
--- /dev/null
+++ b/libs/flake/tests/data/test_markers.svg
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ viewBox="0 0 744.09448819 1052.3622047"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="markers.svg">
+ <defs
+ id="defs4">
+ <marker
+ inkscape:stockid="DotS"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="DotS"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4209"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.2) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="DotM"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="DotM"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4206"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.4) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="DotL"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="DotL"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4203"
+ d="M -2.5,-1.0 C -2.5,1.7600000 -4.7400000,4.0 -7.5,4.0 C -10.260000,4.0 -12.5,1.7600000 -12.5,-1.0 C -12.5,-3.7600000 -10.260000,-6.0 -7.5,-6.0 C -4.7400000,-6.0 -2.5,-3.7600000 -2.5,-1.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.8) translate(7.4, 1)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Send"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Send"
+ style="overflow:visible;"
+ inkscape:isstock="true">
+ <path
+ id="path4157"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.2) rotate(180) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Sstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Sstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4154"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.2) translate(6,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mend"
+ style="overflow:visible;"
+ inkscape:isstock="true">
+ <path
+ id="path4151"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.4) rotate(180) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Mstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4148"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.4) translate(10,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lend"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lend"
+ style="overflow:visible;"
+ inkscape:isstock="true">
+ <path
+ id="path4145"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.8) rotate(180) translate(12.5,0)" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Lstart"
+ orient="auto"
+ refY="0.0"
+ refX="0.0"
+ id="Arrow1Lstart"
+ style="overflow:visible"
+ inkscape:isstock="true">
+ <path
+ id="path4142"
+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
+ transform="scale(0.8) translate(12.5,0)" />
+ </marker>
+ </defs>
+
+</svg>
diff --git a/libs/flake/tests/data/test_svg_file.svg b/libs/flake/tests/data/test_svg_file.svg
new file mode 100644
index 0000000000..025850d432
--- /dev/null
+++ b/libs/flake/tests/data/test_svg_file.svg
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="30pt"
+ height="30pt"
+ viewBox="0 0 37.500001 37.5"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="test_svg_file.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.2"
+ inkscape:cx="21.813814"
+ inkscape:cy="20.149396"
+ inkscape:document-units="pt"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="pt"
+ inkscape:window-width="1861"
+ inkscape:window-height="1056"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1014.8622)">
+ <rect
+ style="color:#000000;solid-opacity:1;fill:#fa8072;fill-opacity:0.46551729;fill-rule:evenodd;stroke:#d541a6;stroke-width:2.06454587;stroke-dashoffset:71.24002075;stroke-opacity:0.53448277"
+ id="rect3593"
+ width="16.14974"
+ height="15.703311"
+ x="8.085845"
+ y="1022.6802" />
+ <path
+ style="color:#000000;solid-opacity:1;fill:#1a8072;fill-opacity:0.85714285;fill-rule:evenodd;stroke:#1241a6;stroke-width:2.04706669;stroke-dashoffset:71.24002075;stroke-opacity:0.53448277"
+ id="path4397"
+ sodipodi:type="arc"
+ sodipodi:cx="22.411266"
+ sodipodi:cy="1036.6479"
+ sodipodi:rx="8.440196"
+ sodipodi:ry="8.0838118"
+ sodipodi:start="2.0413895e-11"
+ sodipodi:end="4.9529128"
+ d="m 30.851462,1036.6479 a 8.440196,8.0838118 0 0 1 -5.684712,7.6409 8.440196,8.0838118 0 0 1 -9.396506,-2.6518 8.440196,8.0838118 0 0 1 -0.45067,-9.3724 8.440196,8.0838118 0 0 1 9.102243,-3.4678 l -2.010551,7.8511 z" />
+ </g>
+</svg>
diff --git a/libs/flake/tools/KoInteractionStrategy.cpp b/libs/flake/tools/KoInteractionStrategy.cpp
index f8473a25c7..73bf380767 100644
--- a/libs/flake/tools/KoInteractionStrategy.cpp
+++ b/libs/flake/tools/KoInteractionStrategy.cpp
@@ -1,77 +1,72 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Thomas Zander <zander@kde.org>
* 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 "KoInteractionStrategy.h"
#include "KoInteractionStrategy_p.h"
#include "KoCanvasBase.h"
#include "KoShapeController.h"
#include "KoDocumentResourceManager.h"
#include <kundo2command.h>
KoInteractionStrategy::KoInteractionStrategy(KoToolBase *parent)
: d_ptr(new KoInteractionStrategyPrivate(parent))
{
}
void KoInteractionStrategy::cancelInteraction()
{
KUndo2Command *cmd = createCommand();
if (cmd) {
cmd->redo(); //some applications rely an redo being called here
cmd->undo();
delete cmd;
}
}
KoInteractionStrategy::KoInteractionStrategy(KoInteractionStrategyPrivate &dd)
: d_ptr(&dd)
{
}
KoInteractionStrategy::~KoInteractionStrategy()
{
delete d_ptr;
}
-void KoInteractionStrategy::handleCustomEvent(KoPointerEvent *event)
-{
- Q_UNUSED(event);
-}
-
void KoInteractionStrategy::paint(QPainter &, const KoViewConverter &)
{
}
KoToolBase *KoInteractionStrategy::tool() const
{
Q_D(const KoInteractionStrategy);
return d->tool;
}
uint KoInteractionStrategy::handleRadius() const
{
return tool()->canvas()->shapeController()->resourceManager()->handleRadius();
}
uint KoInteractionStrategy::grabSensitivity() const
{
return tool()->canvas()->shapeController()->resourceManager()->grabSensitivity();
}
diff --git a/libs/flake/tools/KoInteractionStrategy.h b/libs/flake/tools/KoInteractionStrategy.h
index 6c6e6374b4..fc6e47c750 100644
--- a/libs/flake/tools/KoInteractionStrategy.h
+++ b/libs/flake/tools/KoInteractionStrategy.h
@@ -1,112 +1,105 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2007, 2009 Thomas Zander <zander@kde.org>
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 KOINTERACTIONSTRATEGY_H
#define KOINTERACTIONSTRATEGY_H
#include "kritaflake_export.h"
#include <Qt>
class KoPointerEvent;
class KoViewConverter;
class KoInteractionStrategyPrivate;
class KoToolBase;
class KUndo2Command;
class QPointF;
class QPainter;
/**
* Abstract interface to define what actions a KoInteractionTool can do based on
* the Strategy design pattern.
* e.g, move, select, transform.
* KoInteractionStrategy is a Strategy baseclass for the KoInteractionTool and it
* defines the behavior in case the user clicks or drags the input device.
* The strategy is created in the createPolicy() function which defines the
* resulting behavior and initiates a move or a resize, for example.
* The mouseMove events are forwarded to the handleMouseMove() method and the interaction
* is either finished with finishInteraction() or cancelInteraction() (never both).
*/
class KRITAFLAKE_EXPORT KoInteractionStrategy
{
public:
/// constructor
explicit KoInteractionStrategy(KoToolBase *parent);
/// Destructor
virtual ~KoInteractionStrategy();
/**
* Reimplement this if the action needs to draw a "blob" on the canvas;
* that is, a transient decoration like a rubber band.
*/
virtual void paint(QPainter &painter, const KoViewConverter &converter);
/**
* Extending classes should implement this method to update the selectedShapes
* based on the new mouse position.
* @param mouseLocation the new location in pt
* @param modifiers OR-ed set of keys pressed.
*/
virtual void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) = 0;
- /**
- * Extending classes should implement this method to update the selectedShapes
- * based on the new pointer event. The default implementations does nothing.
- * @param event the new pointer event
- */
- virtual void handleCustomEvent(KoPointerEvent *event);
-
/**
* For interactions that are undo-able this method should be implemented to return such
* a command. Implementations should return 0 otherwise.
* @return a command, or 0.
*/
virtual KUndo2Command *createCommand() = 0;
/**
* This method will undo frames based interactions by calling createCommand()
* and unexecuting that.
*/
virtual void cancelInteraction();
/**
* Override to make final changes to the data on the end of an interaction.
*/
virtual void finishInteraction(Qt::KeyboardModifiers modifiers) = 0;
KoToolBase *tool() const;
protected:
/// constructor
KoInteractionStrategy(KoInteractionStrategyPrivate &);
KoInteractionStrategyPrivate *d_ptr;
/// Convenience function to get the global handle radius
uint handleRadius() const;
/// Cenvenience function to get the global grab sensitivity
uint grabSensitivity() const;
private:
Q_DECLARE_PRIVATE(KoInteractionStrategy)
};
#endif
diff --git a/libs/flake/tools/KoInteractionStrategyFactory.cpp b/libs/flake/tools/KoInteractionStrategyFactory.cpp
new file mode 100644
index 0000000000..d01d1f1e7b
--- /dev/null
+++ b/libs/flake/tools/KoInteractionStrategyFactory.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 "KoInteractionStrategyFactory.h"
+
+#include <QString>
+
+struct KoInteractionStrategyFactory::Private
+{
+ int priority = 0;
+ QString id;
+};
+
+KoInteractionStrategyFactory::KoInteractionStrategyFactory(int priority, const QString &id)
+ : m_d(new Private)
+{
+ m_d->priority = priority;
+ m_d->id = id;
+}
+
+KoInteractionStrategyFactory::~KoInteractionStrategyFactory()
+{
+}
+
+QString KoInteractionStrategyFactory::id() const
+{
+ return m_d->id;
+}
+
+int KoInteractionStrategyFactory::priority() const
+{
+ return m_d->priority;
+}
+
+bool KoInteractionStrategyFactory::compareLess(KoInteractionStrategyFactorySP f1, KoInteractionStrategyFactorySP f2)
+{
+ return f1->priority() < f2->priority();
+}
+
diff --git a/libs/flake/tools/KoInteractionStrategyFactory.h b/libs/flake/tools/KoInteractionStrategyFactory.h
new file mode 100644
index 0000000000..627e05daed
--- /dev/null
+++ b/libs/flake/tools/KoInteractionStrategyFactory.h
@@ -0,0 +1,58 @@
+/*
+ * 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 KOINTERACTIONSTRATEGYFACTORY_H
+#define KOINTERACTIONSTRATEGYFACTORY_H
+
+#include <QScopedPointer>
+#include <QSharedPointer>
+#include "kritaflake_export.h"
+
+class QString;
+class QPainter;
+class KoInteractionStrategy;
+class KoPointerEvent;
+class KoViewConverter;
+
+class KoInteractionStrategyFactory;
+typedef QSharedPointer<KoInteractionStrategyFactory> KoInteractionStrategyFactorySP;
+
+class KRITAFLAKE_EXPORT KoInteractionStrategyFactory
+{
+public:
+ KoInteractionStrategyFactory(int priority, const QString &id);
+ virtual ~KoInteractionStrategyFactory();
+
+ QString id() const;
+ int priority() const;
+
+ virtual KoInteractionStrategy* createStrategy(KoPointerEvent *ev) = 0;
+ virtual bool hoverEvent(KoPointerEvent *ev) = 0;
+ virtual bool paintOnHover(QPainter &painter, const KoViewConverter &converter) = 0;
+ virtual bool tryUseCustomCursor() = 0;
+
+ static bool compareLess(KoInteractionStrategyFactorySP f1, KoInteractionStrategyFactorySP f2);
+
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
+};
+
+
+
+#endif // KOINTERACTIONSTRATEGYFACTORY_H
diff --git a/libs/flake/tools/KoInteractionTool.cpp b/libs/flake/tools/KoInteractionTool.cpp
index dd624b75f6..ee0971ed59 100644
--- a/libs/flake/tools/KoInteractionTool.cpp
+++ b/libs/flake/tools/KoInteractionTool.cpp
@@ -1,129 +1,218 @@
/* This file is part of the KDE project
Copyright (C) 2006-2007, 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 "KoInteractionTool.h"
#include "KoInteractionTool_p.h"
#include "KoToolBase_p.h"
#include "KoPointerEvent.h"
#include "KoCanvasBase.h"
-#include "KoPanTool.h"
+
+#include "kis_global.h"
+#include "kis_assert.h"
+
KoInteractionTool::KoInteractionTool(KoCanvasBase *canvas)
: KoToolBase(*(new KoInteractionToolPrivate(this, canvas)))
{
}
KoInteractionTool::~KoInteractionTool()
{
}
void KoInteractionTool::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_D(KoInteractionTool);
- if (d->currentStrategy)
+
+ if (d->currentStrategy) {
d->currentStrategy->paint(painter, converter);
+ } else {
+ Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) {
+ // skip the rest of rendering if the factory asks for it
+ if (factory->paintOnHover(painter, converter)) break;
+ }
+ }
}
void KoInteractionTool::mousePressEvent(KoPointerEvent *event)
{
Q_D(KoInteractionTool);
if (d->currentStrategy) { // possible if the user presses an extra mouse button
cancelCurrentStrategy();
return;
}
- d->currentStrategy = createStrategy(event);
+ d->currentStrategy = createStrategyBase(event);
if (d->currentStrategy == 0)
event->ignore();
}
void KoInteractionTool::mouseMoveEvent(KoPointerEvent *event)
{
Q_D(KoInteractionTool);
d->lastPoint = event->point;
+
if (d->currentStrategy)
d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers());
- else
+ else {
+ Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) {
+ // skip the rest of rendering if the factory asks for it
+ if (factory->hoverEvent(event)) return;
+ }
+
event->ignore();
+ }
}
void KoInteractionTool::mouseReleaseEvent(KoPointerEvent *event)
{
Q_D(KoInteractionTool);
if (d->currentStrategy) {
d->currentStrategy->finishInteraction(event->modifiers());
KUndo2Command *command = d->currentStrategy->createCommand();
if (command)
d->canvas->addCommand(command);
delete d->currentStrategy;
d->currentStrategy = 0;
repaintDecorations();
} else
event->ignore();
}
void KoInteractionTool::keyPressEvent(QKeyEvent *event)
{
Q_D(KoInteractionTool);
event->ignore();
if (d->currentStrategy &&
(event->key() == Qt::Key_Control ||
event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift ||
event->key() == Qt::Key_Meta)) {
d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers());
event->accept();
}
}
void KoInteractionTool::keyReleaseEvent(QKeyEvent *event)
{
Q_D(KoInteractionTool);
- if (d->currentStrategy == 0) { // catch all cases where no current strategy is needed
- if (event->key() == Qt::Key_Space)
- emit activateTemporary(KoPanTool_ID);
- } else if (event->key() == Qt::Key_Escape) {
+
+ if (!d->currentStrategy) {
+ KoToolBase::keyReleaseEvent(event);
+ return;
+ }
+
+ if (event->key() == Qt::Key_Escape) {
cancelCurrentStrategy();
event->accept();
} else if (event->key() == Qt::Key_Control ||
event->key() == Qt::Key_Alt || event->key() == Qt::Key_Shift ||
event->key() == Qt::Key_Meta) {
d->currentStrategy->handleMouseMove(d->lastPoint, event->modifiers());
}
}
KoInteractionStrategy *KoInteractionTool::currentStrategy()
{
Q_D(KoInteractionTool);
return d->currentStrategy;
}
void KoInteractionTool::cancelCurrentStrategy()
{
Q_D(KoInteractionTool);
if (d->currentStrategy) {
d->currentStrategy->cancelInteraction();
delete d->currentStrategy;
d->currentStrategy = 0;
}
}
+KoInteractionStrategy *KoInteractionTool::createStrategyBase(KoPointerEvent *event)
+{
+ Q_D(KoInteractionTool);
+
+ Q_FOREACH (KoInteractionStrategyFactorySP factory, d->interactionFactories) {
+ KoInteractionStrategy *strategy = factory->createStrategy(event);
+ if (strategy) {
+ return strategy;
+ }
+ }
+
+ return createStrategy(event);
+}
+
+void KoInteractionTool::addInteractionFactory(KoInteractionStrategyFactory *factory)
+{
+ Q_D(KoInteractionTool);
+
+ Q_FOREACH (auto f, d->interactionFactories) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(f->id() != factory->id());
+ }
+
+ d->interactionFactories.append(toQShared(factory));
+ qSort(d->interactionFactories.begin(),
+ d->interactionFactories.end(),
+ KoInteractionStrategyFactory::compareLess);
+}
+
+void KoInteractionTool::removeInteractionFactory(const QString &id)
+{
+ Q_D(KoInteractionTool);
+ QList<KoInteractionStrategyFactorySP>::iterator it =
+ d->interactionFactories.begin();
+
+ while (it != d->interactionFactories.end()) {
+ if ((*it)->id() == id) {
+ it = d->interactionFactories.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
+bool KoInteractionTool::hasInteractioFactory(const QString &id)
+{
+ Q_D(KoInteractionTool);
+
+ Q_FOREACH (auto f, d->interactionFactories) {
+ if (f->id() == id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool KoInteractionTool::tryUseCustomCursor()
+{
+ Q_D(KoInteractionTool);
+
+ Q_FOREACH (auto f, d->interactionFactories) {
+ if (f->tryUseCustomCursor()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
KoInteractionTool::KoInteractionTool(KoInteractionToolPrivate &dd)
: KoToolBase(dd)
{
}
diff --git a/libs/flake/tools/KoInteractionTool.h b/libs/flake/tools/KoInteractionTool.h
index 4afc29985c..a028a6bc4a 100644
--- a/libs/flake/tools/KoInteractionTool.h
+++ b/libs/flake/tools/KoInteractionTool.h
@@ -1,98 +1,106 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2007, 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 KOINTERACTIONTOOL_H
#define KOINTERACTIONTOOL_H
#include "KoToolBase.h"
#include "kritaflake_export.h"
class KoInteractionStrategy;
+class KoInteractionStrategyFactory;
class KoInteractionToolPrivate;
#define KoInteractionTool_ID "InteractionTool"
/**
* The interaction tool adds to the normal KoToolBase class the concept of strategies
* as a means to get one tool to have different actions the user can perform using the mouse.
* Each time the user presses the mouse until she releases the mouse a strategy object
* will be created, used and disgarded.
* If the usage of a tool fits this pattern you need to inherit from this class instead of the
* plain KoToolBase and reimplement your createStrategy() method which returns a tool-specific
* strategy where all the real interaction code is placed.
* A tool can then become as simple as this;
* @code
class MyTool : public KoInteractionTool
{
public:
MyTool::MyTool(KoCanvasBase *canvas) : KoInteractionTool( canvas ) { }
KoInteractionStrategy *MyTool::createStrategy(KoPointerEvent *event) {
return new MyStrategy(this, m_canvas, event->point);
}
};
* @endcode
* Whereas your strategy (MyStrategy in the example) will contain the interaction code.
*/
class KRITAFLAKE_EXPORT KoInteractionTool : public KoToolBase
{
Q_OBJECT
public:
/**
* Constructor for basic interaction tool where user actions are translated
* and handled by interaction strategies of type KoInteractionStrategy.
* @param canvas the canvas this tool will be working for.
*/
explicit KoInteractionTool(KoCanvasBase *canvas);
virtual ~KoInteractionTool();
public:
virtual void paint(QPainter &painter, const KoViewConverter &converter);
virtual void mousePressEvent(KoPointerEvent *event);
virtual void mouseMoveEvent(KoPointerEvent *event);
virtual void mouseReleaseEvent(KoPointerEvent *event);
virtual void keyPressEvent(QKeyEvent *event);
virtual void keyReleaseEvent(QKeyEvent *event);
protected:
/// \internal
KoInteractionTool(KoInteractionToolPrivate &dd);
KoInteractionStrategy *currentStrategy(); ///< the strategy that is 'in progress'
/// Cancels the current strategy and deletes it.
void cancelCurrentStrategy();
/**
* Reimplement this factory method to create your strategy to be used for mouse interaction.
* @returns a new strategy, or 0 when there is nothing to do.
*/
+ KoInteractionStrategy *createStrategyBase(KoPointerEvent *event);
virtual KoInteractionStrategy *createStrategy(KoPointerEvent *event) = 0;
+ void addInteractionFactory(KoInteractionStrategyFactory *factory);
+ void removeInteractionFactory(const QString &id);
+ bool hasInteractioFactory(const QString &id);
+
+ bool tryUseCustomCursor();
+
private:
KoInteractionTool(const KoInteractionTool&);
KoInteractionTool& operator=(const KoInteractionTool&);
Q_DECLARE_PRIVATE(KoInteractionTool)
};
#endif /* KOINTERACTIONTOOL_H */
diff --git a/libs/flake/tools/KoInteractionTool_p.h b/libs/flake/tools/KoInteractionTool_p.h
index 069f24d6bc..af55eef98d 100644
--- a/libs/flake/tools/KoInteractionTool_p.h
+++ b/libs/flake/tools/KoInteractionTool_p.h
@@ -1,44 +1,46 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 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 KOINTERACTIONTOOLPRIVATE_H
#define KOINTERACTIONTOOLPRIVATE_H
#include "KoToolBase_p.h"
#include "KoInteractionStrategy.h"
+#include "KoInteractionStrategyFactory.h"
class KoInteractionToolPrivate : public KoToolBasePrivate
{
public:
KoInteractionToolPrivate(KoToolBase *qq, KoCanvasBase *canvas)
: KoToolBasePrivate(qq, canvas),
currentStrategy(0)
{
}
~KoInteractionToolPrivate() {
delete currentStrategy;
}
QPointF lastPoint;
KoInteractionStrategy *currentStrategy;
+ QList<QSharedPointer<KoInteractionStrategyFactory>> interactionFactories;
};
#endif
diff --git a/libs/flake/tools/KoPanTool.cpp b/libs/flake/tools/KoPanTool.cpp
index 14d4d13b7a..e5fde70305 100644
--- a/libs/flake/tools/KoPanTool.cpp
+++ b/libs/flake/tools/KoPanTool.cpp
@@ -1,129 +1,121 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Boudewijn Rempt <boud@kogmbh.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoPanTool.h"
#include "KoToolBase_p.h"
#include "KoPointerEvent.h"
#include "KoCanvasBase.h"
#include "KoCanvasController.h"
#include "KoCanvasControllerWidget.h"
#include "KoViewConverter.h"
#include <QKeyEvent>
#include <QScrollBar>
#include <FlakeDebug.h>
+#include "kis_assert.h"
+
+
KoPanTool::KoPanTool(KoCanvasBase *canvas)
: KoToolBase(canvas),
- m_controller(0),
- m_temporary(false)
+ m_controller(0)
{
}
bool KoPanTool::wantsAutoScroll() const
{
return false;
}
void KoPanTool::mousePressEvent(KoPointerEvent *event)
{
m_lastPosition = documentToViewport(event->point);
event->accept();
useCursor(QCursor(Qt::ClosedHandCursor));
}
void KoPanTool::mouseMoveEvent(KoPointerEvent *event)
{
Q_ASSERT(m_controller);
if (event->buttons() == 0)
return;
event->accept();
QPointF actualPosition = documentToViewport(event->point);
QPointF distance(m_lastPosition - actualPosition);
m_controller->pan(distance.toPoint());
m_lastPosition = actualPosition;
}
void KoPanTool::mouseReleaseEvent(KoPointerEvent *event)
{
event->accept();
useCursor(QCursor(Qt::OpenHandCursor));
- if (m_temporary)
- emit done();
}
void KoPanTool::keyPressEvent(QKeyEvent *event)
{
// XXX: Make widget-independent!
KoCanvasControllerWidget *canvasControllerWidget = dynamic_cast<KoCanvasControllerWidget*>(m_controller);
if (!canvasControllerWidget) {
return;
}
switch (event->key()) {
case Qt::Key_Up:
m_controller->pan(QPoint(0, -canvasControllerWidget->verticalScrollBar()->singleStep()));
break;
case Qt::Key_Down:
m_controller->pan(QPoint(0, canvasControllerWidget->verticalScrollBar()->singleStep()));
break;
case Qt::Key_Left:
m_controller->pan(QPoint(-canvasControllerWidget->horizontalScrollBar()->singleStep(), 0));
break;
case Qt::Key_Right:
m_controller->pan(QPoint(canvasControllerWidget->horizontalScrollBar()->singleStep(), 0));
break;
}
event->accept();
}
void KoPanTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &)
{
- if (m_controller == 0) {
- emit done();
- return;
- }
- m_temporary = toolActivation == TemporaryActivation;
- useCursor(QCursor(Qt::OpenHandCursor));
-}
+ Q_UNUSED(toolActivation);
+ KIS_ASSERT_RECOVER_NOOP(m_controller);
-void KoPanTool::customMoveEvent(KoPointerEvent * event)
-{
- m_controller->pan(QPoint(-event->x(), -event->y()));
- event->accept();
+ useCursor(QCursor(Qt::OpenHandCursor));
}
QPointF KoPanTool::documentToViewport(const QPointF &p)
{
Q_D(KoToolBase);
QPointF viewportPoint = d->canvas->viewConverter()->documentToView(p);
viewportPoint += d->canvas->documentOrigin();
viewportPoint += QPoint(m_controller->canvasOffsetX(), m_controller->canvasOffsetY());
return viewportPoint;
}
void KoPanTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
mousePressEvent(event);
}
diff --git a/libs/flake/tools/KoPanTool.h b/libs/flake/tools/KoPanTool.h
index 262aa2a03d..918d818821 100644
--- a/libs/flake/tools/KoPanTool.h
+++ b/libs/flake/tools/KoPanTool.h
@@ -1,76 +1,73 @@
/* This file is part of the KDE project
* Copyright (C) 2007 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.
*/
#ifndef KOPANTOOL_H
#define KOPANTOOL_H
#include "KoToolBase.h"
#include <QPointF>
class KoCanvasController;
#define KoPanTool_ID "PanTool"
/**
* This is the tool that allows you to move the canvas by dragging it and 'panning' around.
*/
class KoPanTool : public KoToolBase
{
public:
/**
* Constructor.
* @param canvas the canvas this tool works on.
*/
explicit KoPanTool(KoCanvasBase *canvas);
/// reimplemented from superclass
virtual bool wantsAutoScroll() const;
/// reimplemented from superclass
virtual void mousePressEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseMoveEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseReleaseEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void keyPressEvent(QKeyEvent *event);
/// reimplemented from superclass
virtual void paint(QPainter &, const KoViewConverter &) {}
/// reimplemented from superclass
virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
/// reimplemented method
- virtual void customMoveEvent(KoPointerEvent *event);
- /// reimplemented method
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/// set the canvasController this tool works on.
void setCanvasController(KoCanvasController *controller) {
m_controller = controller;
}
private:
QPointF documentToViewport(const QPointF &p);
KoCanvasController *m_controller;
QPointF m_lastPosition;
- bool m_temporary;
Q_DECLARE_PRIVATE(KoToolBase)
};
#endif
diff --git a/libs/flake/tools/KoParameterChangeStrategy.cpp b/libs/flake/tools/KoParameterChangeStrategy.cpp
index ec3f0ef0bb..d7442eed54 100644
--- a/libs/flake/tools/KoParameterChangeStrategy.cpp
+++ b/libs/flake/tools/KoParameterChangeStrategy.cpp
@@ -1,63 +1,77 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoParameterChangeStrategy.h"
#include "KoParameterChangeStrategy_p.h"
#include "KoParameterShape.h"
#include "commands/KoParameterHandleMoveCommand.h"
+#include <KoCanvasBase.h>
+#include "KoSnapGuide.h"
+
+
KoParameterChangeStrategy::KoParameterChangeStrategy(KoToolBase *tool, KoParameterShape *parameterShape, int handleId)
: KoInteractionStrategy(*(new KoParameterChangeStrategyPrivate(tool, parameterShape, handleId)))
{
+ Q_D(KoParameterChangeStrategy);
+ d->tool->canvas()->snapGuide()->setIgnoredShapes({parameterShape});
}
KoParameterChangeStrategy::KoParameterChangeStrategy(KoParameterChangeStrategyPrivate& dd)
: KoInteractionStrategy(dd)
{
}
KoParameterChangeStrategy::~KoParameterChangeStrategy()
{
}
void KoParameterChangeStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
{
Q_D(KoParameterChangeStrategy);
- d->parameterShape->moveHandle(d->handleId, mouseLocation, modifiers);
+
+ d->tool->canvas()->updateCanvas(d->tool->canvas()->snapGuide()->boundingRect());
+ QPointF snappedPosition = d->tool->canvas()->snapGuide()->snap(mouseLocation, modifiers);
+ d->tool->canvas()->updateCanvas(d->tool->canvas()->snapGuide()->boundingRect());
+
+ d->parameterShape->moveHandle(d->handleId, snappedPosition, modifiers);
d->lastModifierUsed = modifiers;
- d->releasePoint = mouseLocation;
+ d->releasePoint = snappedPosition;
}
KUndo2Command* KoParameterChangeStrategy::createCommand()
{
Q_D(KoParameterChangeStrategy);
+
+ d->tool->canvas()->snapGuide()->reset();
+
KoParameterHandleMoveCommand *cmd = 0;
// check if handle position changed
if (d->startPoint != QPointF(0, 0) && d->startPoint != d->releasePoint) {
cmd = new KoParameterHandleMoveCommand(d->parameterShape, d->handleId, d->startPoint, d->releasePoint, d->lastModifierUsed);
}
return cmd;
}
void KoParameterChangeStrategy::finishInteraction(Qt::KeyboardModifiers /*modifiers*/)
{
}
diff --git a/libs/flake/tools/KoPathPointRubberSelectStrategy.cpp b/libs/flake/tools/KoPathPointRubberSelectStrategy.cpp
index d58f6b0650..05d6dff98a 100644
--- a/libs/flake/tools/KoPathPointRubberSelectStrategy.cpp
+++ b/libs/flake/tools/KoPathPointRubberSelectStrategy.cpp
@@ -1,44 +1,55 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* 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.
*/
#include "KoPathPointRubberSelectStrategy.h"
#include "KoShapeRubberSelectStrategy_p.h"
#include "KoCanvasBase.h"
#include "KoPathTool.h"
#include "KoPathToolSelection.h"
KoPathPointRubberSelectStrategy::KoPathPointRubberSelectStrategy(KoPathTool *tool, const QPointF &clicked)
: KoShapeRubberSelectStrategy(tool, clicked)
, m_tool(tool)
{
}
+void KoPathPointRubberSelectStrategy::handleMouseMove(const QPointF &p, Qt::KeyboardModifiers modifiers)
+{
+ KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
+ if (selection && !(modifiers & Qt::ShiftModifier)) {
+ selection->clear();
+ }
+
+ KoShapeRubberSelectStrategy::handleMouseMove(p, modifiers);
+}
+
void KoPathPointRubberSelectStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_D(KoShapeRubberSelectStrategy);
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
- if (! selection)
+ if (!selection) {
return;
+ }
- selection->selectPoints(d->selectedRect(), !(modifiers & Qt::ControlModifier));
+ selection->selectPoints(d->selectedRect(), !(modifiers & Qt::ShiftModifier));
m_tool->canvas()->updateCanvas(d->selectedRect().normalized());
selection->repaint();
}
diff --git a/libs/flake/tools/KoPathPointRubberSelectStrategy.h b/libs/flake/tools/KoPathPointRubberSelectStrategy.h
index 87b04f7e2a..9817c8c4d1 100644
--- a/libs/flake/tools/KoPathPointRubberSelectStrategy.h
+++ b/libs/flake/tools/KoPathPointRubberSelectStrategy.h
@@ -1,44 +1,46 @@
/* This file is part of the KDE project
* Copyright (C) 2006 Jan Hambrecht <jaham@gmx.net>
* 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.
*/
#ifndef KOPATHPOINTRUBBERSELECTSTRATEGY_H
#define KOPATHPOINTRUBBERSELECTSTRATEGY_H
#include "KoShapeRubberSelectStrategy.h"
class KoPathTool;
/**
* @brief Strategy to rubber select points of a path shape
*/
class KoPathPointRubberSelectStrategy : public KoShapeRubberSelectStrategy
{
public:
KoPathPointRubberSelectStrategy(KoPathTool *tool, const QPointF &clicked);
virtual ~KoPathPointRubberSelectStrategy() {}
- virtual void finishInteraction(Qt::KeyboardModifiers modifiers);
+
+ void handleMouseMove(const QPointF &p, Qt::KeyboardModifiers modifiers) override;
+ void finishInteraction(Qt::KeyboardModifiers modifiers) override;
private:
/// pointer to the path tool
KoPathTool *m_tool;
Q_DECLARE_PRIVATE(KoShapeRubberSelectStrategy)
};
#endif /* KOPATHPOINTRUBBERSELECTSTRATEGY_H */
diff --git a/libs/flake/tools/KoPathSegmentChangeStrategy.cpp b/libs/flake/tools/KoPathSegmentChangeStrategy.cpp
index 9e1551dd36..b412fc9e87 100644
--- a/libs/flake/tools/KoPathSegmentChangeStrategy.cpp
+++ b/libs/flake/tools/KoPathSegmentChangeStrategy.cpp
@@ -1,165 +1,165 @@
/* 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 "KoPathSegmentChangeStrategy.h"
#include "KoPathShape.h"
#include "KoPathPoint.h"
#include "KoPathTool.h"
#include "KoSnapGuide.h"
#include "commands/KoPathControlPointMoveCommand.h"
#include "commands/KoPathSegmentTypeCommand.h"
#include <KoCanvasBase.h>
#include <klocalizedstring.h>
#include <limits>
#include <math.h>
KoPathSegmentChangeStrategy::KoPathSegmentChangeStrategy(KoPathTool *tool, const QPointF &pos, const KoPathPointData &segment, qreal segmentParam)
: KoInteractionStrategy(tool)
, m_originalPosition(pos)
, m_lastPosition(pos)
, m_tool(tool)
, m_segmentParam(segmentParam)
, m_pointData1(segment)
, m_pointData2(segment)
{
// The following value is a bit arbitrary, it would be more mathematically correct to use
// "std::numeric_limits<qreal>::epsilon()", but if the value is too small, when the user
// click near a control point it is relatively easy to create a path shape of almost
// infinite size, which blocks the application for a long period of time. A bigger value
// is mathematically uncorrect, but it avoids to block application, it also avoid to create
// an huge path shape by accident, and anyway, but it does not prevent the user to create one
// if they choose so.
const qreal eps = 1e-2;
// force segment parameter range to avoid division by zero
m_segmentParam = qBound(eps, m_segmentParam, qreal(1.0)-eps);
m_path = segment.pathShape;
m_segment = m_path->segmentByIndex(segment.pointIndex);
m_pointData2.pointIndex = m_path->pathPointIndex(m_segment.second());
m_originalSegmentDegree = m_segment.degree();
}
KoPathSegmentChangeStrategy::~KoPathSegmentChangeStrategy()
{
}
void KoPathSegmentChangeStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
{
m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect());
QPointF snappedPosition = m_tool->canvas()->snapGuide()->snap(mouseLocation, modifiers);
m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect());
QPointF localPos = m_path->documentToShape(snappedPosition);
if (m_segment.degree() == 1) {
// line segment is converted to a curve
KoPathSegmentTypeCommand cmd(m_pointData1, KoPathSegmentTypeCommand::Curve);
cmd.redo();
}
QPointF move1, move2;
if (m_segment.degree() == 2) {
// interpolate quadratic segment between segment start, mouse position and segment end
KoPathSegment ipol = KoPathSegment::interpolate(m_segment.first()->point(),
localPos,
m_segment.second()->point(),
m_segmentParam);
if (ipol.isValid()) {
move1 = move2 = ipol.controlPoints()[1] - m_segment.controlPoints()[1];
}
}
else if (m_segment.degree() == 3) {
/*
* method from inkscape, original method and idea borrowed from Simon Budig
* <simon@gimp.org> and the GIMP
* cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative()
*
* feel good is an arbitrary parameter that distributes the delta between handles
* if t of the drag point is less than 1/6 distance form the endpoint only
* the corresponding handle is adjusted. This matches the behavior in GIMP
*/
const qreal t = m_segmentParam;
qreal feel_good;
if (t <= 1.0 / 6.0)
feel_good = 0;
else if (t <= 0.5)
feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2;
else if (t <= 5.0 / 6.0)
feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
else
feel_good = 1;
QPointF lastLocalPos = m_path->documentToShape(m_lastPosition);
QPointF delta = localPos - lastLocalPos;
move2 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta;
move1 = (feel_good/(3*t*t*(1-t))) * delta;
}
m_path->update();
if(m_segment.first()->activeControlPoint2()) {
KoPathControlPointMoveCommand cmd(m_pointData1, move2, KoPathPoint::ControlPoint2);
cmd.redo();
}
if(m_segment.second()->activeControlPoint1()) {
KoPathControlPointMoveCommand cmd(m_pointData2, move1, KoPathPoint::ControlPoint1);
cmd.redo();
}
m_path->normalize();
m_path->update();
m_ctrlPoint1Move += move1;
m_ctrlPoint2Move += move2;
// save last mouse position
- m_lastPosition = mouseLocation;
+ m_lastPosition = snappedPosition;
}
void KoPathSegmentChangeStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
}
KUndo2Command* KoPathSegmentChangeStrategy::createCommand()
{
m_tool->canvas()->updateCanvas(m_tool->canvas()->snapGuide()->boundingRect());
bool hasControlPoint1 = m_segment.second()->activeControlPoint1();
bool hasControlPoint2 = m_segment.first()->activeControlPoint2();
KUndo2Command * cmd = new KUndo2Command(kundo2_i18n("Change Segment"));
if (m_originalSegmentDegree == 1) {
m_segment.first()->removeControlPoint2();
m_segment.second()->removeControlPoint1();
new KoPathSegmentTypeCommand(m_pointData1, KoPathSegmentTypeCommand::Curve, cmd);
}
if (hasControlPoint2) {
QPointF oldCtrlPointPos = m_segment.first()->controlPoint2()-m_ctrlPoint2Move;
m_segment.first()->setControlPoint2(oldCtrlPointPos);
new KoPathControlPointMoveCommand(m_pointData1, m_ctrlPoint2Move, KoPathPoint::ControlPoint2, cmd);
}
if (hasControlPoint1) {
QPointF oldCtrlPointPos = m_segment.second()->controlPoint1()-m_ctrlPoint1Move;
m_segment.second()->setControlPoint1(oldCtrlPointPos);
new KoPathControlPointMoveCommand(m_pointData2, m_ctrlPoint1Move, KoPathPoint::ControlPoint1, cmd);
}
return cmd;
}
diff --git a/libs/flake/tools/KoPathTool.cpp b/libs/flake/tools/KoPathTool.cpp
index ac5113fb92..2b2f80af88 100644
--- a/libs/flake/tools/KoPathTool.cpp
+++ b/libs/flake/tools/KoPathTool.cpp
@@ -1,963 +1,1221 @@
/* 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/KoPathPointMergeCommand.h"
+#include <commands/KoMultiPathPointMergeCommand.h>
+#include <commands/KoMultiPathPointJoinCommand.h>
#include "KoParameterShape.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 <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)
{
QActionGroup *points = new QActionGroup(this);
// m_pointTypeGroup->setExclusive(true);
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
m_actionPathPointCorner = actionRegistry->makeQAction("pathpoint-corner", this);
addAction("pathpoint-corner", m_actionPathPointCorner);
m_actionPathPointCorner->setData(KoPathPointTypeCommand::Corner);
points->addAction(m_actionPathPointCorner);
m_actionPathPointSmooth = actionRegistry->makeQAction("pathpoint-smooth", this);
addAction("pathpoint-smooth", m_actionPathPointSmooth);
m_actionPathPointSmooth->setData(KoPathPointTypeCommand::Smooth);
points->addAction(m_actionPathPointSmooth);
m_actionPathPointSymmetric = actionRegistry->makeQAction("pathpoint-symmetric", this);
addAction("pathpoint-symmetric", m_actionPathPointSymmetric);
m_actionPathPointSymmetric->setData(KoPathPointTypeCommand::Symmetric);
points->addAction(m_actionPathPointSymmetric);
m_actionCurvePoint = actionRegistry->makeQAction("pathpoint-curve", this);
addAction("pathpoint-curve", m_actionCurvePoint);
connect(m_actionCurvePoint, SIGNAL(triggered()), this, SLOT(pointToCurve()));
m_actionLinePoint = actionRegistry->makeQAction("pathpoint-line", this);
addAction("pathpoint-line", m_actionLinePoint);
connect(m_actionLinePoint, SIGNAL(triggered()), this, SLOT(pointToLine()));
m_actionLineSegment = actionRegistry->makeQAction("pathsegment-line", this);
addAction("pathsegment-line", m_actionLineSegment);
connect(m_actionLineSegment, SIGNAL(triggered()), this, SLOT(segmentToLine()));
m_actionCurveSegment = actionRegistry->makeQAction("pathsegment-curve", this);
addAction("pathsegment-curve", m_actionCurveSegment);
connect(m_actionCurveSegment, SIGNAL(triggered()), this, SLOT(segmentToCurve()));
m_actionAddPoint = actionRegistry->makeQAction("pathpoint-insert", this);
addAction("pathpoint-insert", m_actionAddPoint);
connect(m_actionAddPoint, SIGNAL(triggered()), this, SLOT(insertPoints()));
m_actionRemovePoint = actionRegistry->makeQAction("pathpoint-remove", this);
addAction("pathpoint-remove", m_actionRemovePoint);
connect(m_actionRemovePoint, SIGNAL(triggered()), this, SLOT(removePoints()));
m_actionBreakPoint = actionRegistry->makeQAction("path-break-point", this);
addAction("path-break-point", m_actionBreakPoint);
connect(m_actionBreakPoint, SIGNAL(triggered()), this, SLOT(breakAtPoint()));
m_actionBreakSegment = actionRegistry->makeQAction("path-break-segment", this);
addAction("path-break-segment", m_actionBreakSegment);
connect(m_actionBreakSegment, SIGNAL(triggered()), this, SLOT(breakAtSegment()));
m_actionJoinSegment = actionRegistry->makeQAction("pathpoint-join", this);
addAction("pathpoint-join", m_actionJoinSegment);
connect(m_actionJoinSegment, SIGNAL(triggered()), this, SLOT(joinPoints()));
m_actionMergePoints = actionRegistry->makeQAction("pathpoint-merge", this);
addAction("pathpoint-merge", m_actionMergePoints);
connect(m_actionMergePoints, SIGNAL(triggered()), this, SLOT(mergePoints()));
m_actionConvertToPath = actionRegistry->makeQAction("convert-to-path", this);
addAction("convert-to-path", m_actionConvertToPath);
connect(m_actionConvertToPath, SIGNAL(triggered()), this, SLOT(convertToPath()));
+ m_contextMenu.reset(new QMenu());
+
connect(points, SIGNAL(triggered(QAction*)), this, SLOT(pointTypeChanged(QAction*)));
connect(&m_pointSelection, SIGNAL(selectionChanged()), this, SLOT(pointSelectionChanged()));
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("Line/Curve"));
+ 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();
- QList<KoPathPointData> pointToChange;
- QList<KoPathPointData>::const_iterator it(selectedPoints.constBegin());
- for (; it != selectedPoints.constEnd(); ++it) {
- KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex);
- if (point) {
- if (point->activeControlPoint1() && point->activeControlPoint2()) {
- pointToChange.append(*it);
- }
- }
+ KUndo2Command *initialConversionCommand = createPointToCurveCommand(selectedPoints);
+
+ // conversion should happen before the c-tor
+ // of KoPathPointTypeCommand is executed!
+ if (initialConversionCommand) {
+ initialConversionCommand->redo();
}
- if (!pointToChange.isEmpty()) {
- KoPathPointTypeCommand *cmd = new KoPathPointTypeCommand(pointToChange,
- static_cast<KoPathPointTypeCommand::PointType>(type->data().toInt()));
- d->canvas->addCommand(cmd);
- updateActions();
+ 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);
- if (m_pointSelection.size() > 1) {
- QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
- if (!segments.isEmpty()) {
- KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, 0.5);
- d->canvas->addCommand(cmd);
+ QList<KoPathPointData> segments(m_pointSelection.selectedSegmentsData());
+ if (segments.size() == 1) {
+ qreal positionInSegment = 0.5;
+ if (m_activeSegment && m_activeSegment->isValid()) {
+ positionInSegment = m_activeSegment->positionOnSegment;
+ }
- foreach (KoPathPoint * p, cmd->insertedPoints()) {
- m_pointSelection.add(p, false);
- }
- updateActions();
+ 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;
}
- m_pointSelection.clear();
+ 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));
- updateActions();
}
}
}
void KoPathTool::pointToCurve()
{
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);
- }
+ KUndo2Command *command = createPointToCurveCommand(selectedPoints);
- if (! pointToChange.isEmpty()) {
- d->canvas->addCommand(new KoPathPointTypeCommand(pointToChange, KoPathPointTypeCommand::Curve));
- updateActions();
+ 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));
- updateActions();
}
}
}
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));
- updateActions();
}
}
}
void KoPathTool::convertToPath()
{
Q_D(KoToolBase);
QList<KoParameterShape*> shapesToConvert;
Q_FOREACH (KoShape *shape, m_pointSelection.selectedShapes()) {
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
if (parameterShape && parameterShape->isParametricShape())
shapesToConvert.append(parameterShape);
}
if (shapesToConvert.count())
d->canvas->addCommand(new KoParameterToPathCommand(shapesToConvert));
updateOptionsWidget();
}
-void KoPathTool::joinPoints()
+namespace {
+bool checkCanJoinToPoints(const KoPathPointData & pd1, const KoPathPointData & pd2)
{
- Q_D(KoToolBase);
- if (m_pointSelection.objectCount() == 1 && m_pointSelection.size() == 2) {
- QList<KoPathPointData> pd(m_pointSelection.selectedPointsData());
- const KoPathPointData & pd1 = pd.at(0);
- const KoPathPointData & pd2 = pd.at(1);
- KoPathShape * pathShape = pd1.pathShape;
- if (!pathShape->isClosedSubpath(pd1.pointIndex.first) &&
- (pd1.pointIndex.second == 0 ||
- pd1.pointIndex.second == pathShape->subpathPointCount(pd1.pointIndex.first) - 1) &&
- !pathShape->isClosedSubpath(pd2.pointIndex.first) &&
- (pd2.pointIndex.second == 0 ||
- pd2.pointIndex.second == pathShape->subpathPointCount(pd2.pointIndex.first) - 1)) {
- KoSubpathJoinCommand *cmd = new KoSubpathJoinCommand(pd1, pd2);
- d->canvas->addCommand(cmd);
- }
- updateActions();
- }
+ 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::mergePoints()
+void KoPathTool::mergePointsImpl(bool doJoin)
{
Q_D(KoToolBase);
- if (m_pointSelection.objectCount() != 1 || m_pointSelection.size() != 2)
+
+ 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);
- const KoPathPointIndex & index1 = pd1.pointIndex;
- const KoPathPointIndex & index2 = pd2.pointIndex;
-
- KoPathShape * path = pd1.pathShape;
- // check if subpaths are already closed
- if (path->isClosedSubpath(index1.first) || path->isClosedSubpath(index2.first))
- return;
- // check if first point is an endpoint
- if (index1.second != 0 && index1.second != path->subpathPointCount(index1.first)-1)
- return;
- // check if second point is an endpoint
- if (index2.second != 0 && index2.second != path->subpathPointCount(index2.first)-1)
+ if (!checkCanJoinToPoints(pd1, pd2)) {
return;
+ }
+
+ clearActivePointSelectionReferences();
- // now we can start merging the endpoints
- KoPathPointMergeCommand *cmd = new KoPathPointMergeCommand(pd1, pd2);
+ 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);
- updateActions();
+}
+
+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()));
- updateActions();
}
}
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)));
- updateActions();
}
}
}
void KoPathTool::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_D(KoToolBase);
- painter.setRenderHint(QPainter::Antialiasing, true);
- // use different colors so that it is also visible on a background of the same color
- painter.setBrush(Qt::white);
- painter.setPen(Qt::blue);
Q_FOREACH (KoPathShape *shape, m_pointSelection.selectedShapes()) {
- painter.save();
- painter.setTransform(shape->absoluteTransformation(&converter) * painter.transform());
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius);
+ helper.setHandleStyle(KisHandleStyle::primarySelection());
KoParameterShape * parameterShape = dynamic_cast<KoParameterShape*>(shape);
if (parameterShape && parameterShape->isParametricShape()) {
- parameterShape->paintHandles(painter, converter, m_handleRadius);
+ parameterShape->paintHandles(helper);
} else {
- shape->paintPoints(painter, converter, m_handleRadius);
+ shape->paintPoints(helper);
}
- painter.restore();
+ 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();
}
- painter.setBrush(Qt::green);
- painter.setPen(Qt::blue);
-
- m_pointSelection.paint(painter, converter);
-
- painter.setBrush(Qt::red);
- painter.setPen(Qt::blue);
+ m_pointSelection.paint(painter, converter, m_handleRadius);
if (m_activeHandle) {
if (m_activeHandle->check(m_pointSelection.selectedShapes())) {
- m_activeHandle->paint(painter, converter);
+ 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();
+
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&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);
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()) {
- KoPathPointIndex index = m_activeSegment->path->pathPointIndex(m_activeSegment->segmentStart);
- KoPathPointData data(m_activeSegment->path, index);
+
+ 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();
- delete m_activeSegment;
- m_activeSegment = 0;
} else {
- if ((event->modifiers() & Qt::ControlModifier) == 0) {
- m_pointSelection.clear();
+
+ 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();
}
- // start rubberband selection
- Q_ASSERT(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)
+ if (m_activeHandle) {
m_activeHandle->repaint();
+ }
+
+ if (m_activeSegment) {
+ repaintSegment(m_activeSegment);
+ }
+
return;
}
- delete m_activeSegment;
- m_activeSegment = 0;
+ 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)) {
//qDebug() << "handleId" << handleId;
m_activeHandle = new ConnectionHandle(this, connectionShape, handleId);
m_activeHandle->repaint();
return;
} else {
//qDebug() << "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)
+
+ 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;
-
- if (m_pointSelection.selectedShapes().count() == 1)
- emit pathChanged(m_pointSelection.selectedShapes().first());
- else
- emit pathChanged(0);
}
}
void KoPathTool::keyPressEvent(QKeyEvent *event)
{
Q_D(KoToolBase);
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()) {
-// TODO move these to the actions in the constructor.
- case Qt::Key_I: {
- KoDocumentResourceManager *rm = d->canvas->shapeController()->resourceManager();
- int handleRadius = rm->handleRadius();
- if (event->modifiers() & Qt::ControlModifier)
- handleRadius--;
- else
- handleRadius++;
- rm->setHandleRadius(handleRadius);
- break;
- }
#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_currentStrategy) return;
- PathSegment *s = segmentAtPoint(event->point);
- if (!s)
- return;
+ QScopedPointer<PathSegment> s(segmentAtPoint(event->point));
- if (s->isValid()) {
+ if (s && s->isValid()) {
QList<KoPathPointData> segments;
segments.append(KoPathPointData(s->path, s->path->pathPointIndex(s->segmentStart)));
KoPathPointInsertCommand *cmd = new KoPathPointInsertCommand(segments, s->positionOnSegment);
d->canvas->addCommand(cmd);
foreach (KoPathPoint * p, cmd->insertedPoints()) {
m_pointSelection.add(p, false);
}
updateActions();
event->accept();
+ } else if (!m_activeHandle && !m_activeSegment && m_activatedTemporarily) {
+ emit done();
}
- delete s;
}
KoPathTool::PathSegment* KoPathTool::segmentAtPoint(const QPointF &point)
{
Q_D(KoToolBase);
const int clickProximity = 5;
// convert click proximity to point using the current zoom level
QPointF clickOffset = d->canvas->viewConverter()->viewToDocument(QPointF(clickProximity, clickProximity));
// the max allowed distance from a segment
const qreal maxSquaredDistance = clickOffset.x()*clickOffset.x();
- PathSegment *segment = new PathSegment;
+ 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
QPointF p = shape->documentToShape(point);
// our region of interest, i.e. a region around our mouse position
QRectF roi(p - clickOffset, p + clickOffset);
qreal minSqaredDistance = HUGE_VAL;
// check all segments of this shape which intersect the region of interest
QList<KoPathSegment> segments = shape->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 < minSqaredDistance) {
segment->path = shape;
segment->segmentStart = s.first();
segment->positionOnSegment = nearestPointParam;
}
}
}
if (!segment->isValid()) {
- delete segment;
- segment = 0;
+ segment.reset();
}
- return segment;
+ return segment.take();
}
-void KoPathTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
+void KoPathTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
+ KoToolBase::activate(activation, shapes);
+
Q_D(KoToolBase);
- Q_UNUSED(toolActivation);
+
+ m_activatedTemporarily = activation == TemporaryActivation;
+
// retrieve the actual global handle radius
m_handleRadius = handleRadius();
d->canvas->snapGuide()->reset();
- repaintDecorations();
+ 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()));
+
+ initializeWithShapes(shapes.toList());
+}
+
+void KoPathTool::slotSelectionChanged()
+{
+ Q_D(KoToolBase);
+ QList<KoShape*> shapes =
+ d->canvas->selectedShapesProxy()->selection()->selectedEditableShapesAndDelegates();
+
+ initializeWithShapes(shapes);
+}
+
+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 (shape->isEditable() && pathShape) {
- // 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(pathShape->boundingRect());
+ if (pathShape && pathShape->isEditable()) {
selectedShapes.append(pathShape);
}
}
- if (selectedShapes.isEmpty()) {
- emit done();
- return;
+
+ if (selectedShapes != m_pointSelection.selectedShapes()) {
+ clearActivePointSelectionReferences();
+ m_pointSelection.setSelectedShapes(selectedShapes);
+ repaintDecorations();
}
- m_pointSelection.setSelectedShapes(selectedShapes);
- useCursor(m_selectCursor);
- connect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate()));
- updateOptionsWidget();
- updateActions();
-}
-void KoPathTool::activate()
-{
- Q_D(KoToolBase);
- QSet<KoShape*> shapes;
- Q_FOREACH (KoShape *shape, d->canvas->shapeManager()->selection()->selectedShapes()) {
- QSet<KoShape*> delegates = shape->toolDelegates();
- if (delegates.isEmpty()) {
- shapes << shape;
- } else {
- shapes += delegates;
- }
+ 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());
}
- activate(DefaultActivation, shapes);
+
+ 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;
}
- if (selectedShapes.count() == 1)
- emit pathChanged(selectedShapes.first());
- else
- emit pathChanged(0);
+
+ emit singleShapeChanged(selectedShapes.size() == 1 ? selectedShapes.first() : 0);
emit typeChanged(type);
}
void KoPathTool::updateActions()
{
- const bool hasPointsSelected = m_pointSelection.hasSelection();
- m_actionPathPointCorner->setEnabled(hasPointsSelected);
- m_actionPathPointSmooth->setEnabled(hasPointsSelected);
- m_actionPathPointSymmetric->setEnabled(hasPointsSelected);
- m_actionRemovePoint->setEnabled(hasPointsSelected);
- m_actionBreakPoint->setEnabled(hasPointsSelected);
- m_actionCurvePoint->setEnabled(hasPointsSelected);
- m_actionLinePoint->setEnabled(hasPointsSelected);
-
- bool hasSegmentsSelected = false;
- if (hasPointsSelected && m_pointSelection.size() > 1)
- hasSegmentsSelected = !m_pointSelection.selectedSegmentsData().isEmpty();
- m_actionAddPoint->setEnabled(hasSegmentsSelected);
- m_actionLineSegment->setEnabled(hasSegmentsSelected);
- m_actionCurveSegment->setEnabled(hasSegmentsSelected);
-
- const uint objectCount = m_pointSelection.objectCount();
- const uint pointCount = m_pointSelection.size();
- m_actionBreakSegment->setEnabled(objectCount == 1 && pointCount == 2);
- m_actionJoinSegment->setEnabled(objectCount == 1 && pointCount == 2);
- m_actionMergePoints->setEnabled(objectCount == 1 && pointCount == 2);
+ 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);
+
}
void KoPathTool::deactivate()
{
Q_D(KoToolBase);
- disconnect(d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(activate()));
+
+ 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();
+
+ 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();
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/KoPathTool.h b/libs/flake/tools/KoPathTool.h
index 95ab452b60..336fa8601b 100644
--- a/libs/flake/tools/KoPathTool.h
+++ b/libs/flake/tools/KoPathTool.h
@@ -1,152 +1,156 @@
/* 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 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.
*/
#ifndef KOPATHTOOL_H
#define KOPATHTOOL_H
#include "KoPathShape.h"
#include "KoToolBase.h"
#include "KoPathToolSelection.h"
+#include "kis_signal_auto_connection.h"
#include <QList>
#include <QCursor>
class QButtonGroup;
class KoCanvasBase;
class KoInteractionStrategy;
class KoPathToolHandle;
class KoParameterShape;
+class KUndo2Command;
class QAction;
+class QMenu;
+
/// The tool for editing a KoPathShape or a KoParameterShape.
/// See KoCreatePathTool for code handling the initial path creation.
class KRITAFLAKE_EXPORT KoPathTool : public KoToolBase
{
Q_OBJECT
public:
explicit KoPathTool(KoCanvasBase *canvas);
~KoPathTool();
- /// reimplemented
- virtual void paint(QPainter &painter, const KoViewConverter &converter);
-
- /// reimplemented
- virtual void repaintDecorations();
-
- /// reimplemented
- virtual void mousePressEvent(KoPointerEvent *event);
- /// reimplemented
- virtual void mouseMoveEvent(KoPointerEvent *event);
- /// reimplemented
- virtual void mouseReleaseEvent(KoPointerEvent *event);
- /// reimplemented
- virtual void keyPressEvent(QKeyEvent *event);
- /// reimplemented
- virtual void keyReleaseEvent(QKeyEvent *event);
- /// reimplemented
- virtual void mouseDoubleClickEvent(KoPointerEvent *event);
- /// reimplemented
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
- /// reimplemented
- virtual void deactivate();
-
- /// reimplemented
- virtual void deleteSelection();
-
- /// reimplemented
- virtual KoToolSelection* selection();
+ void paint(QPainter &painter, const KoViewConverter &converter) override;
+ void repaintDecorations() override;
+ void mousePressEvent(KoPointerEvent *event) override;
+ void mouseMoveEvent(KoPointerEvent *event) override;
+ void mouseReleaseEvent(KoPointerEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void keyReleaseEvent(QKeyEvent *event) override;
+ void mouseDoubleClickEvent(KoPointerEvent *event) override;
+ void activate(ToolActivation activation, const QSet<KoShape*> &shapes) override;
+ void deactivate() override;
+ void deleteSelection() override;
+ KoToolSelection* selection() override;
+ void requestUndoDuringStroke();
+ void requestStrokeCancellation() override;
+ void requestStrokeEnd() override;
+ void explicitUserStrokeEndRequest() override;
/// repaints the specified rect
void repaint(const QRectF &repaintRect);
+ QMenu* popupActionsMenu() override;
+
public Q_SLOTS:
void documentResourceChanged(int key, const QVariant & res);
Q_SIGNALS:
void typeChanged(int types);
- void pathChanged(KoPathShape* path); // TODO this is unused, can we remove this one?
+ void singleShapeChanged(KoPathShape* path);
+
protected:
/// reimplemented
virtual QList<QPointer<QWidget> > createOptionWidgets();
private:
struct PathSegment;
void updateOptionsWidget();
PathSegment* segmentAtPoint(const QPointF &point);
private Q_SLOTS:
void pointTypeChanged(QAction *type);
void insertPoints();
void removePoints();
void segmentToLine();
void segmentToCurve();
void convertToPath();
void joinPoints();
void mergePoints();
void breakAtPoint();
void breakAtSegment();
void pointSelectionChanged();
void updateActions();
void pointToLine();
void pointToCurve();
- void activate();
+ void slotSelectionChanged();
+
+private:
+ void clearActivePointSelectionReferences();
+ void initializeWithShapes(const QList<KoShape*> shapes);
+ KUndo2Command* createPointToCurveCommand(const QList<KoPathPointData> &points);
+ void repaintSegment(PathSegment *pathSegment);
+ void mergePointsImpl(bool doJoin);
protected:
KoPathToolSelection m_pointSelection; ///< the point selection
QCursor m_selectCursor;
private:
-
KoPathToolHandle * m_activeHandle; ///< the currently active handle
int m_handleRadius; ///< the radius of the control point handles
uint m_grabSensitivity; ///< the grab sensitivity
QPointF m_lastPoint; ///< needed for interaction strategy
PathSegment *m_activeSegment;
// make a frind so that it can test private member/methods
friend class TestPathTool;
KoInteractionStrategy *m_currentStrategy; ///< the rubber selection strategy
QButtonGroup *m_pointTypeGroup;
QAction *m_actionPathPointCorner;
QAction *m_actionPathPointSmooth;
QAction *m_actionPathPointSymmetric;
QAction *m_actionCurvePoint;
QAction *m_actionLinePoint;
QAction *m_actionLineSegment;
QAction *m_actionCurveSegment;
QAction *m_actionAddPoint;
QAction *m_actionRemovePoint;
QAction *m_actionBreakPoint;
QAction *m_actionBreakSegment;
QAction *m_actionJoinSegment;
QAction *m_actionMergePoints;
QAction *m_actionConvertToPath;
QCursor m_moveCursor;
+ bool m_activatedTemporarily;
+ QScopedPointer<QMenu> m_contextMenu;
+ KisSignalAutoConnectionsStore m_canvasConnections;
Q_DECLARE_PRIVATE(KoToolBase)
};
#endif
diff --git a/libs/flake/tools/KoPathToolFactory.cpp b/libs/flake/tools/KoPathToolFactory.cpp
index 7f72474cd6..bdcdd8dc08 100644
--- a/libs/flake/tools/KoPathToolFactory.cpp
+++ b/libs/flake/tools/KoPathToolFactory.cpp
@@ -1,44 +1,44 @@
/* This file is part of the KDE project
* 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 "KoPathToolFactory.h"
#include "KoPathTool.h"
#include "KoPathShape.h"
#include <KoIcon.h>
#include <klocalizedstring.h>
KoPathToolFactory::KoPathToolFactory()
: KoToolFactoryBase("PathTool")
{
- setToolTip(i18n("Path editing"));
+ setToolTip(i18n("Edit Shapes Tool"));
setSection(mainToolType());
setIconName(koIconNameCStr("shape_handling"));
- setPriority(2);
- setActivationShapeId(KoPathShapeId);
+ setPriority(1);
+ setActivationShapeId("flake/always,KoPathShape");
}
KoPathToolFactory::~KoPathToolFactory()
{
}
KoToolBase * KoPathToolFactory::createTool(KoCanvasBase *canvas)
{
return new KoPathTool(canvas);
}
diff --git a/libs/flake/tools/KoPathToolHandle.cpp b/libs/flake/tools/KoPathToolHandle.cpp
index eb2c50b61b..58c7ea3328 100644
--- a/libs/flake/tools/KoPathToolHandle.cpp
+++ b/libs/flake/tools/KoPathToolHandle.cpp
@@ -1,210 +1,219 @@
/* 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)
+void PointHandle::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius)
{
- painter.save();
- painter.setTransform(m_activePoint->parent()->absoluteTransformation(&converter) * painter.transform());
- KoShape::applyConversion(painter, converter);
-
KoPathToolSelection * selection = dynamic_cast<KoPathToolSelection*>(m_tool->selection());
KoPathPoint::PointType type = KoPathPoint::Node;
- if (selection && selection->contains(m_activePoint))
+ if (selection && selection->contains(m_activePoint)) {
type = KoPathPoint::All;
- m_activePoint->paint(painter, handleRadius(), type);
- painter.restore();
+ }
+
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&painter, m_activePoint->parent(), converter, handleRadius);
+ helper.setHandleStyle(KisHandleStyle::highlightedPrimaryHandles());
+ m_activePoint->paint(helper, type);
}
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::ShiftModifier) == 0) { // no shift pressed.
+ 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::ControlModifier) {
+ 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)
+void ParameterHandle::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius)
{
- painter.save();
- painter.setTransform(m_parameterShape->absoluteTransformation(&converter) * painter.transform());
-
- m_parameterShape->paintHandle(painter, converter, m_handleId, handleRadius());
- painter.restore();
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&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/KoPathToolHandle.h b/libs/flake/tools/KoPathToolHandle.h
index f1e44b8225..a363a6ed7b 100644
--- a/libs/flake/tools/KoPathToolHandle.h
+++ b/libs/flake/tools/KoPathToolHandle.h
@@ -1,94 +1,99 @@
/* 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.
*/
#ifndef KOPATHTOOLHANDLE_H
#define KOPATHTOOLHANDLE_H
#include <KoPathPoint.h>
#include "KoInteractionStrategy.h"
#include <QList>
#include <QRect>
class KoPathTool;
class KoParameterShape;
class KoViewConverter;
class KoPointerEvent;
class QPainter;
class KoPathShape;
+class KisHandlePainterHelper;
+
class KoPathToolHandle
{
public:
explicit KoPathToolHandle(KoPathTool *tool);
virtual ~KoPathToolHandle();
- virtual void paint(QPainter &painter, const KoViewConverter &converter) = 0;
+ virtual void paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius) = 0;
virtual void repaint() const = 0;
virtual KoInteractionStrategy * handleMousePress(KoPointerEvent *event) = 0;
// test if handle is still valid
virtual bool check(const QList<KoPathShape*> &selectedShapes) = 0;
+ virtual void trySelectHandle() {};
+
protected:
uint handleRadius() const;
KoPathTool *m_tool;
};
class PointHandle : public KoPathToolHandle
{
public:
PointHandle(KoPathTool *tool, KoPathPoint *activePoint, KoPathPoint::PointType activePointType);
- void paint(QPainter &painter, const KoViewConverter &converter);
+ void paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius);
void repaint() const;
KoInteractionStrategy *handleMousePress(KoPointerEvent *event);
virtual bool check(const QList<KoPathShape*> &selectedShapes);
KoPathPoint *activePoint() const;
KoPathPoint::PointType activePointType() const;
+ void trySelectHandle() override;
private:
KoPathPoint *m_activePoint;
KoPathPoint::PointType m_activePointType;
mutable QRectF m_oldRepaintedRect;
};
class ParameterHandle : public KoPathToolHandle
{
public:
ParameterHandle(KoPathTool *tool, KoParameterShape *parameterShape, int handleId);
- void paint(QPainter &painter, const KoViewConverter &converter);
+ void paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius);
void repaint() const;
KoInteractionStrategy *handleMousePress(KoPointerEvent *event);
- virtual bool check(const QList<KoPathShape*> &selectedShapes);
+ bool check(const QList<KoPathShape*> &selectedShapes);
protected:
KoParameterShape *m_parameterShape;
int m_handleId;
};
class ConnectionHandle : public ParameterHandle
{
public:
ConnectionHandle(KoPathTool *tool, KoParameterShape *parameterShape, int handleId);
// XXX: Later: create a paint even to distinguish a connection
// handle from another handle type
KoInteractionStrategy *handleMousePress(KoPointerEvent *event);
};
#endif // KOPATHTOOLHANDLE_H
diff --git a/libs/flake/tools/KoPathToolSelection.cpp b/libs/flake/tools/KoPathToolSelection.cpp
index 10a8614616..45dea7a181 100644
--- a/libs/flake/tools/KoPathToolSelection.cpp
+++ b/libs/flake/tools/KoPathToolSelection.cpp
@@ -1,247 +1,244 @@
/* 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)
+void KoPathToolSelection::paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius)
{
- int handleRadius = m_tool->canvas()->shapeController()->resourceManager()->handleRadius();
-
PathShapePointMap::iterator it(m_shapePointMap.begin());
for (; it != m_shapePointMap.end(); ++it) {
- painter.save();
-
- painter.setTransform(it.key()->absoluteTransformation(&converter) * painter.transform());
- KoShape::applyConversion(painter, converter);
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&painter, it.key(), converter, handleRadius);
+ helper.setHandleStyle(KisHandleStyle::selectedPrimaryHandles());
- Q_FOREACH (KoPathPoint *p, it.value())
- p->paint(painter, handleRadius, KoPathPoint::All);
-
- painter.restore();
+ 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());
qSort(pd);
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)
{
m_selectedShapes = shapes;
}
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();
}
diff --git a/libs/flake/tools/KoPathToolSelection.h b/libs/flake/tools/KoPathToolSelection.h
index c0d1471f99..85abe777e3 100644
--- a/libs/flake/tools/KoPathToolSelection.h
+++ b/libs/flake/tools/KoPathToolSelection.h
@@ -1,160 +1,160 @@
/* 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.
*/
#ifndef KOPATHTOOLSELECTION_H
#define KOPATHTOOLSELECTION_H
#include <KoToolSelection.h>
#include <KoPathShape.h>
class KoPathTool;
class KoPathPoint;
class KoPathPointData;
class KoViewConverter;
class QPainter;
/**
* @brief Handle the selection of points
*
* This class handles the selection of points. It makes sure
* the canvas is repainted when the selection changes.
*/
class KRITAFLAKE_EXPORT KoPathToolSelection : public KoToolSelection
{
Q_OBJECT
public:
explicit KoPathToolSelection(KoPathTool *tool);
~KoPathToolSelection();
/// @brief Draw the selected points
- void paint(QPainter &painter, const KoViewConverter &converter);
+ void paint(QPainter &painter, const KoViewConverter &converter, qreal handleRadius);
/**
* @brief Add a point to the selection
*
* @param point to add to the selection
* @param clear if true the selection will be cleared before adding the point
*/
void add(KoPathPoint *point, bool clear);
/**
* @brief Remove a point form the selection
*
* @param point to remove from the selection
*/
void remove(KoPathPoint *point);
/**
* @brief Clear the selection
*/
void clear();
/**
* @brief Select points in rect
*
* @param rect the selection rectangle in document coordinates
* @param clearSelection if set clear the current selection before the selection
*/
void selectPoints(const QRectF &rect, bool clearSelection);
/**
* @brief Get the number of path objects in the selection
*
* @return number of path object in the point selection
*/
int objectCount() const;
/**
* @brief Get the number of path points in the selection
*
* @return number of points in the selection
*/
int size() const;
/**
* @brief Check if a point is in the selection
*
* @return true when the point is in the selection, false otherwise
*/
bool contains(KoPathPoint *point);
/**
* @brief Get all selected points
*
* @return set of selected points
*/
const QSet<KoPathPoint *> &selectedPoints() const;
/**
* @brief Get the point data of all selected points
*
* This is subject to change
*/
QList<KoPathPointData> selectedPointsData() const;
/**
* @brief Get the point data of all selected segments
*
* This is subject to change
*/
QList<KoPathPointData> selectedSegmentsData() const;
/// Returns list of selected shapes
QList<KoPathShape*> selectedShapes() const;
/// Sets list of selected shapes
void setSelectedShapes(const QList<KoPathShape*> shapes);
/**
* @brief trigger a repaint
*/
void repaint();
/**
* @brief Update the selection to contain only valid points
*
* This function checks which points are no longer valid and removes them
* from the selection.
* If e.g. some points are selected and the shape which contains the points
* is removed by undo, the points are no longer valid and have therefore to
* be removed from the selection.
*/
void update();
/// reimplemented from KoToolSelection
virtual bool hasSelection();
Q_SIGNALS:
void selectionChanged();
private:
typedef QMap<KoPathShape *, QSet<KoPathPoint *> > PathShapePointMap;
QSet<KoPathPoint *> m_selectedPoints;
PathShapePointMap m_shapePointMap;
KoPathTool *m_tool;
QList<KoPathShape*> m_selectedShapes;
};
#endif // PATHTOOLSELECTION_H
diff --git a/libs/flake/tools/KoShapeRubberSelectStrategy.cpp b/libs/flake/tools/KoShapeRubberSelectStrategy.cpp
index 0b0a0e8a3d..458c3b90d9 100644
--- a/libs/flake/tools/KoShapeRubberSelectStrategy.cpp
+++ b/libs/flake/tools/KoShapeRubberSelectStrategy.cpp
@@ -1,117 +1,140 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
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.
*/
#include "KoShapeRubberSelectStrategy.h"
#include "KoShapeRubberSelectStrategy_p.h"
#include "KoViewConverter.h"
#include <QPainter>
#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoCanvasBase.h"
+
KoShapeRubberSelectStrategy::KoShapeRubberSelectStrategy(KoToolBase *tool, const QPointF &clicked, bool useSnapToGrid)
: KoInteractionStrategy(*(new KoShapeRubberSelectStrategyPrivate(tool)))
{
Q_D(KoShapeRubberSelectStrategy);
d->snapGuide->enableSnapStrategies(KoSnapGuide::GridSnapping);
d->snapGuide->enableSnapping(useSnapToGrid);
d->selectRect = QRectF(d->snapGuide->snap(clicked, 0), QSizeF(0, 0));
}
void KoShapeRubberSelectStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
Q_D(KoShapeRubberSelectStrategy);
painter.setRenderHint(QPainter::Antialiasing, false);
- QColor selectColor(Qt::blue); // TODO make configurable
- selectColor.setAlphaF(0.5);
- QBrush sb(selectColor, Qt::SolidPattern);
- painter.setPen(QPen(sb, 0));
- painter.setBrush(sb);
+ const QColor crossingColor(80,130,8);
+ const QColor coveringColor(8,60,167);
+
+ QColor selectColor(
+ currentMode() == CrossingSelection ?
+ crossingColor : coveringColor);
+
+ selectColor.setAlphaF(0.8);
+ painter.setPen(QPen(selectColor, 0));
+
+ selectColor.setAlphaF(0.4);
+ const QBrush fillBrush(selectColor);
+ painter.setBrush(fillBrush);
+
QRectF paintRect = converter.documentToView(d->selectedRect());
paintRect = paintRect.normalized();
- paintRect.adjust(0., -0.5, 0.5, 0.);
+
painter.drawRect(paintRect);
}
void KoShapeRubberSelectStrategy::handleMouseMove(const QPointF &p, Qt::KeyboardModifiers modifiers)
{
Q_D(KoShapeRubberSelectStrategy);
QPointF point = d->snapGuide->snap(p, modifiers);
- if ((modifiers & Qt::AltModifier) != 0) {
+ if (modifiers & Qt::ControlModifier) {
d->tool->canvas()->updateCanvas(d->selectedRect());
d->selectRect.moveTopLeft(d->selectRect.topLeft() - (d->lastPos - point));
d->lastPos = point;
d->tool->canvas()->updateCanvas(d->selectedRect());
return;
}
d->lastPos = point;
QPointF old = d->selectRect.bottomRight();
d->selectRect.setBottomRight(point);
/*
+---------------|--+
| | | We need to figure out rects A and B based on the two points. BUT
| old | A| we need to do that even if the points are switched places
| \ | | (i.e. the rect got smaller) and even if the rect is mirrored
+---------------+ | in either the horizontal or vertical axis.
| B |
+------------------+
`- point
*/
QPointF x1 = old;
x1.setY(d->selectRect.topLeft().y());
qreal h1 = point.y() - x1.y();
qreal h2 = old.y() - x1.y();
QRectF A(x1, QSizeF(point.x() - x1.x(), point.y() < d->selectRect.top() ? qMin(h1, h2) : qMax(h1, h2)));
A = A.normalized();
d->tool->canvas()->updateCanvas(A);
QPointF x2 = old;
x2.setX(d->selectRect.topLeft().x());
qreal w1 = point.x() - x2.x();
qreal w2 = old.x() - x2.x();
QRectF B(x2, QSizeF(point.x() < d->selectRect.left() ? qMin(w1, w2) : qMax(w1, w2), point.y() - x2.y()));
B = B.normalized();
d->tool->canvas()->updateCanvas(B);
}
void KoShapeRubberSelectStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_D(KoShapeRubberSelectStrategy);
Q_UNUSED(modifiers);
KoSelection * selection = d->tool->canvas()->shapeManager()->selection();
- QList<KoShape *> shapes(d->tool->canvas()->shapeManager()->shapesAt(d->selectRect));
+
+ const bool useContainedMode = currentMode() == CoveringSelection;
+
+ QList<KoShape *> shapes =
+ d->tool->canvas()->shapeManager()->
+ shapesAt(d->selectedRect(), true, useContainedMode);
+
Q_FOREACH (KoShape * shape, shapes) {
- if (!(shape->isSelectable() && shape->isVisible()))
- continue;
+ if (!shape->isSelectable()) continue;
+
selection->select(shape);
}
+
d->tool->repaintDecorations();
d->tool->canvas()->updateCanvas(d->selectedRect());
}
+KoShapeRubberSelectStrategy::SelectionMode KoShapeRubberSelectStrategy::currentMode() const
+{
+ Q_D(const KoShapeRubberSelectStrategy);
+ return d->selectRect.left() < d->selectRect.right() ? CoveringSelection : CrossingSelection;
+}
+
KUndo2Command *KoShapeRubberSelectStrategy::createCommand()
{
return 0;
}
diff --git a/libs/flake/tools/KoShapeRubberSelectStrategy.h b/libs/flake/tools/KoShapeRubberSelectStrategy.h
index f0ec46fc3b..5981814e13 100644
--- a/libs/flake/tools/KoShapeRubberSelectStrategy.h
+++ b/libs/flake/tools/KoShapeRubberSelectStrategy.h
@@ -1,64 +1,74 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann <zachmann@kde.org>
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 KOSHAPERUBBERSELECTSTRATEGY_H
#define KOSHAPERUBBERSELECTSTRATEGY_H
#include "KoInteractionStrategy.h"
#include <QRectF>
#include "kritaflake_export.h"
class KoToolBase;
class KoShapeRubberSelectStrategyPrivate;
/**
* Implement the rubber band selection of flake objects.
+ *
+ * When the user selects stuff in left-to-right way, selection is in "covering"
+ * (or "containing") mode, when in "left-to-right" in "crossing" mode
*/
class KRITAFLAKE_EXPORT KoShapeRubberSelectStrategy : public KoInteractionStrategy
{
public:
/**
* Constructor that initiates the rubber select.
* A rubber select is basically rectangle area that the user drags out
* from @p clicked to a point later provided in the handleMouseMove() continuously
* showing a semi-transarant 'rubber-mat' over the objects it is about to select.
* @param tool the parent tool which controls this strategy
* @param clicked the initial point that the user depressed (in pt).
* @param useSnapToGrid use the snap-to-grid settings while doing the rubberstamp.
*/
KoShapeRubberSelectStrategy(KoToolBase *tool, const QPointF &clicked, bool useSnapToGrid = false);
virtual void paint(QPainter &painter, const KoViewConverter &converter);
virtual void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers);
virtual KUndo2Command *createCommand();
virtual void finishInteraction(Qt::KeyboardModifiers modifiers);
protected:
/// constructor
KoShapeRubberSelectStrategy(KoShapeRubberSelectStrategyPrivate &);
+ enum SelectionMode {
+ CrossingSelection,
+ CoveringSelection
+ };
+
+ virtual SelectionMode currentMode() const;
+
private:
Q_DECLARE_PRIVATE(KoShapeRubberSelectStrategy)
};
#endif /* KOSHAPERUBBERSELECTSTRATEGY_H */
diff --git a/libs/flake/tools/KoZoomStrategy.cpp b/libs/flake/tools/KoZoomStrategy.cpp
index f4c22ed984..36dd71ac03 100644
--- a/libs/flake/tools/KoZoomStrategy.cpp
+++ b/libs/flake/tools/KoZoomStrategy.cpp
@@ -1,71 +1,76 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* 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 "KoZoomStrategy.h"
#include "KoShapeRubberSelectStrategy_p.h"
#include "KoZoomTool.h"
#include "KoCanvasBase.h"
#include "KoCanvasController.h"
#include "KoViewConverter.h"
#include <FlakeDebug.h>
KoZoomStrategy::KoZoomStrategy(KoZoomTool *tool, KoCanvasController *controller, const QPointF &clicked)
: KoShapeRubberSelectStrategy(tool, clicked, false),
m_controller(controller),
m_forceZoomOut(false)
{
}
void KoZoomStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_D(KoShapeRubberSelectStrategy);
QRect pixelRect = m_controller->canvas()->viewConverter()->documentToView(d->selectedRect()).toRect();
pixelRect.translate(m_controller->canvas()->documentOrigin());
bool m_zoomOut = m_forceZoomOut;
if (modifiers & Qt::ControlModifier) {
m_zoomOut = !m_zoomOut;
}
if (m_zoomOut) {
m_controller->zoomOut(pixelRect.center());
} else if (pixelRect.width() > 5 && pixelRect.height() > 5) {
m_controller->zoomTo(pixelRect);
} else {
m_controller->zoomIn(pixelRect.center());
}
}
void KoZoomStrategy::cancelInteraction()
{
Q_D(KoShapeRubberSelectStrategy);
d->tool->repaintDecorations();
d->tool->canvas()->updateCanvas(d->selectedRect().toRect().normalized());
}
+KoShapeRubberSelectStrategy::SelectionMode KoZoomStrategy::currentMode() const
+{
+ return CoveringSelection;
+}
+
void KoZoomStrategy::forceZoomOut()
{
m_forceZoomOut = true;
}
void KoZoomStrategy::forceZoomIn()
{
m_forceZoomOut = false;
}
diff --git a/libs/flake/tools/KoZoomStrategy.h b/libs/flake/tools/KoZoomStrategy.h
index 1db4d09d91..bff94e6e2b 100644
--- a/libs/flake/tools/KoZoomStrategy.h
+++ b/libs/flake/tools/KoZoomStrategy.h
@@ -1,57 +1,60 @@
/* This file is part of the KDE project
* 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.
*/
#ifndef KOZOOMSTRATEGY_H
#define KOZOOMSTRATEGY_H
#include "KoShapeRubberSelectStrategy.h"
class KoCanvasController;
class KoZoomTool;
/**
* //internal
* This is a strategy for the KoZoomTool which will be used to do the actual zooming
*/
class KoZoomStrategy : public KoShapeRubberSelectStrategy
{
public:
/**
* constructor
* @param tool the parent tool this strategy is for
* @param controller the canvas controller that wraps the canvas the tool is acting on.
* @param clicked the location (in documnet points) where the interaction starts.
*/
KoZoomStrategy(KoZoomTool *tool, KoCanvasController *controller, const QPointF &clicked);
void forceZoomOut();
void forceZoomIn();
/// Execute the zoom
virtual void finishInteraction(Qt::KeyboardModifiers modifiers);
virtual void cancelInteraction();
+
+protected:
+ SelectionMode currentMode() const override;
private:
KoCanvasController *m_controller;
bool m_forceZoomOut;
Q_DECLARE_PRIVATE(KoShapeRubberSelectStrategy)
};
#endif
diff --git a/libs/flake/tools/PathToolOptionWidget.cpp b/libs/flake/tools/PathToolOptionWidget.cpp
index bd070180d3..657244b492 100644
--- a/libs/flake/tools/PathToolOptionWidget.cpp
+++ b/libs/flake/tools/PathToolOptionWidget.cpp
@@ -1,56 +1,154 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "PathToolOptionWidget.h"
#include "KoPathTool.h"
#include <QAction>
+#include <KoPathShape.h>
+#include <KoParameterShape.h>
+#include <KoShapeConfigWidgetBase.h>
+#include <QVBoxLayout>
+#include <KoCanvasBase.h>
+#include <KoShapeRegistry.h>
+#include <KoShapeFactoryBase.h>
+#include <KoUnit.h>
+#include "kis_assert.h"
+
PathToolOptionWidget::PathToolOptionWidget(KoPathTool *tool, QWidget *parent)
- : QWidget(parent)
+ : QWidget(parent),
+ m_currentShape(0),
+ m_currentPanel(0),
+ m_canvas(tool->canvas())
+
{
widget.setupUi(this);
widget.corner->setDefaultAction(tool->action("pathpoint-corner"));
widget.smooth->setDefaultAction(tool->action("pathpoint-smooth"));
widget.symmetric->setDefaultAction(tool->action("pathpoint-symmetric"));
widget.lineSegment->setDefaultAction(tool->action("pathsegment-line"));
widget.curveSegment->setDefaultAction(tool->action("pathsegment-curve"));
widget.linePoint->setDefaultAction(tool->action("pathpoint-line"));
widget.curvePoint->setDefaultAction(tool->action("pathpoint-curve"));
widget.addPoint->setDefaultAction(tool->action("pathpoint-insert"));
widget.removePoint->setDefaultAction(tool->action("pathpoint-remove"));
widget.breakPoint->setDefaultAction(tool->action("path-break-point"));
widget.breakSegment->setDefaultAction(tool->action("path-break-segment"));
widget.joinSegment->setDefaultAction(tool->action("pathpoint-join"));
widget.mergePoints->setDefaultAction(tool->action("pathpoint-merge"));
+ widget.wdgShapeProperties->setVisible(false);
+ widget.lineShapeProperties->setVisible(false);
+
connect(widget.convertToPath, SIGNAL(released()), tool->action("convert-to-path"), SLOT(trigger()));
}
PathToolOptionWidget::~PathToolOptionWidget()
{
}
void PathToolOptionWidget::setSelectionType(int type)
{
const bool plain = type & PlainPath;
if (plain)
widget.stackedWidget->setCurrentIndex(0);
else
widget.stackedWidget->setCurrentIndex(1);
}
+
+void PathToolOptionWidget::setCurrentShape(KoPathShape *pathShape)
+{
+ if (pathShape == m_currentShape) return;
+
+ if (m_currentShape) {
+ m_currentShape = 0;
+ if (m_currentPanel) {
+ m_currentPanel->deleteLater();
+ m_currentPanel = 0;
+ }
+ }
+
+ if (pathShape) {
+ m_currentShape = pathShape;
+ QString shapeId = m_currentShape->pathShapeId();
+
+ // check if we have an edited parametric shape, then we use the path shape id
+ KoParameterShape *paramShape = dynamic_cast<KoParameterShape *>(m_currentShape);
+ if (paramShape && !paramShape->isParametricShape()) {
+ shapeId = paramShape->shapeId();
+ }
+
+ KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shapeId);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(factory);
+
+ QList<KoShapeConfigWidgetBase*> panels = factory->createShapeOptionPanels();
+ if (!panels.isEmpty()) {
+ KoShapeConfigWidgetBase *activePanel = 0;
+
+ Q_FOREACH (KoShapeConfigWidgetBase *panel, panels) {
+ if (!activePanel && panel->showOnShapeSelect()) {
+ activePanel = panel;
+ } else {
+ delete panel;
+ }
+ }
+
+ if (activePanel) {
+ KIS_ASSERT_RECOVER_RETURN(m_canvas);
+ m_currentPanel = activePanel;
+ m_currentPanel->setUnit(m_canvas->unit());
+
+ QLayout *baseLayout = widget.wdgShapeProperties->layout();
+ QVBoxLayout *layout = dynamic_cast<QVBoxLayout*>(widget.wdgShapeProperties->layout());
+
+ if (!layout) {
+ KIS_SAFE_ASSERT_RECOVER(!baseLayout) {
+ delete baseLayout;
+ }
+ widget.wdgShapeProperties->setLayout(new QVBoxLayout());
+ }
+
+
+ KIS_ASSERT_RECOVER_RETURN(widget.wdgShapeProperties->layout());
+ widget.wdgShapeProperties->layout()->addWidget(m_currentPanel);
+ connect(m_currentPanel, SIGNAL(propertyChanged()), SLOT(slotShapePropertyChanged()));
+ m_currentPanel->open(m_currentShape);
+ }
+ }
+ }
+
+ widget.wdgShapeProperties->setVisible(m_currentPanel);
+ widget.lineShapeProperties->setVisible(m_currentPanel);
+}
+
+void PathToolOptionWidget::slotShapePropertyChanged()
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_currentPanel);
+
+ KUndo2Command *command = m_currentPanel->createCommand();
+ if (command) {
+ m_canvas->addCommand(command);
+ }
+}
+
+void PathToolOptionWidget::showEvent(QShowEvent *event)
+{
+ emit sigRequestUpdateActions();
+ QWidget::showEvent(event);
+}
diff --git a/libs/flake/tools/PathToolOptionWidget.h b/libs/flake/tools/PathToolOptionWidget.h
index 08e65ab132..495089e9db 100644
--- a/libs/flake/tools/PathToolOptionWidget.h
+++ b/libs/flake/tools/PathToolOptionWidget.h
@@ -1,51 +1,69 @@
/* This file is part of the KDE project
* 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 PATHTOOLOPTIONWIDGET_H
#define PATHTOOLOPTIONWIDGET_H
#include <QWidget>
#include <QFlags>
#include <ui_PathToolOptionWidgetBase.h>
class KoPathTool;
+class KoPathShape;
+class KoShapeConfigWidgetBase;
+class KoCanvasBase;
+
class PathToolOptionWidget : public QWidget
{
Q_OBJECT
public:
enum Type {
PlainPath = 1,
ParametricShape = 2
};
Q_DECLARE_FLAGS(Types, Type)
explicit PathToolOptionWidget(KoPathTool *tool, QWidget *parent = 0);
~PathToolOptionWidget();
public Q_SLOTS:
void setSelectionType(int type);
+ void setCurrentShape(KoPathShape *pathShape);
+
+private Q_SLOTS:
+ void slotShapePropertyChanged();
+
+Q_SIGNALS:
+ void sigRequestUpdateActions();
+
+protected:
+ void showEvent(QShowEvent *event);
private:
Ui::PathToolOptionWidgetBase widget;
+
+ KoPathShape *m_currentShape;
+ KoShapeConfigWidgetBase *m_currentPanel;
+ KoCanvasBase *m_canvas;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(PathToolOptionWidget::Types)
#endif
diff --git a/libs/flake/tools/PathToolOptionWidgetBase.ui b/libs/flake/tools/PathToolOptionWidgetBase.ui
index 214350d503..d7ec32cfc9 100644
--- a/libs/flake/tools/PathToolOptionWidgetBase.ui
+++ b/libs/flake/tools/PathToolOptionWidgetBase.ui
@@ -1,221 +1,271 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PathToolOptionWidgetBase</class>
<widget class="QWidget" name="PathToolOptionWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>280</width>
- <height>60</height>
+ <width>321</width>
+ <height>208</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="leftMargin">
<number>0</number>
</property>
- <property name="margin">
+ <property name="topMargin">
<number>0</number>
</property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QWidget" name="wdgShapeProperties" native="true"/>
+ </item>
+ <item>
+ <widget class="Line" name="lineShapeProperties">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="pathPage">
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,1">
- <property name="margin">
+ <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>
+ <property name="spacing">
+ <number>3</number>
+ </property>
<item row="0" column="0">
<widget class="QToolButton" name="corner">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="smooth">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="symmetric">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="5">
<widget class="QToolButton" name="lineSegment">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QToolButton" name="curveSegment">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QToolButton" name="linePoint">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QToolButton" name="curvePoint">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QToolButton" name="addPoint">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="removePoint">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QToolButton" name="breakPoint">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QToolButton" name="breakSegment">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="7">
<widget class="QToolButton" name="joinSegment">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="8">
<widget class="QToolButton" name="mergePoints">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item row="2" column="5">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="parameterPage">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
- <layout class="QGridLayout">
- <property name="margin">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <property name="leftMargin">
<number>0</number>
</property>
- <item row="1" column="0">
- <widget class="QPushButton" name="convertToPath">
- <property name="text">
- <string>Convert To Path</string>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
</property>
- </widget>
+ <item>
+ <widget class="QPushButton" name="convertToPath">
+ <property name="text">
+ <string>Convert To Path</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="spacer">
+ <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="1" column="1">
- <spacer>
+ <item>
+ <spacer name="verticalSpacer">
<property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
- <height>20</height>
+ <width>20</width>
+ <height>150</height>
</size>
</property>
</spacer>
</item>
- <item row="2" column="0">
- <widget class="QWidget" name="SpecialSpacer">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
diff --git a/libs/global/CMakeLists.txt b/libs/global/CMakeLists.txt
index 9092bfc23c..9b20290f3b 100644
--- a/libs/global/CMakeLists.txt
+++ b/libs/global/CMakeLists.txt
@@ -1,36 +1,43 @@
include(CheckFunctionExists)
check_function_exists(backtrace HAVE_BACKTRACE)
configure_file(config-debug.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-debug.h)
option(HAVE_MEMORY_LEAK_TRACKER "Enable memory leak tracker (always disabled in release build)" OFF)
option(HAVE_BACKTRACE_SUPPORT "Enable recording of backtrace in memory leak tracker" OFF)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-memory-leak-tracker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-memory-leak-tracker.h) ### WRONG PLACE???
set(kritaglobal_LIB_SRCS
kis_assert.cpp
kis_debug.cpp
+ kis_algebra_2d.cpp
kis_memory_leak_tracker.cpp
kis_shared.cpp
kis_dom_utils.cpp
kis_painting_tweaks.cpp
+ KisHandlePainterHelper.cpp
+ KisHandleStyle.cpp
+ kis_signal_compressor.cpp
+ kis_signal_compressor_with_param.cpp
+ kis_acyclic_signal_connector.cpp
+ KisQPainterStateSaver.cpp
)
add_library(kritaglobal SHARED ${kritaglobal_LIB_SRCS} )
generate_export_header(kritaglobal BASE_NAME kritaglobal)
target_link_libraries(kritaglobal
PUBLIC
Qt5::Concurrent
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::Xml
KF5::I18n
)
set_target_properties(kritaglobal PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaglobal ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/global/KisHandlePainterHelper.cpp b/libs/global/KisHandlePainterHelper.cpp
new file mode 100644
index 0000000000..6d830e4b62
--- /dev/null
+++ b/libs/global/KisHandlePainterHelper.cpp
@@ -0,0 +1,280 @@
+/*
+ * 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 "KisHandlePainterHelper.h"
+
+#include <QPainter>
+#include "kis_algebra_2d.h"
+#include "kis_painting_tweaks.h"
+
+using KisPaintingTweaks::PenBrushSaver;
+
+KisHandlePainterHelper::KisHandlePainterHelper(QPainter *_painter, qreal handleRadius)
+ : m_painter(_painter),
+ m_originalPainterTransform(m_painter->transform()),
+ m_painterTransform(m_painter->transform()),
+ m_handleRadius(handleRadius),
+ m_decomposedMatrix(m_painterTransform)
+{
+ init();
+}
+
+KisHandlePainterHelper::KisHandlePainterHelper(QPainter *_painter, const QTransform &originalPainterTransform, qreal handleRadius)
+ : m_painter(_painter),
+ m_originalPainterTransform(originalPainterTransform),
+ m_painterTransform(m_painter->transform()),
+ m_handleRadius(handleRadius),
+ m_decomposedMatrix(m_painterTransform)
+{
+ init();
+}
+
+KisHandlePainterHelper::KisHandlePainterHelper(KisHandlePainterHelper &&rhs)
+ : m_painter(rhs.m_painter),
+ m_originalPainterTransform(rhs.m_originalPainterTransform),
+ m_painterTransform(rhs.m_painterTransform),
+ m_handleRadius(rhs.m_handleRadius),
+ m_decomposedMatrix(rhs.m_decomposedMatrix),
+ m_handleTransform(rhs.m_handleTransform),
+ m_handlePolygon(rhs.m_handlePolygon),
+ m_handleStyle(rhs.m_handleStyle)
+{
+ // disable the source helper
+ rhs.m_painter = 0;
+}
+
+void KisHandlePainterHelper::init()
+{
+ m_handleStyle = KisHandleStyle::inheritStyle();
+
+ m_painter->setTransform(QTransform());
+ m_handleTransform = m_decomposedMatrix.shearTransform() * m_decomposedMatrix.rotateTransform();
+
+ if (m_handleRadius > 0.0) {
+ const QRectF handleRect(-m_handleRadius, -m_handleRadius, 2 * m_handleRadius, 2 * m_handleRadius);
+ m_handlePolygon = m_handleTransform.map(QPolygonF(handleRect));
+ }
+}
+
+KisHandlePainterHelper::~KisHandlePainterHelper() {
+ if (m_painter) {
+ m_painter->setTransform(m_originalPainterTransform);
+ }
+}
+
+void KisHandlePainterHelper::setHandleStyle(const KisHandleStyle &style)
+{
+ m_handleStyle = style;
+}
+
+void KisHandlePainterHelper::drawHandleRect(const QPointF &center, qreal radius) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius);
+ QPolygonF handlePolygon = m_handleTransform.map(QPolygonF(handleRect));
+ handlePolygon.translate(m_painterTransform.map(center));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPolygon(handlePolygon);
+ }
+}
+
+void KisHandlePainterHelper::drawHandleCircle(const QPointF &center, qreal radius) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QRectF handleRect(-radius, -radius, 2 * radius, 2 * radius);
+ handleRect.translate(m_painterTransform.map(center));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawEllipse(handleRect);
+ }
+}
+
+void KisHandlePainterHelper::drawHandleCircle(const QPointF &center)
+{
+ drawHandleCircle(center, m_handleRadius);
+}
+
+void KisHandlePainterHelper::drawHandleSmallCircle(const QPointF &center)
+{
+ drawHandleCircle(center, 0.7 * m_handleRadius);
+}
+
+void KisHandlePainterHelper::drawHandleRect(const QPointF &center) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+ QPolygonF paintingPolygon = m_handlePolygon.translated(m_painterTransform.map(center));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPolygon(paintingPolygon);
+ }
+}
+
+void KisHandlePainterHelper::drawGradientHandle(const QPointF &center, qreal radius) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QPolygonF handlePolygon;
+
+ handlePolygon << QPointF(-radius, 0);
+ handlePolygon << QPointF(0, radius);
+ handlePolygon << QPointF(radius, 0);
+ handlePolygon << QPointF(0, -radius);
+
+ handlePolygon = m_handleTransform.map(handlePolygon);
+ handlePolygon.translate(m_painterTransform.map(center));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPolygon(handlePolygon);
+ }
+}
+
+void KisHandlePainterHelper::drawGradientHandle(const QPointF &center)
+{
+ drawGradientHandle(center, 1.41 * m_handleRadius);
+}
+
+void KisHandlePainterHelper::drawGradientCrossHandle(const QPointF &center, qreal radius) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ { // Draw a cross
+ QPainterPath p;
+ p.moveTo(-radius, -radius);
+ p.lineTo(radius, radius);
+ p.moveTo(radius, -radius);
+ p.lineTo(-radius, radius);
+
+ p = m_handleTransform.map(p);
+ p.translate(m_painterTransform.map(center));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPath(p);
+ }
+ }
+
+ { // Draw a square
+ const qreal halfRadius = 0.5 * radius;
+
+ QPolygonF handlePolygon;
+ handlePolygon << QPointF(-halfRadius, 0);
+ handlePolygon << QPointF(0, halfRadius);
+ handlePolygon << QPointF(halfRadius, 0);
+ handlePolygon << QPointF(0, -halfRadius);
+
+ handlePolygon = m_handleTransform.map(handlePolygon);
+ handlePolygon.translate(m_painterTransform.map(center));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPolygon(handlePolygon);
+ }
+ }
+}
+
+void KisHandlePainterHelper::drawArrow(const QPointF &pos, const QPointF &from, qreal radius)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QPainterPath p;
+
+ QLineF line(pos, from);
+ line.setLength(radius);
+
+ QPointF norm = KisAlgebra2D::leftUnitNormal(pos - from);
+ norm *= 0.34 * radius;
+
+ p.moveTo(line.p2() + norm);
+ p.lineTo(line.p1());
+ p.lineTo(line.p2() - norm);
+
+ p.translate(-pos);
+
+ p = m_handleTransform.map(p).translated(m_painterTransform.map(pos));
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.handleIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPath(p);
+ }
+}
+
+void KisHandlePainterHelper::drawGradientArrow(const QPointF &start, const QPointF &end, qreal radius)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QPainterPath p;
+ p.moveTo(start);
+ p.lineTo(end);
+ p = m_painterTransform.map(p);
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPath(p);
+ }
+
+ const qreal length = kisDistance(start, end);
+ const QPointF diff = end - start;
+
+ if (length > 5 * radius) {
+ drawArrow(start + 0.33 * diff, start, radius);
+ drawArrow(start + 0.66 * diff, start, radius);
+ } else if (length > 3 * radius) {
+ drawArrow(start + 0.5 * diff, start, radius);
+ }
+}
+
+void KisHandlePainterHelper::drawRubberLine(const QPolygonF &poly) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QPolygonF paintingPolygon = m_painterTransform.map(poly);
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPolygon(paintingPolygon);
+ }
+}
+
+void KisHandlePainterHelper::drawConnectionLine(const QLineF &line)
+{
+ drawConnectionLine(line.p1(), line.p2());
+}
+
+void KisHandlePainterHelper::drawConnectionLine(const QPointF &p1, const QPointF &p2)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_painter);
+
+ QPointF realP1 = m_painterTransform.map(p1);
+ QPointF realP2 = m_painterTransform.map(p2);
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawLine(realP1, realP2);
+ }
+}
+
+void KisHandlePainterHelper::drawPath(const QPainterPath &path)
+{
+ const QPainterPath realPath = m_painterTransform.map(path);
+
+ Q_FOREACH (KisHandleStyle::IterationStyle it, m_handleStyle.lineIterations) {
+ PenBrushSaver saver(it.isValid ? m_painter : 0, it.stylePair, PenBrushSaver::allow_noop);
+ m_painter->drawPath(realPath);
+ }
+}
diff --git a/libs/global/KisHandlePainterHelper.h b/libs/global/KisHandlePainterHelper.h
new file mode 100644
index 0000000000..8cb8b2c82d
--- /dev/null
+++ b/libs/global/KisHandlePainterHelper.h
@@ -0,0 +1,169 @@
+/*
+ * 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 KISHANDLEPAINTERHELPER_H
+#define KISHANDLEPAINTERHELPER_H
+
+#include "kritaglobal_export.h"
+#include "kis_algebra_2d.h"
+
+#include <QPainter>
+#include <KisHandleStyle.h>
+class QPainter;
+class KoShape;
+class KoViewConverter;
+
+/**
+ * @brief The KisHandlePainterHelper class is a special helper for
+ * painting handles around objects. It ensures the handlesare painted
+ * with the same size and line width whatever transformation is setup
+ * in the painter. The handles will also be rotated/skewed if the object
+ * itself has these transformations.
+ *
+ * On construction it resets QPainter transformation and on destruction
+ * recovers it back.
+ *
+ * Please consider using KoShape::createHandlePainterHelper instead of direct
+ * construction of the helper. This factory method will also apply the
+ * transformations needed for a shape.
+ */
+
+class KRITAGLOBAL_EXPORT KisHandlePainterHelper
+{
+public:
+
+ /**
+ * Creates the helper, initializes all the internal transformations and
+ * *resets* the transformation of the painter.
+ */
+ KisHandlePainterHelper(QPainter *_painter, qreal handleRadius = 0.0);
+
+ /**
+ * Creates the helper, initializes all the internal transformations and
+ * *resets* the transformation of the painter. This override also adjusts the
+ * transformation of the painter into the coordinate system of the shape
+ */
+ KisHandlePainterHelper(QPainter *_painter, const QTransform &originalPainterTransform, qreal handleRadius);
+
+ /**
+ * Move c-tor. Used to create and return the helper from functions by-value.
+ */
+ KisHandlePainterHelper(KisHandlePainterHelper &&rhs);
+ KisHandlePainterHelper(KisHandlePainterHelper &rhs) = delete;
+
+ /**
+ * Restores the transformation of the painter
+ */
+ ~KisHandlePainterHelper();
+
+ /**
+ * Sets style used for painting the handles. Please use static methods of
+ * KisHandleStyle to select predefined styles.
+ */
+ void setHandleStyle(const KisHandleStyle &style);
+
+ /**
+ * Draws a handle rect with a custom \p radius at position \p center
+ */
+ void drawHandleRect(const QPointF &center, qreal radius);
+
+ /**
+ * Draws a handle circle with a custom \p radius at position \p center
+ */
+ void drawHandleCircle(const QPointF &center, qreal radius);
+
+ /**
+ * Optimized version of the drawing method for drawing handles of
+ * predefined size
+ */
+ void drawHandleRect(const QPointF &center);
+
+ /**
+ * Optimized version of the drawing method for drawing handles of
+ * predefined size
+ */
+ void drawHandleCircle(const QPointF &center);
+
+ /**
+ * Optimized version of the drawing method for drawing handles of
+ * predefined size
+ */
+ void drawHandleSmallCircle(const QPointF &center);
+
+ /**
+ * Draw a rotated handle representing the gradient handle
+ */
+ void drawGradientHandle(const QPointF &center, qreal radius);
+
+ /**
+ * Draw a rotated handle representing the gradient handle
+ */
+ void drawGradientHandle(const QPointF &center);
+
+ /**
+ * Draw a special handle representing the center of the gradient
+ */
+ void drawGradientCrossHandle(const QPointF &center, qreal radius);
+
+ /**
+ * Draw an arrow representing gradient position
+ */
+ void drawGradientArrow(const QPointF &start, const QPointF &end, qreal radius);
+
+ /**
+ * Draw a line showing the bounding box of the selection
+ */
+ void drawRubberLine(const QPolygonF &poly);
+
+ /**
+ * Draw a line connecting two points
+ */
+ void drawConnectionLine(const QLineF &line);
+
+ /**
+ * Draw a line connecting two points
+ */
+ void drawConnectionLine(const QPointF &p1, const QPointF &p2);
+
+ /**
+ * Draw an arbitrary path
+ */
+ void drawPath(const QPainterPath &path);
+
+private:
+
+ /**
+ * Draw a single arrow with the tip at position \p pos, directed from \p from,
+ * of size \p radius.
+ */
+ void drawArrow(const QPointF &pos, const QPointF &from, qreal radius);
+
+ void init();
+
+private:
+ QPainter *m_painter;
+ QTransform m_originalPainterTransform;
+ QTransform m_painterTransform;
+ qreal m_handleRadius;
+ KisAlgebra2D::DecomposedMatix m_decomposedMatrix;
+ QTransform m_handleTransform;
+ QPolygonF m_handlePolygon;
+ KisHandleStyle m_handleStyle;
+};
+
+#endif // KISHANDLEPAINTERHELPER_H
diff --git a/libs/global/KisHandleStyle.cpp b/libs/global/KisHandleStyle.cpp
new file mode 100644
index 0000000000..5821481a8d
--- /dev/null
+++ b/libs/global/KisHandleStyle.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "KisHandleStyle.h"
+#include "kis_painting_tweaks.h"
+
+namespace {
+void initDashedStyle(const QColor &baseColor, const QColor &handleFill, KisHandleStyle *style) {
+ QPen ants;
+ QPen outline;
+ KisPaintingTweaks::initAntsPen(&ants, &outline);
+
+ ants.setColor(baseColor);
+
+ style->lineIterations << KisHandleStyle::IterationStyle(outline, Qt::NoBrush);
+ style->lineIterations << KisHandleStyle::IterationStyle(ants, Qt::NoBrush);
+
+ QPen handlePen(baseColor);
+ handlePen.setWidth(2);
+ handlePen.setJoinStyle(Qt::RoundJoin);
+
+ style->handleIterations << KisHandleStyle::IterationStyle(handlePen, handleFill);
+}
+
+static const QColor primaryColor(0, 0, 90, 180);
+static const QColor secondaryColor(0, 0, 255, 127);
+static const QColor gradientFillColor(255, 197, 39);
+static const QColor highlightColor(255, 100, 100);
+static const QColor selectionColor(66, 255, 0);
+
+}
+
+
+KisHandleStyle &KisHandleStyle::inheritStyle()
+{
+ static QScopedPointer<KisHandleStyle> style;
+
+ if (!style) {
+ style.reset(new KisHandleStyle());
+ style->lineIterations << KisHandleStyle::IterationStyle();
+ style->handleIterations << KisHandleStyle::IterationStyle();
+ }
+
+ return *style;
+}
+
+KisHandleStyle &KisHandleStyle::primarySelection()
+{
+ static QScopedPointer<KisHandleStyle> style;
+
+ if (!style) {
+ style.reset(new KisHandleStyle());
+ initDashedStyle(primaryColor, Qt::white, style.data());
+ }
+
+ return *style;
+}
+
+KisHandleStyle &KisHandleStyle::secondarySelection()
+{
+ static QScopedPointer<KisHandleStyle> style;
+
+ if (!style) {
+ style.reset(new KisHandleStyle());
+ initDashedStyle(secondaryColor, Qt::white, style.data());
+ }
+
+ return *style;
+}
+
+KisHandleStyle &KisHandleStyle::gradientHandles()
+{
+ static QScopedPointer<KisHandleStyle> style;
+
+ if (!style) {
+ style.reset(new KisHandleStyle());
+ initDashedStyle(primaryColor, gradientFillColor, style.data());
+ }
+
+ return *style;
+}
+
+KisHandleStyle &KisHandleStyle::gradientArrows()
+{
+ return primarySelection();
+}
+
+
+KisHandleStyle &KisHandleStyle::highlightedPrimaryHandles()
+{
+ static QScopedPointer<KisHandleStyle> style;
+
+ if (!style) {
+ style.reset(new KisHandleStyle());
+ initDashedStyle(primaryColor, highlightColor, style.data());
+ }
+
+ return *style;
+}
+
+KisHandleStyle &KisHandleStyle::selectedPrimaryHandles()
+{
+ static QScopedPointer<KisHandleStyle> style;
+
+ if (!style) {
+ style.reset(new KisHandleStyle());
+ initDashedStyle(primaryColor, selectionColor, style.data());
+ }
+
+ return *style;
+}
+
diff --git a/libs/global/KisHandleStyle.h b/libs/global/KisHandleStyle.h
new file mode 100644
index 0000000000..4e7bf8610e
--- /dev/null
+++ b/libs/global/KisHandleStyle.h
@@ -0,0 +1,93 @@
+/*
+ * 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 KISHANDLESTYLE_H
+#define KISHANDLESTYLE_H
+
+#include <QVector>
+#include <QPen>
+#include <QBrush>
+
+#include "kritaglobal_export.h"
+
+
+/**
+ * A special class that defines a set of predefined styles for painting handles.
+ * Please use static methods for requesting standard krita styles.
+ */
+class KRITAGLOBAL_EXPORT KisHandleStyle
+{
+public:
+
+ /**
+ * Default style that does *no* change to the painter. That is, the painter
+ * will paint with its current pen and brush.
+ */
+ static KisHandleStyle& inheritStyle();
+
+ /**
+ * Main style. Used for showing a single selection or a boundary box of
+ * multiple selected objects.
+ */
+ static KisHandleStyle& primarySelection();
+
+ /**
+ * Secondary style. Used for highlighting objects inside a mupltiple
+ * selection.
+ */
+ static KisHandleStyle& secondarySelection();
+
+ /**
+ * Style for painting gradient handles
+ */
+ static KisHandleStyle& gradientHandles();
+
+ /**
+ * Style for painting linear gradient arrows
+ */
+ static KisHandleStyle& gradientArrows();
+
+ /**
+ * Same as primary style, but the handles are filled with red color to show
+ * that the user is hovering them.
+ */
+ static KisHandleStyle& highlightedPrimaryHandles();
+
+ /**
+ * Same as primary style, but the handles are filled with green color to show
+ * that they are selected.
+ */
+ static KisHandleStyle& selectedPrimaryHandles();
+
+ struct IterationStyle {
+ IterationStyle() : isValid(false) {}
+ IterationStyle(const QPen &pen, const QBrush &brush)
+ : isValid(true),
+ stylePair(pen, brush)
+ {
+ }
+
+ bool isValid;
+ QPair<QPen, QBrush> stylePair;
+ };
+
+ QVector<IterationStyle> handleIterations;
+ QVector<IterationStyle> lineIterations;
+};
+
+#endif // KISHANDLESTYLE_H
diff --git a/libs/global/KisQPainterStateSaver.cpp b/libs/global/KisQPainterStateSaver.cpp
new file mode 100644
index 0000000000..b255a3fd7e
--- /dev/null
+++ b/libs/global/KisQPainterStateSaver.cpp
@@ -0,0 +1,33 @@
+/*
+ * 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 "KisQPainterStateSaver.h"
+
+#include <QPainter>
+
+KisQPainterStateSaver::KisQPainterStateSaver(QPainter *painter)
+ : m_painter(painter)
+{
+ m_painter->save();
+}
+
+KisQPainterStateSaver::~KisQPainterStateSaver()
+{
+ m_painter->restore();
+}
+
diff --git a/libs/global/KisQPainterStateSaver.h b/libs/global/KisQPainterStateSaver.h
new file mode 100644
index 0000000000..4cc15b29a0
--- /dev/null
+++ b/libs/global/KisQPainterStateSaver.h
@@ -0,0 +1,37 @@
+/*
+ * 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 KISQPAINTERSTATESAVER_H
+#define KISQPAINTERSTATESAVER_H
+
+#include "kritaglobal_export.h"
+
+class QPainter;
+
+class KRITAGLOBAL_EXPORT KisQPainterStateSaver
+{
+public:
+ KisQPainterStateSaver(QPainter *painter);
+ ~KisQPainterStateSaver();
+
+private:
+ KisQPainterStateSaver(const KisQPainterStateSaver &rhs);
+ QPainter *m_painter;
+};
+
+#endif // KISQPAINTERSTATESAVER_H
diff --git a/libs/global/kis_acyclic_signal_connector.cpp b/libs/global/kis_acyclic_signal_connector.cpp
new file mode 100644
index 0000000000..38c605faba
--- /dev/null
+++ b/libs/global/kis_acyclic_signal_connector.cpp
@@ -0,0 +1,237 @@
+/*
+ * 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_acyclic_signal_connector.h"
+
+#include "kis_debug.h"
+
+
+KisAcyclicSignalConnector::KisAcyclicSignalConnector(QObject *parent)
+ : QObject(parent),
+ m_signalsBlocked(0)
+{
+}
+
+void KisAcyclicSignalConnector::connectForwardDouble(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(forwardSlotDouble(double)));
+ connect(this, SIGNAL(forwardSignalDouble(double)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectBackwardDouble(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(backwardSlotDouble(double)));
+ connect(this, SIGNAL(backwardSignalDouble(double)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectForwardInt(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(forwardSlotInt(int)));
+ connect(this, SIGNAL(forwardSignalInt(int)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectBackwardInt(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(backwardSlotInt(int)));
+ connect(this, SIGNAL(backwardSignalInt(int)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectForwardBool(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(forwardSlotBool(bool)));
+ connect(this, SIGNAL(forwardSignalBool(bool)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectBackwardBool(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(backwardSlotBool(bool)));
+ connect(this, SIGNAL(backwardSignalBool(bool)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectForwardVoid(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(forwardSlotVoid()));
+ connect(this, SIGNAL(forwardSignalVoid()), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectBackwardVoid(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(backwardSlotVoid()));
+ connect(this, SIGNAL(backwardSignalVoid()), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectForwardVariant(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+
+ connect(sender, signal, this, SLOT(forwardSlotVariant(const QVariant&)));
+ connect(this, SIGNAL(forwardSignalVariant(const QVariant&)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectBackwardVariant(QObject *sender, const char *signal,
+ QObject *receiver, const char *method)
+{
+ connect(sender, signal, this, SLOT(backwardSlotVariant(const QVariant&)));
+ connect(this, SIGNAL(backwardSignalVariant(const QVariant&)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectForwardResourcePair(QObject *sender, const char *signal, QObject *receiver, const char *method)
+{
+ connect(sender, signal, this, SLOT(forwardSlotResourcePair(int,QVariant)));
+ connect(this, SIGNAL(forwardSignalResourcePair(int,QVariant)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::connectBackwardResourcePair(QObject *sender, const char *signal, QObject *receiver, const char *method)
+{
+ connect(sender, signal, this, SLOT(backwardSlotResourcePair(int,QVariant)));
+ connect(this, SIGNAL(backwardSignalResourcePair(int,QVariant)), receiver, method);
+}
+
+void KisAcyclicSignalConnector::lock()
+{
+ m_signalsBlocked++;
+}
+
+void KisAcyclicSignalConnector::unlock()
+{
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::forwardSlotDouble(double value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit forwardSignalDouble(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::backwardSlotDouble(double value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit backwardSignalDouble(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::forwardSlotInt(int value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit forwardSignalInt(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::backwardSlotInt(int value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit backwardSignalInt(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::forwardSlotBool(bool value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit forwardSignalBool(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::backwardSlotBool(bool value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit backwardSignalBool(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::forwardSlotVoid()
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit forwardSignalVoid();
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::backwardSlotVoid()
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit backwardSignalVoid();
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::forwardSlotVariant(const QVariant &value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit forwardSignalVariant(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::backwardSlotVariant(const QVariant &value)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit backwardSignalVariant(value);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::forwardSlotResourcePair(int key, const QVariant &resource)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit forwardSignalResourcePair(key, resource);
+ m_signalsBlocked--;
+}
+
+void KisAcyclicSignalConnector::backwardSlotResourcePair(int key, const QVariant &resource)
+{
+ if (m_signalsBlocked) return;
+
+ m_signalsBlocked++;
+ emit backwardSignalResourcePair(key, resource);
+ m_signalsBlocked--;
+}
diff --git a/libs/global/kis_acyclic_signal_connector.h b/libs/global/kis_acyclic_signal_connector.h
new file mode 100644
index 0000000000..ef9492bb9f
--- /dev/null
+++ b/libs/global/kis_acyclic_signal_connector.h
@@ -0,0 +1,138 @@
+/*
+ * 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_ACYCLIC_SIGNAL_CONNECTOR_H
+#define __KIS_ACYCLIC_SIGNAL_CONNECTOR_H
+
+#include <QObject>
+#include "kritaglobal_export.h"
+#include <mutex>
+
+
+/**
+ * A special class for connecting UI elements to manager classes.
+ * It allows to avoid direct calling blockSignals() for the sender UI
+ * element all the time. This is the most important when the measured
+ * value can be changed not only by the user through the UI, but also
+ * by the manager according to some internal rules.
+ *
+ * Example:
+ *
+ * Suppose we have the following connections:
+ *
+ * 1) QDoubleSpinBox::valueChanged(double) -> Manager::slotSetValue(double)
+ * 2) Manager::valueChanged(double) -> QDoubleSpinBox::setValue(double)
+ *
+ * Now if the manager decides to change/correct the value, the spinbox
+ * will go into an infinite loop.
+ *
+ * See an example in KisToolCropConfigWidget.
+ */
+
+class KRITAGLOBAL_EXPORT KisAcyclicSignalConnector : public QObject
+{
+ Q_OBJECT
+public:
+ typedef std::unique_lock<KisAcyclicSignalConnector> Blocker;
+
+public:
+
+ KisAcyclicSignalConnector(QObject *parent = 0);
+
+ void connectForwardDouble(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectBackwardDouble(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectForwardInt(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectBackwardInt(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectForwardBool(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectBackwardBool(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectForwardVoid(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectBackwardVoid(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectForwardVariant(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectBackwardVariant(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectForwardResourcePair(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void connectBackwardResourcePair(QObject *sender, const char *signal,
+ QObject *receiver, const char *method);
+
+ void lock();
+ void unlock();
+
+private Q_SLOTS:
+ void forwardSlotDouble(double value);
+ void backwardSlotDouble(double value);
+
+ void forwardSlotInt(int value);
+ void backwardSlotInt(int value);
+
+ void forwardSlotBool(bool value);
+ void backwardSlotBool(bool value);
+
+ void forwardSlotVoid();
+ void backwardSlotVoid();
+
+ void forwardSlotVariant(const QVariant &value);
+ void backwardSlotVariant(const QVariant &value);
+
+ void forwardSlotResourcePair(int key, const QVariant &resource);
+ void backwardSlotResourcePair(int key, const QVariant &resource);
+
+Q_SIGNALS:
+ void forwardSignalDouble(double value);
+ void backwardSignalDouble(double value);
+
+ void forwardSignalInt(int value);
+ void backwardSignalInt(int value);
+
+ void forwardSignalBool(bool value);
+ void backwardSignalBool(bool value);
+
+ void forwardSignalVoid();
+ void backwardSignalVoid();
+
+ void forwardSignalVariant(const QVariant &value);
+ void backwardSignalVariant(const QVariant &value);
+
+ void forwardSignalResourcePair(int key, const QVariant &value);
+ void backwardSignalResourcePair(int key, const QVariant &value);
+
+private:
+ int m_signalsBlocked;
+};
+
+#endif /* __KIS_ACYCLIC_SIGNAL_CONNECTOR_H */
diff --git a/libs/global/kis_algebra_2d.cpp b/libs/global/kis_algebra_2d.cpp
new file mode 100644
index 0000000000..1ba61509d0
--- /dev/null
+++ b/libs/global/kis_algebra_2d.cpp
@@ -0,0 +1,570 @@
+/*
+ * 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_algebra_2d.h"
+
+#include <QTransform>
+#include <QPainterPath>
+#include <kis_debug.h>
+
+#include <boost/accumulators/accumulators.hpp>
+#include <boost/accumulators/statistics/stats.hpp>
+#include <boost/accumulators/statistics/min.hpp>
+#include <boost/accumulators/statistics/max.hpp>
+
+#include <array>
+#include <QVector3D>
+
+#define SANITY_CHECKS
+
+namespace KisAlgebra2D {
+
+void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
+{
+ const int numPoints = poly.size();
+ for (int i = 0; i < numPoints; i++) {
+ int nextI = i + 1;
+ if (nextI >= numPoints) {
+ nextI = 0;
+ }
+
+ const QPointF &p0 = poly[i];
+ const QPointF &p1 = poly[nextI];
+
+ QPointF edge = p1 - p0;
+
+ qreal cross = crossProduct(edge, *pt - p0)
+ / (0.5 * edge.manhattanLength());
+
+ if (cross < 1.0 &&
+ isInRange(pt->x(), p0.x(), p1.x()) &&
+ isInRange(pt->y(), p0.y(), p1.y())) {
+
+ QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection);
+
+ QPointF adjustedPoint = *pt + salt;
+
+ // in case the polygon is self-intersecting, polygon direction
+ // might not help
+ if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
+ adjustedPoint = *pt - salt;
+
+#ifdef SANITY_CHECKS
+ if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
+ dbgKrita << ppVar(*pt);
+ dbgKrita << ppVar(adjustedPoint);
+ dbgKrita << ppVar(QLineF(p0, p1));
+ dbgKrita << ppVar(salt);
+
+ dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill));
+
+ dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1)));
+ dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1)));
+ }
+
+ *pt = adjustedPoint;
+
+ KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4);
+#endif /* SANITY_CHECKS */
+ }
+ }
+ }
+}
+
+QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) {
+ qreal len1 = norm(base1);
+ if (len1 < 1e-5) return pt;
+ qreal sin1 = base1.y() / len1;
+ qreal cos1 = base1.x() / len1;
+
+ qreal len2 = norm(base2);
+ if (len2 < 1e-5) return QPointF();
+ qreal sin2 = base2.y() / len2;
+ qreal cos2 = base2.x() / len2;
+
+ qreal sinD = sin2 * cos1 - cos2 * sin1;
+ qreal cosD = cos1 * cos2 + sin1 * sin2;
+ qreal scaleD = len2 / len1;
+
+ QPointF result;
+ result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD);
+ result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD);
+
+ return result;
+}
+
+qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
+{
+ qreal a1 = std::atan2(v1.y(), v1.x());
+ qreal a2 = std::atan2(v2.y(), v2.x());
+
+ return a2 - a1;
+}
+
+QPainterPath smallArrow()
+{
+ QPainterPath p;
+
+ p.moveTo(5, 2);
+ p.lineTo(-3, 8);
+ p.lineTo(-5, 5);
+ p.lineTo( 2, 0);
+ p.lineTo(-5,-5);
+ p.lineTo(-3,-8);
+ p.lineTo( 5,-2);
+ p.arcTo(QRectF(3, -2, 4, 4), 90, -180);
+
+ return p;
+}
+
+template <class Point, class Rect>
+inline Point ensureInRectImpl(Point pt, const Rect &bounds)
+{
+ if (pt.x() > bounds.right()) {
+ pt.rx() = bounds.right();
+ } else if (pt.x() < bounds.left()) {
+ pt.rx() = bounds.left();
+ }
+
+ if (pt.y() > bounds.bottom()) {
+ pt.ry() = bounds.bottom();
+ } else if (pt.y() < bounds.top()) {
+ pt.ry() = bounds.top();
+ }
+
+ return pt;
+}
+
+QPoint ensureInRect(QPoint pt, const QRect &bounds)
+{
+ return ensureInRectImpl(pt, bounds);
+}
+
+QPointF ensureInRect(QPointF pt, const QRectF &bounds)
+{
+ return ensureInRectImpl(pt, bounds);
+}
+
+bool intersectLineRect(QLineF &line, const QRect rect)
+{
+ QPointF pt1 = QPointF(), pt2 = QPointF();
+ QPointF tmp;
+
+ if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &tmp) != QLineF::NoIntersection) {
+ if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) {
+ pt1 = tmp;
+ }
+ }
+
+ if (line.intersect(QLineF(rect.topRight(), rect.bottomRight()), &tmp) != QLineF::NoIntersection) {
+ if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) {
+ if (pt1.isNull()) pt1 = tmp;
+ else pt2 = tmp;
+ }
+ }
+ if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &tmp) != QLineF::NoIntersection) {
+ if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) {
+ if (pt1.isNull()) pt1 = tmp;
+ else pt2 = tmp;
+ }
+ }
+ if (line.intersect(QLineF(rect.bottomLeft(), rect.topLeft()), &tmp) != QLineF::NoIntersection) {
+ if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) {
+ if (pt1.isNull()) pt1 = tmp;
+ else pt2 = tmp;
+ }
+ }
+
+ if (pt1.isNull() || pt2.isNull()) return false;
+
+ // Attempt to retain polarity of end points
+ if ((line.x1() < line.x2()) != (pt1.x() > pt2.x()) || (line.y1() < line.y2()) != (pt1.y() > pt2.y())) {
+ tmp = pt1;
+ pt1 = pt2;
+ pt2 = tmp;
+ }
+
+ line.setP1(pt1);
+ line.setP2(pt2);
+
+ return true;
+}
+
+ template <class Rect, class Point>
+ QVector<Point> sampleRectWithPoints(const Rect &rect)
+ {
+ QVector<Point> points;
+
+ Point m1 = 0.5 * (rect.topLeft() + rect.topRight());
+ Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight());
+
+ points << rect.topLeft();
+ points << m1;
+ points << rect.topRight();
+
+ points << 0.5 * (rect.topLeft() + rect.bottomLeft());
+ points << 0.5 * (m1 + m2);
+ points << 0.5 * (rect.topRight() + rect.bottomRight());
+
+ points << rect.bottomLeft();
+ points << m2;
+ points << rect.bottomRight();
+
+ return points;
+ }
+
+ QVector<QPoint> sampleRectWithPoints(const QRect &rect)
+ {
+ return sampleRectWithPoints<QRect, QPoint>(rect);
+ }
+
+ QVector<QPointF> sampleRectWithPoints(const QRectF &rect)
+ {
+ return sampleRectWithPoints<QRectF, QPointF>(rect);
+ }
+
+
+ template <class Rect, class Point, bool alignPixels>
+ Rect approximateRectFromPointsImpl(const QVector<Point> &points)
+ {
+ using namespace boost::accumulators;
+ accumulator_set<qreal, stats<tag::min, tag::max > > accX;
+ accumulator_set<qreal, stats<tag::min, tag::max > > accY;
+
+ Q_FOREACH (const Point &pt, points) {
+ accX(pt.x());
+ accY(pt.y());
+ }
+
+ Rect resultRect;
+
+ if (alignPixels) {
+ resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
+ std::ceil(max(accX)), std::ceil(max(accY)));
+ } else {
+ resultRect.setCoords(min(accX), min(accY),
+ max(accX), max(accY));
+ }
+
+ return resultRect;
+ }
+
+ QRect approximateRectFromPoints(const QVector<QPoint> &points)
+ {
+ return approximateRectFromPointsImpl<QRect, QPoint, true>(points);
+ }
+
+ QRectF approximateRectFromPoints(const QVector<QPointF> &points)
+ {
+ return approximateRectFromPointsImpl<QRectF, QPointF, false>(points);
+ }
+
+ QRect approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func)
+ {
+ QVector<QPoint> points = sampleRectWithPoints(rect);
+
+ using namespace boost::accumulators;
+ accumulator_set<qreal, stats<tag::min, tag::max > > accX;
+ accumulator_set<qreal, stats<tag::min, tag::max > > accY;
+
+ Q_FOREACH (const QPoint &pt, points) {
+ QPointF dstPt = func(pt);
+
+ accX(dstPt.x());
+ accY(dstPt.y());
+ }
+
+ QRect resultRect;
+ resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
+ std::ceil(max(accX)), std::ceil(max(accY)));
+
+ return resultRect;
+ }
+
+
+QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p)
+{
+ QVector<QPointF> points;
+
+ const QLineF cutLine = p.getLine();
+
+ points << rc.topLeft();
+ points << rc.topRight();
+ points << rc.bottomRight();
+ points << rc.bottomLeft();
+
+ QPointF p1 = points[3];
+ bool p1Valid = p.pos(p1) >= 0;
+
+ QVector<QPointF> resultPoints;
+
+ for (int i = 0; i < 4; i++) {
+ const QPointF p2 = points[i];
+ const bool p2Valid = p.pos(p2) >= 0;
+
+ if (p1Valid != p2Valid) {
+ QPointF intersection;
+ cutLine.intersect(QLineF(p1, p2), &intersection);
+ resultPoints << intersection;
+ }
+
+ if (p2Valid) {
+ resultPoints << p2;
+ }
+
+ p1 = p2;
+ p1Valid = p2Valid;
+ }
+
+ return approximateRectFromPoints(resultPoints);
+}
+
+int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2)
+{
+ int numSolutions = 0;
+
+ const qreal D = pow2(b) - 4 * a * c;
+
+ if (D < 0) {
+ return 0;
+ } else if (qFuzzyCompare(D, 0)) {
+ *x1 = -b / (2 * a);
+ numSolutions = 1;
+ } else {
+ const qreal sqrt_D = std::sqrt(D);
+
+ *x1 = (-b + sqrt_D) / (2 * a);
+ *x2 = (-b - sqrt_D) / (2 * a);
+ numSolutions = 2;
+ }
+
+ return numSolutions;
+}
+
+QVector<QPointF> intersectTwoCircles(const QPointF &center1, qreal r1,
+ const QPointF &center2, qreal r2)
+{
+ QVector<QPointF> points;
+
+ const QPointF diff = (center2 - center1);
+ const QPointF c1;
+ const QPointF c2 = diff;
+
+ const qreal centerDistance = norm(diff);
+
+ if (centerDistance > r1 + r2) return points;
+ if (centerDistance < qAbs(r1 - r2)) return points;
+
+ if (centerDistance < qAbs(r1 - r2) + 0.001) {
+ qDebug() << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2));
+ return points;
+ }
+
+ const qreal x_kp1 = diff.x();
+ const qreal y_kp1 = diff.y();
+
+ const qreal F2 =
+ 0.5 * (pow2(x_kp1) +
+ pow2(y_kp1) + pow2(r1) - pow2(r2));
+
+ if (qFuzzyCompare(diff.y(), 0)) {
+ qreal x = F2 / diff.x();
+ qreal y1, y2;
+ int result = KisAlgebra2D::quadraticEquation(
+ 1, 0,
+ pow2(x) - pow2(r2),
+ &y1, &y2);
+
+ KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
+
+ if (result == 1) {
+ points << QPointF(x, y1);
+ } else if (result == 2) {
+ KisAlgebra2D::RightHalfPlane p(c1, c2);
+
+ QPointF p1(x, y1);
+ QPointF p2(x, y2);
+
+ if (p.pos(p1) >= 0) {
+ points << p1;
+ points << p2;
+ } else {
+ points << p2;
+ points << p1;
+ }
+ }
+ } else {
+ const qreal A = diff.x() / diff.y();
+ const qreal C = F2 / diff.y();
+
+ qreal x1, x2;
+ int result = KisAlgebra2D::quadraticEquation(
+ 1 + pow2(A), -2 * A * C,
+ pow2(C) - pow2(r1),
+ &x1, &x2);
+
+ KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
+
+ if (result == 1) {
+ points << QPointF(x1, C - x1 * A);
+ } else if (result == 2) {
+ KisAlgebra2D::RightHalfPlane p(c1, c2);
+
+ QPointF p1(x1, C - x1 * A);
+ QPointF p2(x2, C - x2 * A);
+
+ if (p.pos(p1) >= 0) {
+ points << p1;
+ points << p2;
+ } else {
+ points << p2;
+ points << p1;
+ }
+ }
+ }
+
+ for (int i = 0; i < points.size(); i++) {
+ points[i] = center1 + points[i];
+ }
+
+ return points;
+}
+
+QTransform mapToRect(const QRectF &rect)
+{
+ return
+ QTransform(rect.width(), 0, 0, rect.height(),
+ rect.x(), rect.y());
+}
+
+bool fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta) {
+ return
+ qAbs(t1.m11() - t2.m11()) < delta &&
+ qAbs(t1.m12() - t2.m12()) < delta &&
+ qAbs(t1.m13() - t2.m13()) < delta &&
+ qAbs(t1.m21() - t2.m21()) < delta &&
+ qAbs(t1.m22() - t2.m22()) < delta &&
+ qAbs(t1.m23() - t2.m23()) < delta &&
+ qAbs(t1.m31() - t2.m31()) < delta &&
+ qAbs(t1.m32() - t2.m32()) < delta &&
+ qAbs(t1.m33() - t2.m33()) < delta;
+}
+
+
+/********************************************************/
+/* DecomposedMatix */
+/********************************************************/
+
+DecomposedMatix::DecomposedMatix()
+{
+}
+
+DecomposedMatix::DecomposedMatix(const QTransform &t0)
+{
+ QTransform t(t0);
+
+ QTransform projMatrix;
+
+ if (t.m33() == 0.0 || t0.determinant() == 0.0) {
+ qWarning() << "Cannot decompose matrix!" << t;
+ valid = false;
+ return;
+ }
+
+ if (t.type() == QTransform::TxProject) {
+ QTransform affineTransform(t.toAffine());
+ projMatrix = affineTransform.inverted() * t;
+
+ t = affineTransform;
+ proj[0] = projMatrix.m13();
+ proj[1] = projMatrix.m23();
+ proj[2] = projMatrix.m33();
+ }
+
+
+ std::array<QVector3D, 3> rows;
+
+ rows[0] = QVector3D(t.m11(), t.m12(), t.m13());
+ rows[1] = QVector3D(t.m21(), t.m22(), t.m23());
+ rows[2] = QVector3D(t.m31(), t.m32(), t.m33());
+
+ if (!qFuzzyCompare(t.m33(), 1.0)) {
+ const qreal invM33 = 1.0 / t.m33();
+
+ for (auto row : rows) {
+ row *= invM33;
+ }
+ }
+
+ dx = rows[2].x();
+ dy = rows[2].y();
+
+ rows[2] = QVector3D(0,0,1);
+
+ scaleX = rows[0].length();
+ rows[0] *= 1.0 / scaleX;
+
+ shearXY = QVector3D::dotProduct(rows[0], rows[1]);
+ rows[1] = rows[1] - shearXY * rows[0];
+
+ scaleY = rows[1].length();
+ rows[1] *= 1.0 / scaleY;
+ shearXY *= 1.0 / scaleY;
+
+ // If determinant is negative, one axis was flipped.
+ qreal determinant = rows[0].x() * rows[1].y() - rows[0].y() * rows[1].x();
+ if (determinant < 0) {
+ // Flip axis with minimum unit vector dot product.
+ if (rows[0].x() < rows[1].y()) {
+ scaleX = -scaleX;
+ rows[0] = -rows[0];
+ } else {
+ scaleY = -scaleY;
+ rows[1] = -rows[1];
+ }
+ shearXY = - shearXY;
+ }
+
+ angle = kisRadiansToDegrees(std::atan2(rows[0].y(), rows[0].x()));
+
+ if (angle != 0.0) {
+ // Rotate(-angle) = [cos(angle), sin(angle), -sin(angle), cos(angle)]
+ // = [row0x, -row0y, row0y, row0x]
+ // Thanks to the normalization above.
+
+ qreal sn = -rows[0].y();
+ qreal cs = rows[0].x();
+ qreal m11 = rows[0].x();
+ qreal m12 = rows[0].y();
+ qreal m21 = rows[1].x();
+ qreal m22 = rows[1].y();
+ rows[0].setX(cs * m11 + sn * m21);
+ rows[0].setY(cs * m12 + sn * m22);
+ rows[1].setX(-sn * m11 + cs * m21);
+ rows[1].setY(-sn * m12 + cs * m22);
+ }
+
+ QTransform leftOver(
+ rows[0].x(), rows[0].y(), rows[0].z(),
+ rows[1].x(), rows[1].y(), rows[1].z(),
+ rows[2].x(), rows[2].y(), rows[2].z());
+
+ KIS_SAFE_ASSERT_RECOVER_NOOP(fuzzyMatrixCompare(leftOver, QTransform(), 1e-4));
+}
+
+}
diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h
new file mode 100644
index 0000000000..d001586026
--- /dev/null
+++ b/libs/global/kis_algebra_2d.h
@@ -0,0 +1,564 @@
+/*
+ * 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_ALGEBRA_2D_H
+#define __KIS_ALGEBRA_2D_H
+
+#include <QPoint>
+#include <QPointF>
+#include <QVector>
+#include <QPolygonF>
+#include <QTransform>
+#include <cmath>
+#include <kis_global.h>
+#include <kritaglobal_export.h>
+#include <functional>
+
+class QPainterPath;
+class QTransform;
+
+namespace KisAlgebra2D {
+
+template <class T>
+struct PointTypeTraits
+{
+};
+
+template <>
+struct PointTypeTraits<QPoint>
+{
+ typedef int value_type;
+ typedef qreal calculation_type;
+ typedef QRect rect_type;
+};
+
+template <>
+struct PointTypeTraits<QPointF>
+{
+ typedef qreal value_type;
+ typedef qreal calculation_type;
+ typedef QRectF rect_type;
+};
+
+
+template <class T>
+typename PointTypeTraits<T>::value_type dotProduct(const T &a, const T &b)
+{
+ return a.x() * b.x() + a.y() * b.y();
+}
+
+template <class T>
+typename PointTypeTraits<T>::value_type crossProduct(const T &a, const T &b)
+{
+ return a.x() * b.y() - a.y() * b.x();
+}
+
+template <class T>
+qreal norm(const T &a)
+{
+ return std::sqrt(pow2(a.x()) + pow2(a.y()));
+}
+
+template <class Point>
+Point normalize(const Point &a)
+{
+ const qreal length = norm(a);
+ return (1.0 / length) * a;
+}
+
+/**
+ * Usual sign() function with positive zero
+ */
+template <typename T>
+T signPZ(T x) {
+ return x >= T(0) ? T(1) : T(-1);
+}
+
+/**
+ * Usual sign() function with zero returning zero
+ */
+template <typename T>
+T signZZ(T x) {
+ return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1);
+}
+
+/**
+ * Copies the sign of \p y into \p x and returns the result
+ */
+template <typename T>
+ inline T copysign(T x, T y) {
+ T strippedX = qAbs(x);
+ return y >= T(0) ? strippedX : -strippedX;
+}
+
+template <class T>
+T leftUnitNormal(const T &a)
+{
+ T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0);
+ qreal length = norm(result);
+ result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length;
+
+ return -result;
+}
+
+template <class T>
+T rightUnitNormal(const T &a)
+{
+ return -leftUnitNormal(a);
+}
+
+template <class T>
+T inwardUnitNormal(const T &a, int polygonDirection)
+{
+ return polygonDirection * leftUnitNormal(a);
+}
+
+/**
+ * \return 1 if the polygon is counterclockwise
+ * -1 if the polygon is clockwise
+ *
+ * Note: the sign is flipped because our 0y axis
+ * is reversed
+ */
+template <class T>
+int polygonDirection(const QVector<T> &polygon) {
+
+ typename PointTypeTraits<T>::value_type doubleSum = 0;
+
+ const int numPoints = polygon.size();
+ for (int i = 1; i <= numPoints; i++) {
+ int prev = i - 1;
+ int next = i == numPoints ? 0 : i;
+
+ doubleSum +=
+ (polygon[next].x() - polygon[prev].x()) *
+ (polygon[next].y() + polygon[prev].y());
+ }
+
+ return doubleSum >= 0 ? 1 : -1;
+}
+
+template <typename T>
+bool isInRange(T x, T a, T b) {
+ T length = qAbs(a - b);
+ return qAbs(x - a) <= length && qAbs(x - b) <= length;
+}
+
+void KRITAGLOBAL_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt);
+
+/**
+ * Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled
+ * and then rotated into \p base2 using transformation matrix S *
+ * R. The function applies the same transformation to \pt and returns
+ * the result.
+ **/
+QPointF KRITAGLOBAL_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2);
+
+qreal KRITAGLOBAL_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2);
+
+namespace Private {
+ inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) {
+ *rc = QRect(pt, QSize(1, 1));
+ }
+
+ inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) {
+ static const qreal eps = 1e-10;
+ *rc = QRectF(pt, QSizeF(eps, eps));
+ }
+}
+
+template <class Point, class Rect>
+inline void accumulateBounds(const Point &pt, Rect *bounds)
+{
+ if (bounds->isEmpty()) {
+ Private::resetEmptyRectangle(pt, bounds);
+ }
+
+ if (pt.x() > bounds->right()) {
+ bounds->setRight(pt.x());
+ }
+
+ if (pt.x() < bounds->left()) {
+ bounds->setLeft(pt.x());
+ }
+
+ if (pt.y() > bounds->bottom()) {
+ bounds->setBottom(pt.y());
+ }
+
+ if (pt.y() < bounds->top()) {
+ bounds->setTop(pt.y());
+ }
+}
+
+template <template <class T> class Container, class Point, class Rect>
+inline void accumulateBounds(const Container<Point> &points, Rect *bounds)
+{
+ Q_FOREACH (const Point &pt, points) {
+ accumulateBounds(pt, bounds);
+ }
+}
+
+template <template <class T> class Container, class Point>
+inline typename PointTypeTraits<Point>::rect_type
+accumulateBounds(const Container<Point> &points)
+{
+ typename PointTypeTraits<Point>::rect_type result;
+
+ Q_FOREACH (const Point &pt, points) {
+ accumulateBounds(pt, &result);
+ }
+
+ return result;
+}
+
+template <class Point, class Rect>
+inline Point clampPoint(Point pt, const Rect &bounds)
+{
+ if (pt.x() > bounds.right()) {
+ pt.rx() = bounds.right();
+ }
+
+ if (pt.x() < bounds.left()) {
+ pt.rx() = bounds.left();
+ }
+
+ if (pt.y() > bounds.bottom()) {
+ pt.ry() = bounds.bottom();
+ }
+
+ if (pt.y() < bounds.top()) {
+ pt.ry() = bounds.top();
+ }
+
+ return pt;
+}
+
+template <class Size>
+auto maxDimension(Size size) -> decltype(size.width()) {
+ return qMax(size.width(), size.height());
+}
+
+QPainterPath KRITAGLOBAL_EXPORT smallArrow();
+
+/**
+ * Multiply width and height of \p rect by \p coeff keeping the
+ * center of the rectangle pinned
+ */
+template <class Rect>
+Rect blowRect(const Rect &rect, qreal coeff)
+{
+ typedef decltype(rect.x()) CoordType;
+
+ CoordType w = rect.width() * coeff;
+ CoordType h = rect.height() * coeff;
+
+ return rect.adjusted(-w, -h, w, h);
+}
+
+QPoint KRITAGLOBAL_EXPORT ensureInRect(QPoint pt, const QRect &bounds);
+QPointF KRITAGLOBAL_EXPORT ensureInRect(QPointF pt, const QRectF &bounds);
+
+template <class Rect>
+Rect ensureRectNotSmaller(Rect rc, const decltype(Rect().size()) &size)
+{
+ typedef decltype(Rect().size()) Size;
+ typedef decltype(Rect().top()) ValueType;
+
+ if (rc.width() < size.width() ||
+ rc.height() < size.height()) {
+
+ ValueType width = qMax(rc.width(), size.width());
+ ValueType height = qMax(rc.height(), size.height());
+
+ rc = Rect(rc.topLeft(), Size(width, height));
+ }
+
+ return rc;
+}
+
+template <class Size>
+Size ensureSizeNotSmaller(const Size &size, const Size &bounds)
+{
+ Size result = size;
+
+ const auto widthBound = qAbs(bounds.width());
+ auto width = result.width();
+ if (qAbs(width) < widthBound) {
+ width = copysign(widthBound, width);
+ result.setWidth(width);
+ }
+
+ const auto heightBound = qAbs(bounds.height());
+ auto height = result.height();
+ if (qAbs(height) < heightBound) {
+ height = copysign(heightBound, height);
+ result.setHeight(height);
+ }
+
+ return result;
+}
+
+
+/**
+ * Attempt to intersect a line to the area of the a rectangle.
+ *
+ * If the line intersects the rectange, it will be modified to represent the intersecting line segment and true is returned.
+ * If the line does not intersect the area, it remains unmodified and false will be returned.
+ *
+ * @param segment
+ * @param area
+ * @return true if successful
+ */
+bool KRITAGLOBAL_EXPORT intersectLineRect(QLineF &line, const QRect rect);
+
+
+template <class Point>
+inline Point abs(const Point &pt) {
+ return Point(qAbs(pt.x()), qAbs(pt.y()));
+}
+
+
+class RightHalfPlane {
+public:
+
+ RightHalfPlane(const QPointF &a, const QPointF &b)
+ : m_a(a), m_p(b - a), m_norm_p_inv(1.0 / norm(m_p))
+ {
+ }
+
+ RightHalfPlane(const QLineF &line)
+ : RightHalfPlane(line.p1(), line.p2())
+ {
+ }
+
+ qreal valueSq(const QPointF &pt) const {
+ const qreal val = value(pt);
+ return signZZ(val) * pow2(val);
+ }
+
+ qreal value(const QPointF &pt) const {
+ return crossProduct(m_p, pt - m_a) * m_norm_p_inv;
+ }
+
+ int pos(const QPointF &pt) const {
+ return signZZ(value(pt));
+ }
+
+ QLineF getLine() const {
+ return QLineF(m_a, m_a + m_p);
+ }
+
+private:
+ const QPointF m_a;
+ const QPointF m_p;
+ const qreal m_norm_p_inv;
+};
+
+class OuterCircle {
+public:
+
+ OuterCircle(const QPointF &c, qreal radius)
+ : m_c(c),
+ m_radius(radius),
+ m_radius_sq(pow2(radius)),
+ m_fadeCoeff(1.0 / (pow2(radius + 1.0) - m_radius_sq))
+ {
+ }
+
+ qreal valueSq(const QPointF &pt) const {
+ const qreal val = value(pt);
+
+ return signZZ(val) * pow2(val);
+ }
+
+ qreal value(const QPointF &pt) const {
+ return kisDistance(pt, m_c) - m_radius;
+ }
+
+ int pos(const QPointF &pt) const {
+ return signZZ(valueSq(pt));
+ }
+
+ qreal fadeSq(const QPointF &pt) const {
+ const qreal valSq = kisSquareDistance(pt, m_c);
+ return (valSq - m_radius_sq) * m_fadeCoeff;
+ }
+
+private:
+ const QPointF m_c;
+ const qreal m_radius;
+ const qreal m_radius_sq;
+ const qreal m_fadeCoeff;
+};
+
+QVector<QPoint> KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRect &rect);
+QVector<QPointF> KRITAGLOBAL_EXPORT sampleRectWithPoints(const QRectF &rect);
+
+QRect KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector<QPoint> &points);
+QRectF KRITAGLOBAL_EXPORT approximateRectFromPoints(const QVector<QPointF> &points);
+
+QRect KRITAGLOBAL_EXPORT approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func);
+
+
+/**
+ * Cuts off a portion of a rect \p rc defined by a half-plane \p p
+ * \return the bounding rect of the resulting polygon
+ */
+KRITAGLOBAL_EXPORT
+QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p);
+
+
+/**
+ * Solves a quadratic equation in a form:
+ *
+ * a * x^2 + b * x + c = 0
+ *
+ * WARNING: Please note that \p a *must* be nonzero! Otherwise the
+ * equation is not quadratic! And this function doesn't check that!
+ *
+ * \return the number of solutions. It can be 0, 1 or 2.
+ *
+ * \p x1, \p x2 --- the found solution. The variables are filled with
+ * data iff the corresponding solution is found. That
+ * is: 0 solutions --- variabled are not touched, 1
+ * solution --- x1 is filled with the result, 2
+ * solutions --- x1 and x2 are filled.
+ */
+KRITAGLOBAL_EXPORT
+int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2);
+
+/**
+ * Finds the points of intersections between two circles
+ * \return the found circles, the result can have 0, 1 or 2 points
+ */
+KRITAGLOBAL_EXPORT
+QVector<QPointF> intersectTwoCircles(const QPointF &c1, qreal r1,
+ const QPointF &c2, qreal r2);
+
+KRITAGLOBAL_EXPORT
+QTransform mapToRect(const QRectF &rect);
+
+/**
+ * Scale the relative point \pt into the bounds of \p rc. The point might be
+ * outside the rectangle.
+ */
+inline QPointF relativeToAbsolute(const QPointF &pt, const QRectF &rc) {
+ return rc.topLeft() + QPointF(pt.x() * rc.width(), pt.y() * rc.height());
+}
+
+/**
+ * Get the relative position of \p pt inside rectangle \p rc. The point can be
+ * outside the rectangle.
+ */
+inline QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc) {
+ if (!rc.isValid()) return QPointF();
+
+ const QPointF rel = pt - rc.topLeft();
+ return QPointF(rel.x() / rc.width(), rel.y() / rc.height());
+
+}
+
+/**
+ * Compare the matrices with tolerance \p delta
+ */
+bool KRITAGLOBAL_EXPORT fuzzyMatrixCompare(const QTransform &t1, const QTransform &t2, qreal delta);
+
+/**
+ * Compare two rectangles with tolerance \p tolerance. The tolerance means that the
+ * coordinates of top left and bottom right corners should not differ more than \p tolerance
+ * pixels.
+ */
+template<class Rect, typename Difference = decltype(Rect::width())>
+bool fuzzyCompareRects(const Rect &r1, const Rect &r2, Difference tolerance) {
+ typedef decltype(r1.topLeft()) Point;
+
+ const Point d1 = abs(r1.topLeft() - r2.topLeft());
+ const Point d2 = abs(r1.bottomRight() - r2.bottomRight());
+
+ const Difference maxError = std::max({d1.x(), d1.y(), d2.x(), d2.y()});
+ return maxError < tolerance;
+}
+
+struct KRITAGLOBAL_EXPORT DecomposedMatix {
+ DecomposedMatix();
+
+ DecomposedMatix(const QTransform &t0);
+
+ inline QTransform scaleTransform() const
+ {
+ return QTransform::fromScale(scaleX, scaleY);
+ }
+
+ inline QTransform shearTransform() const
+ {
+ QTransform t;
+ t.shear(shearXY, 0);
+ return t;
+ }
+
+ inline QTransform rotateTransform() const
+ {
+ QTransform t;
+ t.rotate(angle);
+ return t;
+ }
+
+ inline QTransform translateTransform() const
+ {
+ return QTransform::fromTranslate(dx, dy);
+ }
+
+ inline QTransform projectTransform() const
+ {
+ return
+ QTransform(
+ 1,0,proj[0],
+ 0,1,proj[1],
+ 0,0,proj[2]);
+ }
+
+ inline QTransform transform() const {
+ return
+ scaleTransform() *
+ shearTransform() *
+ rotateTransform() *
+ translateTransform() *
+ projectTransform();
+ }
+
+ inline bool isValid() const {
+ return valid;
+ }
+
+ qreal scaleX = 1.0;
+ qreal scaleY = 1.0;
+ qreal shearXY = 0.0;
+ qreal angle = 0.0;
+ qreal dx = 0.0;
+ qreal dy = 0.0;
+ qreal proj[3] = {0.0, 0.0, 1.0};
+
+private:
+ bool valid = true;
+};
+
+}
+
+
+#endif /* __KIS_ALGEBRA_2D_H */
diff --git a/libs/global/kis_dom_utils.h b/libs/global/kis_dom_utils.h
index 27e08407e9..e5b8ae3043 100644
--- a/libs/global/kis_dom_utils.h
+++ b/libs/global/kis_dom_utils.h
@@ -1,254 +1,274 @@
/*
* 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_DOM_UTILS_H
#define __KIS_DOM_UTILS_H
+#include <float.h>
+
#include <QPointF>
#include <QVector3D>
#include <QVector>
#include <QDomElement>
#include <QLocale>
#include <klocalizedstring.h>
#include "kritaglobal_export.h"
#include "kis_debug.h"
namespace KisDomUtils {
inline QString toString(const QString &value) {
return value;
}
template<typename T>
inline QString toString(T value) {
return QString::number(value);
}
+ inline QString toString(float value) {
+ QString str;
+ QTextStream stream;
+ stream.setString(&str, QIODevice::WriteOnly);
+ stream.setRealNumberPrecision(FLT_DIG);
+ stream << value;
+ return str;
+ }
+
+ inline QString toString(double value) {
+ QString str;
+ QTextStream stream;
+ stream.setString(&str, QIODevice::WriteOnly);
+ stream.setRealNumberPrecision(11);
+ stream << value;
+ return str;
+ }
+
inline int toInt(const QString &str) {
bool ok = false;
int value = 0;
QLocale c(QLocale::German);
value = str.toInt(&ok);
if (!ok) {
value = c.toInt(str, &ok);
}
if (!ok) {
warnKrita << "WARNING: KisDomUtils::toInt failed:" << ppVar(str);
value = 0;
}
return value;
}
inline double toDouble(const QString &str) {
bool ok = false;
double value = 0;
QLocale c(QLocale::German);
/**
* A special workaround to handle ','/'.' decimal point
* in different locales. Added for backward compatibility,
* because we used to save qreals directly using
*
* e.setAttribute("w", (qreal)value),
*
* which did local-aware conversion.
*/
value = str.toDouble(&ok);
if (!ok) {
value = c.toDouble(str, &ok);
}
if (!ok) {
warnKrita << "WARNING: KisDomUtils::toDouble failed:" << ppVar(str);
value = 0;
}
return value;
}
/**
* Save a value of type QRect into an XML tree. A child for \p parent
* is created and assigned a tag \p tag. The corresponding value can
* be fetched from the XML using loadValue() later.
*
* \see loadValue()
*/
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QRect &rc);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QSize &size);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QPoint &pt);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QPointF &pt);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QVector3D &pt);
void KRITAGLOBAL_EXPORT saveValue(QDomElement *parent, const QString &tag, const QTransform &t);
/**
* Save a value of a scalar type into an XML tree. A child for \p parent
* is created and assigned a tag \p tag. The corresponding value can
* be fetched from the XML using loadValue() later.
*
* \see loadValue()
*/
template <typename T>
void saveValue(QDomElement *parent, const QString &tag, T value)
{
QDomDocument doc = parent->ownerDocument();
QDomElement e = doc.createElement(tag);
parent->appendChild(e);
e.setAttribute("type", "value");
e.setAttribute("value", toString(value));
}
/**
* Save a vector of values into an XML tree. A child for \p parent is
* created and assigned a tag \p tag. The values in the array should
* have a type supported by saveValue() overrides. The corresponding
* vector can be fetched from the XML using loadValue() later.
*
* \see loadValue()
*/
template <template <class> class Container, typename T>
void saveValue(QDomElement *parent, const QString &tag, const Container<T> &array)
{
QDomDocument doc = parent->ownerDocument();
QDomElement e = doc.createElement(tag);
parent->appendChild(e);
e.setAttribute("type", "array");
int i = 0;
Q_FOREACH (const T &v, array) {
saveValue(&e, QString("item_%1").arg(i++), v);
}
}
/**
* Find an element with tag \p tag which is a child of \p parent. The element should
* be the only element with the provided tag in this parent.
*
* \return true is the element with \p tag is found and it is unique
*/
bool KRITAGLOBAL_EXPORT findOnlyElement(const QDomElement &parent, const QString &tag, QDomElement *el, QStringList *errorMessages = 0);
/**
* Load an object from an XML element, which is a child of \p parent and has
* a tag \p tag.
*
* \return true if the object is successfully loaded and is unique
*
* \see saveValue()
*/
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, float *v);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, double *v);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QSize *size);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QRect *rc);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QPoint *pt);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QPointF *pt);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QVector3D *pt);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QTransform *t);
bool KRITAGLOBAL_EXPORT loadValue(const QDomElement &e, QString *value);
namespace Private {
bool KRITAGLOBAL_EXPORT checkType(const QDomElement &e, const QString &expectedType);
}
/**
* Load a scalar value from an XML element, which is a child of \p parent
* and has a tag \p tag.
*
* \return true if the object is successfully loaded and is unique
*
* \see saveValue()
*/
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
loadValue(const QDomElement &e, T *value)
{
if (!Private::checkType(e, "value")) return false;
QVariant v(e.attribute("value", "no-value"));
*value = v.value<T>();
return true;
}
/**
* A special adapter method that makes vector- and tag-based methods
* work with environment parameter uniformly.
*/
template <typename T, typename E>
typename std::enable_if<std::is_empty<E>::value, bool>::type
loadValue(const QDomElement &parent, T *value, const E &/*env*/) {
return KisDomUtils::loadValue(parent, value);
}
/**
* Load an array from an XML element, which is a child of \p parent
* and has a tag \p tag.
*
* \return true if the object is successfully loaded and is unique
*
* \see saveValue()
*/
template <template <class> class Container, typename T, typename E = std::tuple<>>
bool loadValue(const QDomElement &e, Container<T> *array, const E &env = E())
{
if (!Private::checkType(e, "array")) return false;
QDomElement child = e.firstChildElement();
while (!child.isNull()) {
T value;
if (!loadValue(child, &value, env)) return false;
*array << value;
child = child.nextSiblingElement();
}
return true;
}
template <typename T, typename E = std::tuple<>>
bool loadValue(const QDomElement &parent, const QString &tag, T *value, const E &env = E())
{
QDomElement e;
if (!findOnlyElement(parent, tag, &e)) return false;
return loadValue(e, value, env);
}
KRITAGLOBAL_EXPORT QDomElement findElementByAttibute(QDomNode parent,
const QString &tag,
const QString &attribute,
const QString &key);
KRITAGLOBAL_EXPORT bool removeElements(QDomElement &parent, const QString &tag);
}
#endif /* __KIS_DOM_UTILS_H */
diff --git a/libs/global/kis_global.h b/libs/global/kis_global.h
index 785cd338bf..84cb472528 100644
--- a/libs/global/kis_global.h
+++ b/libs/global/kis_global.h
@@ -1,323 +1,273 @@
/*
* Copyright (c) 2000 Matthias Elter <elter@kde.org>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KISGLOBAL_H_
#define KISGLOBAL_H_
#include <limits.h>
#include <KoConfig.h>
#include "kis_assert.h"
#include <QPoint>
#include <QPointF>
const quint8 quint8_MAX = UCHAR_MAX;
const quint16 quint16_MAX = 65535;
const qint32 qint32_MAX = (2147483647);
const qint32 qint32_MIN = (-2147483647 - 1);
const quint8 MAX_SELECTED = UCHAR_MAX;
const quint8 MIN_SELECTED = 0;
const quint8 SELECTION_THRESHOLD = 1;
enum OutlineStyle {
OUTLINE_NONE = 0,
OUTLINE_CIRCLE,
OUTLINE_FULL,
OUTLINE_TILT,
OUTLINE_COLOR,
N_OUTLINE_STYLE_SIZE
};
enum CursorStyle {
CURSOR_STYLE_NO_CURSOR = 0,
CURSOR_STYLE_TOOLICON,
CURSOR_STYLE_POINTER,
CURSOR_STYLE_SMALL_ROUND,
CURSOR_STYLE_CROSSHAIR,
CURSOR_STYLE_TRIANGLE_RIGHTHANDED,
CURSOR_STYLE_TRIANGLE_LEFTHANDED,
CURSOR_STYLE_BLACK_PIXEL,
CURSOR_STYLE_WHITE_PIXEL,
N_CURSOR_STYLE_SIZE
};
enum OldCursorStyle {
OLD_CURSOR_STYLE_TOOLICON = 0,
OLD_CURSOR_STYLE_CROSSHAIR = 1,
OLD_CURSOR_STYLE_POINTER = 2,
OLD_CURSOR_STYLE_OUTLINE = 3,
OLD_CURSOR_STYLE_NO_CURSOR = 4,
OLD_CURSOR_STYLE_SMALL_ROUND = 5,
OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT = 6,
OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS = 7,
OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED = 8,
OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED = 9,
OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED = 10,
OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED = 11
};
/*
* Most wacom pads have 512 levels of pressure; Qt only supports 256, and even
* this is downscaled to 127 levels because the line would be too jittery, and
* the amount of masks take too much memory otherwise.
*/
const qint32 PRESSURE_LEVELS = 127;
const double PRESSURE_MIN = 0.0;
const double PRESSURE_MAX = 1.0;
const double PRESSURE_DEFAULT = PRESSURE_MAX;
const double PRESSURE_THRESHOLD = 5.0 / 255.0;
// copy of lcms.h
#define INTENT_PERCEPTUAL 0
#define INTENT_RELATIVE_COLORIMETRIC 1
#define INTENT_SATURATION 2
#define INTENT_ABSOLUTE_COLORIMETRIC 3
#include <cmath>
#include <QPointF>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// converts \p a to [0, 2 * M_PI) range
inline qreal normalizeAngle(qreal a) {
if (a < 0.0) {
a = 2 * M_PI + fmod(a, 2 * M_PI);
}
- return a > 2 * M_PI ? fmod(a, 2 * M_PI) : a;
+ return a >= 2 * M_PI ? fmod(a, 2 * M_PI) : a;
}
// converts \p a to [0, 360.0) range
inline qreal normalizeAngleDegrees(qreal a) {
if (a < 0.0) {
a = 360.0 + fmod(a, 360.0);
}
- return a > 360.0 ? fmod(a, 360.0) : a;
+ return a >= 360.0 ? fmod(a, 360.0) : a;
}
inline qreal shortestAngularDistance(qreal a, qreal b) {
qreal dist = fmod(qAbs(a - b), 2 * M_PI);
if (dist > M_PI) dist = 2 * M_PI - dist;
return dist;
}
inline qreal incrementInDirection(qreal a, qreal inc, qreal direction) {
qreal b1 = a + inc;
qreal b2 = a - inc;
qreal d1 = shortestAngularDistance(b1, direction);
qreal d2 = shortestAngularDistance(b2, direction);
return d1 < d2 ? b1 : b2;
}
+inline qreal bisectorAngle(qreal a, qreal b) {
+ const qreal diff = shortestAngularDistance(a, b);
+ return incrementInDirection(a, 0.5 * diff, b);
+}
+
template<typename PointType>
inline PointType snapToClosestAxis(PointType P) {
if (qAbs(P.x()) < qAbs(P.y())) {
P.setX(0);
} else {
P.setY(0);
}
return P;
}
template<typename T>
inline T pow2(const T& x) {
return x * x;
}
template<typename T>
inline T kisDegreesToRadians(T degrees) {
return degrees * M_PI / 180.0;
}
template<typename T>
inline T kisRadiansToDegrees(T radians) {
return radians * 180.0 / M_PI;
}
template<class T, typename U>
inline T kisGrowRect(const T &rect, U offset) {
return rect.adjusted(-offset, -offset, offset, offset);
}
inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) {
return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y()));
}
inline qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2) {
return pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y());
}
#include <QLineF>
inline qreal kisDistanceToLine(const QPointF &m, const QLineF &line)
{
const QPointF &p1 = line.p1();
const QPointF &p2 = line.p2();
qreal distance = 0;
if (qFuzzyCompare(p1.x(), p2.x())) {
distance = qAbs(m.x() - p2.x());
} else if (qFuzzyCompare(p1.y(), p2.y())) {
distance = qAbs(m.y() - p2.y());
} else {
qreal A = 1;
qreal B = - (p1.x() - p2.x()) / (p1.y() - p2.y());
qreal C = - p1.x() - B * p1.y();
distance = qAbs(A * m.x() + B * m.y() + C) / std::sqrt(pow2(A) + pow2(B));
}
return distance;
}
inline QPointF kisProjectOnVector(const QPointF &base, const QPointF &v)
{
const qreal prod = base.x() * v.x() + base.y() * v.y();
const qreal lengthSq = pow2(base.x()) + pow2(base.y());
qreal coeff = prod / lengthSq;
return coeff * base;
}
#include <QRect>
inline QRect kisEnsureInRect(QRect rc, const QRect &bounds)
{
if(rc.right() > bounds.right()) {
rc.translate(bounds.right() - rc.right(), 0);
}
if(rc.left() < bounds.left()) {
rc.translate(bounds.left() - rc.left(), 0);
}
if(rc.bottom() > bounds.bottom()) {
rc.translate(0, bounds.bottom() - rc.bottom());
}
if(rc.top() < bounds.top()) {
rc.translate(0, bounds.top() - rc.top());
}
return rc;
}
-#include <QSharedPointer>
-
-template <class T>
-inline QSharedPointer<T> toQShared(T* ptr) {
- return QSharedPointer<T>(ptr);
-}
-
-template <class A, template <class C> class List>
-List<QSharedPointer<A>> listToQShared(const List<A*> list) {
- List<QSharedPointer<A>> newList;
- Q_FOREACH(A* value, list) {
- newList.append(toQShared(value));
- }
- return newList;
-}
-
-
-/**
- * Convert a list of strong pointers into a list of weak pointers
- */
-template <template <class> class Container, class T>
-Container<QWeakPointer<T>> listStrongToWeak(const Container<QSharedPointer<T>> &containter)
-{
- Container<QWeakPointer<T> > result;
- Q_FOREACH (QSharedPointer<T> v, containter) {
- result << v;
- }
- return result;
-}
-
-/**
- * Convert a list of weak pointers into a list of strong pointers
- *
- * WARNING: By default, uses "all or nothing" rule. If at least one of
- * the weak pointers is invalid, returns an *empty* list!
- * Even though some other pointer can still be converted
- * correctly.
- */
-template <template <class> class Container, class T>
- Container<QSharedPointer<T> > listWeakToStrong(const Container<QWeakPointer<T>> &containter,
- bool allOrNothing = true)
-{
- Container<QSharedPointer<T> > result;
- Q_FOREACH (QWeakPointer<T> v, containter) {
- QSharedPointer<T> strong(v);
- if (!strong && allOrNothing) {
- result.clear();
- return result;
- }
-
- if (strong) {
- result << strong;
- }
- }
- return result;
-}
+#include "kis_pointer_utils.h"
/**
* A special wrapper object that converts Qt-style mutexes and locks
* into an object that supports Std's (and Boost's) "Lockable"
* concept. Basically, it converts tryLock() into try_lock() to comply
* with the syntax.
*/
template <class T>
struct StdLockableWrapper {
StdLockableWrapper(T *lock) : m_lock(lock) {}
void lock() {
m_lock->lock();
}
bool try_lock() {
return m_lock->tryLock();
}
void unlock() {
m_lock->unlock();
}
private:
T *m_lock;
};
#endif // KISGLOBAL_H_
diff --git a/libs/global/kis_painting_tweaks.cpp b/libs/global/kis_painting_tweaks.cpp
index 1c59e1a782..56a29d03f2 100644
--- a/libs/global/kis_painting_tweaks.cpp
+++ b/libs/global/kis_painting_tweaks.cpp
@@ -1,51 +1,111 @@
/*
* 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_painting_tweaks.h"
+
+
+#include <QPen>
#include <QRegion>
#include <QPainter>
#include <QTransform>
#include "kis_debug.h"
namespace KisPaintingTweaks {
QRegion safeClipRegion(const QPainter &painter)
{
const QTransform t = painter.transform();
QRegion region = t.type() <= QTransform::TxScale ?
painter.clipRegion() :
QRegion(painter.clipBoundingRect().toAlignedRect());
if (region.rectCount() > 1000) {
qWarning() << "WARNING: KisPaintingTweaks::safeClipRegion: too many rectangles in the region!" << ppVar(region.rectCount());
region = QRegion(painter.clipBoundingRect().toAlignedRect());
}
return region;
}
QRect safeClipBoundingRect(const QPainter &painter)
{
return painter.clipBoundingRect().toAlignedRect();
}
+void initAntsPen(QPen *antsPen, QPen *outlinePen,
+ int antLength, int antSpace)
+{
+ QVector<qreal> antDashPattern;
+ antDashPattern << antLength << antSpace;
+
+ *antsPen = QPen(Qt::CustomDashLine);
+ antsPen->setDashPattern(antDashPattern);
+ antsPen->setCosmetic(true);
+ antsPen->setColor(Qt::black);
+
+ *outlinePen = QPen(Qt::SolidLine);
+ outlinePen->setCosmetic(true);
+ outlinePen->setColor(Qt::white);
+}
+
+PenBrushSaver::PenBrushSaver(QPainter *painter)
+ : m_painter(painter),
+ m_pen(painter->pen()),
+ m_brush(painter->brush())
+{
+}
+
+PenBrushSaver::PenBrushSaver(QPainter *painter, const QPen &pen, const QBrush &brush)
+ : PenBrushSaver(painter)
+{
+ m_painter->setPen(pen);
+ m_painter->setBrush(brush);
+}
+
+PenBrushSaver::PenBrushSaver(QPainter *painter, const QPair<QPen, QBrush> &pair)
+ : PenBrushSaver(painter)
+{
+ m_painter->setPen(pair.first);
+ m_painter->setBrush(pair.second);
+}
+
+PenBrushSaver::PenBrushSaver(QPainter *painter, const QPair<QPen, QBrush> &pair, allow_noop_t)
+ : m_painter(painter)
+{
+ if (m_painter) {
+ m_pen = m_painter->pen();
+ m_brush = m_painter->brush();
+ m_painter->setPen(pair.first);
+ m_painter->setBrush(pair.second);
+ }
+}
+
+PenBrushSaver::~PenBrushSaver()
+{
+ if (m_painter) {
+ m_painter->setPen(m_pen);
+ m_painter->setBrush(m_brush);
+ }
+}
+
+
}
diff --git a/libs/global/kis_painting_tweaks.h b/libs/global/kis_painting_tweaks.h
index 274cbad763..5588b51d7f 100644
--- a/libs/global/kis_painting_tweaks.h
+++ b/libs/global/kis_painting_tweaks.h
@@ -1,45 +1,96 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PAINTING_TWEAKS_H
#define __KIS_PAINTING_TWEAKS_H
#include "kritaglobal_export.h"
+#include <QPen>
+#include <QBrush>
+
class QPainter;
class QRegion;
class QRect;
+class QPen;
namespace KisPaintingTweaks {
/**
* This is a workaround for QPainter::clipRegion() bug. When zoom
* is about 2000% and rotation is in a range[-5;5] degrees, the
* generated region will have about 20k+ regtangles inside. Their
* processing will be really slow. These functions fworkarounds
* the issue.
*/
KRITAGLOBAL_EXPORT QRegion safeClipRegion(const QPainter &painter);
/**
* \see safeClipRegion()
*/
KRITAGLOBAL_EXPORT QRect safeClipBoundingRect(const QPainter &painter);
+
+ KRITAGLOBAL_EXPORT void initAntsPen(QPen *antsPen, QPen *outlinePen,
+ int antLength = 4, int antSpace = 4);
+
+
+ /**
+ * A special class to save painter->pen() and painter->brush() using RAII
+ * principle.
+ */
+ class KRITAGLOBAL_EXPORT PenBrushSaver
+ {
+ public:
+ struct allow_noop_t { explicit allow_noop_t() = default; };
+ static constexpr allow_noop_t allow_noop { };
+
+ /**
+ * Saves pen and brush state of the provided painter object. \p painter cannot be null.
+ */
+ PenBrushSaver(QPainter *painter);
+
+ /**
+ * Overrides pen and brush of \p painter with the provided values. \p painter cannot be null.
+ */
+ PenBrushSaver(QPainter *painter, const QPen &pen, const QBrush &brush);
+
+ /**
+ * Overrides pen and brush of \p painter with the provided values. \p painter cannot be null.
+ */
+ PenBrushSaver(QPainter *painter, const QPair<QPen, QBrush> &pair);
+
+ /**
+ * A special contructor of PenBrushSaver that allows \p painter to be null. Passing null
+ * pointer will basically mean that the whole saver existance will be a noop.
+ */
+ PenBrushSaver(QPainter *painter, const QPair<QPen, QBrush> &pair, allow_noop_t);
+
+ /**
+ * Restores the state of the painter that has been saved during the construction of the saver
+ */
+ ~PenBrushSaver();
+
+ private:
+ PenBrushSaver(const PenBrushSaver &rhs) = delete;
+ QPainter *m_painter;
+ QPen m_pen;
+ QBrush m_brush;
+ };
}
#endif /* __KIS_PAINTING_TWEAKS_H */
diff --git a/libs/global/kis_pointer_utils.h b/libs/global/kis_pointer_utils.h
new file mode 100644
index 0000000000..dac37ca939
--- /dev/null
+++ b/libs/global/kis_pointer_utils.h
@@ -0,0 +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.
+ */
+
+#ifndef KIS_POINTER_UTILS_H
+#define KIS_POINTER_UTILS_H
+
+#include <QSharedPointer>
+
+/**
+ * Convert a raw pointer into a shared pointer
+ */
+template <class T>
+inline QSharedPointer<T> toQShared(T* ptr) {
+ return QSharedPointer<T>(ptr);
+}
+
+/**
+ * Convert a list of raw pointers into a list of shared pointers
+ */
+template <class A, template <class C> class List>
+List<QSharedPointer<A>> listToQShared(const List<A*> list) {
+ List<QSharedPointer<A>> newList;
+ Q_FOREACH(A* value, list) {
+ newList.append(toQShared(value));
+ }
+ return newList;
+}
+
+
+/**
+ * Convert a list of strong pointers into a list of weak pointers
+ */
+template <template <class> class Container, class T>
+Container<QWeakPointer<T>> listStrongToWeak(const Container<QSharedPointer<T>> &containter)
+{
+ Container<QWeakPointer<T> > result;
+ Q_FOREACH (QSharedPointer<T> v, containter) {
+ result << v;
+ }
+ return result;
+}
+
+/**
+ * Convert a list of weak pointers into a list of strong pointers
+ *
+ * WARNING: By default, uses "all or nothing" rule. If at least one of
+ * the weak pointers is invalid, returns an *empty* list!
+ * Even though some other pointer can still be converted
+ * correctly.
+ */
+template <template <class> class Container, class T>
+ Container<QSharedPointer<T> > listWeakToStrong(const Container<QWeakPointer<T>> &containter,
+ bool allOrNothing = true)
+{
+ Container<QSharedPointer<T> > result;
+ Q_FOREACH (QWeakPointer<T> v, containter) {
+ QSharedPointer<T> strong(v);
+ if (!strong && allOrNothing) {
+ result.clear();
+ return result;
+ }
+
+ if (strong) {
+ result << strong;
+ }
+ }
+ return result;
+}
+
+/**
+ * Coverts a list of objects with type T into a list of objects of type R.
+ * The conversion is done implicitly, therefore the c-tor of type R should
+ * support it. The main usage case is conversion of pointers in "descendant-
+ * to-parent" way.
+ */
+template <typename R, typename T>
+inline QList<R> implicitCastList(const QList<T> &list)
+{
+ QList<R> result;
+
+ Q_FOREACH(const T &item, list) {
+ result.append(item);
+ }
+ return result;
+}
+
+#endif // KIS_POINTER_UTILS_H
+
diff --git a/libs/image/kis_signal_auto_connection.h b/libs/global/kis_signal_auto_connection.h
similarity index 100%
rename from libs/image/kis_signal_auto_connection.h
rename to libs/global/kis_signal_auto_connection.h
diff --git a/libs/image/kis_signal_compressor.cpp b/libs/global/kis_signal_compressor.cpp
similarity index 100%
rename from libs/image/kis_signal_compressor.cpp
rename to libs/global/kis_signal_compressor.cpp
diff --git a/libs/global/kis_signal_compressor.h b/libs/global/kis_signal_compressor.h
new file mode 100644
index 0000000000..ab6d6945e6
--- /dev/null
+++ b/libs/global/kis_signal_compressor.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_SIGNAL_COMPRESSOR_H
+#define __KIS_SIGNAL_COMPRESSOR_H
+
+#include <QTimer>
+#include "kritaglobal_export.h"
+
+class QTimer;
+
+/**
+ * Sets a timer to delay or throttle activation of a Qt slot. One example of
+ * where this is used is to limit the amount of expensive redraw activity on the
+ * canvas.
+ *
+ * There are three behaviors to choose from.
+ *
+ * POSTPONE resets the timer after each call. Therefore if the calls are made
+ * quickly enough, the timer will never be activated.
+ *
+ * FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all
+ * the other actions the other action like in POSTPONE. This mode is
+ * used e.g. in move/remove layer functionality. If you remove a
+ * single layer, you'll see the result immediately. But if you want to
+ * remove multiple layers, you should wait until all the actions are
+ * finished.
+ *
+ * FIRST_ACTIVE emits the timeout() event immediately and sets a timer of
+ * duration \p delay. If the compressor is triggered during this time, it will
+ * wait until the end of the delay period to fire the signal. Further events are
+ * ignored until the timer elapses. Think of it as a queue with size 1, and
+ * where the leading element is popped every \p delay ms.
+ *
+ * FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p
+ * delay ms. The compressor becomes inactive and all events are ignored until
+ * the timer has elapsed.
+ *
+ */
+class KRITAGLOBAL_EXPORT KisSignalCompressor : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum Mode {
+ POSTPONE, /* Calling start() resets the timer to \p delay ms */
+ FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */
+ FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */
+ FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */
+ UNDEFINED /* KisSignalCompressor is created without an explicit mode */
+ };
+
+public:
+ KisSignalCompressor();
+ KisSignalCompressor(int delay, Mode mode, QObject *parent = 0);
+ bool isActive() const;
+ void setMode(Mode mode);
+ void setDelay(int delay);
+
+public Q_SLOTS:
+ void start();
+ void stop();
+
+private Q_SLOTS:
+ void slotTimerExpired();
+
+Q_SIGNALS:
+ void timeout();
+
+private:
+ QTimer *m_timer;
+ Mode m_mode;
+ bool m_gotSignals;
+};
+
+#endif /* __KIS_SIGNAL_COMPRESSOR_H */
diff --git a/libs/image/kis_signal_compressor_with_param.cpp b/libs/global/kis_signal_compressor_with_param.cpp
similarity index 100%
rename from libs/image/kis_signal_compressor_with_param.cpp
rename to libs/global/kis_signal_compressor_with_param.cpp
diff --git a/libs/global/kis_signal_compressor_with_param.h b/libs/global/kis_signal_compressor_with_param.h
new file mode 100644
index 0000000000..a6882bc0f5
--- /dev/null
+++ b/libs/global/kis_signal_compressor_with_param.h
@@ -0,0 +1,140 @@
+/*
+ * 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_SIGNAL_COMPRESSOR_WITH_PARAM_H
+#define __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H
+
+#include <kis_signal_compressor.h>
+#include <functional>
+
+
+/**
+ * A special class that converts a Qt signal into a std::function call.
+ *
+ * Example:
+ *
+ * std::function<void ()> destinationFunctionCall(std::bind(someNiceFunc, firstParam, secondParam));
+ * SignalToFunctionProxy proxy(destinationFunctionCall);
+ * connect(srcObject, SIGNAL(sigSomethingChanged()), &proxy, SLOT(start()));
+ *
+ * Now every time sigSomethingChanged() is emitted, someNiceFunc is
+ * called. std::bind allows us to call any method of any class without
+ * changing signature of the class or creating special wrappers.
+ */
+class KRITAGLOBAL_EXPORT SignalToFunctionProxy : public QObject
+{
+ Q_OBJECT
+public:
+ using TrivialFunction = std::function<void ()>;
+
+public:
+ SignalToFunctionProxy(TrivialFunction function)
+ : m_function(function)
+ {
+ }
+
+public Q_SLOTS:
+ void start() {
+ m_function();
+ }
+
+private:
+ TrivialFunction m_function;
+};
+
+
+/**
+ * A special class for deferring and comressing events with one
+ * parameter of type T. This works like KisSignalCompressor but can
+ * handle events with one parameter. Due to limitation of the Qt this
+ * doesn't allow signal/slots, so it uses std::function instead.
+ *
+ * In the end (after a timeout) the latest param value is returned to
+ * the callback.
+ *
+ * Usage:
+ *
+ * \code{.cpp}
+ *
+ * using namespace std::placeholders; // For _1 placeholder
+ *
+ * // prepare the callback function
+ * std::function<void (qreal)> callback(
+ * std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1));
+ *
+ * // Create the compressor object
+ * KisSignalCompressorWithParam<qreal> compressor(40, callback);
+ *
+ * // When event comes:
+ * compressor.start(0.123456);
+ *
+ * \endcode
+ */
+
+template <typename T>
+class KisSignalCompressorWithParam
+{
+public:
+ using CallbackFunction = std::function<void (T)>;
+
+public:
+KisSignalCompressorWithParam(int delay, CallbackFunction function, KisSignalCompressor::Mode mode = KisSignalCompressor::FIRST_ACTIVE)
+ : m_compressor(delay, mode),
+ m_function(function)
+ {
+ std::function<void ()> callback(
+ std::bind(&KisSignalCompressorWithParam<T>::fakeSlotTimeout, this));
+ m_signalProxy.reset(new SignalToFunctionProxy(callback));
+
+ m_compressor.connect(&m_compressor, SIGNAL(timeout()), m_signalProxy.data(), SLOT(start()));
+ }
+
+ ~KisSignalCompressorWithParam()
+ {
+ }
+
+ void start(T param) {
+ m_currentParamValue = param;
+ m_compressor.start();
+ }
+
+ void stop() {
+ m_compressor.stop();
+ }
+
+ bool isActive() const {
+ return m_compressor.isActive();
+ }
+
+ void setDelay(int value) {
+ m_compressor.setDelay(value);
+ }
+
+private:
+ void fakeSlotTimeout() {
+ m_function(m_currentParamValue);
+ }
+
+private:
+ KisSignalCompressor m_compressor;
+ CallbackFunction m_function;
+ QScopedPointer<SignalToFunctionProxy> m_signalProxy;
+ T m_currentParamValue;
+};
+
+#endif /* __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H */
diff --git a/libs/image/kis_signals_blocker.h b/libs/global/kis_signals_blocker.h
similarity index 100%
rename from libs/image/kis_signals_blocker.h
rename to libs/global/kis_signals_blocker.h
diff --git a/libs/global/krita_container_utils.h b/libs/global/krita_container_utils.h
new file mode 100644
index 0000000000..6d1ca96da4
--- /dev/null
+++ b/libs/global/krita_container_utils.h
@@ -0,0 +1,63 @@
+/*
+ * 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 KRITA_CONTAINER_UTILS_H
+#define KRITA_CONTAINER_UTILS_H
+
+#include <functional>
+
+namespace KritaUtils
+{
+
+template <class T>
+ bool compareListsUnordered(const QList<T> &a, const QList<T> &b) {
+ if (a.size() != b.size()) return false;
+
+ Q_FOREACH(const T &t, a) {
+ if (!b.contains(t)) return false;
+ }
+
+ return true;
+}
+
+template <class C>
+ void makeContainerUnique(C &container) {
+ std::sort(container.begin(), container.end());
+ auto newEnd = std::unique(container.begin(), container.end());
+
+ while (newEnd != container.end()) {
+ newEnd = container.erase(newEnd);
+ }
+}
+
+
+template <class C, typename KeepIfFunction>
+ auto filterContainer(C &container, KeepIfFunction keepIf)
+ -> decltype(bool(keepIf(container[0])), void()) {
+
+ auto newEnd = std::remove_if(container.begin(), container.end(), [keepIf] (typename C::reference p) { return !keepIf(p); });
+ while (newEnd != container.end()) {
+ newEnd = container.erase(newEnd);
+ }
+}
+
+}
+
+
+#endif // KRITA_CONTAINER_UTILS_H
+
diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt
index c86431986a..693622f780 100644
--- a/libs/image/CMakeLists.txt
+++ b/libs/image/CMakeLists.txt
@@ -1,395 +1,389 @@
add_subdirectory( tests )
add_subdirectory( tiles3 )
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/metadata
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty
${CMAKE_CURRENT_SOURCE_DIR}/brushengine
${CMAKE_CURRENT_SOURCE_DIR}/commands
${CMAKE_CURRENT_SOURCE_DIR}/commands_new
${CMAKE_CURRENT_SOURCE_DIR}/filter
${CMAKE_CURRENT_SOURCE_DIR}/floodfill
${CMAKE_CURRENT_SOURCE_DIR}/generator
${CMAKE_CURRENT_SOURCE_DIR}/layerstyles
${CMAKE_CURRENT_SOURCE_DIR}/processing
${CMAKE_CURRENT_SOURCE_DIR}/recorder
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
if(FFTW3_FOUND)
include_directories(${FFTW3_INCLUDE_DIR})
endif()
if(HAVE_VC)
include_directories(SYSTEM ${Vc_INCLUDE_DIR} ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS})
ko_compile_for_all_implementations(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp)
else()
set(__per_arch_circle_mask_generator_objs kis_brush_mask_applicator_factories.cpp)
endif()
set(kritaimage_LIB_SRCS
tiles3/kis_tile.cc
tiles3/kis_tile_data.cc
tiles3/kis_tile_data_store.cc
tiles3/kis_tile_data_pooler.cc
tiles3/kis_tiled_data_manager.cc
tiles3/kis_memento_manager.cc
tiles3/kis_hline_iterator.cpp
tiles3/kis_vline_iterator.cpp
tiles3/kis_random_accessor.cc
tiles3/swap/kis_abstract_compression.cpp
tiles3/swap/kis_lzf_compression.cpp
tiles3/swap/kis_abstract_tile_compressor.cpp
tiles3/swap/kis_legacy_tile_compressor.cpp
tiles3/swap/kis_tile_compressor_2.cpp
tiles3/swap/kis_chunk_allocator.cpp
tiles3/swap/kis_memory_window.cpp
tiles3/swap/kis_swapped_data_store.cpp
tiles3/swap/kis_tile_data_swapper.cpp
kis_distance_information.cpp
kis_painter.cc
kis_marker_painter.cpp
kis_progress_updater.cpp
brushengine/kis_paint_information.cc
brushengine/kis_random_source.cpp
brushengine/kis_stroke_random_source.cpp
brushengine/kis_paintop.cc
brushengine/kis_paintop_factory.cpp
brushengine/kis_paintop_preset.cpp
brushengine/kis_paintop_registry.cc
brushengine/kis_paintop_settings.cpp
brushengine/kis_paintop_settings_update_proxy.cpp
brushengine/kis_no_size_paintop_settings.cpp
brushengine/kis_locked_properties.cc
brushengine/kis_locked_properties_proxy.cpp
brushengine/kis_locked_properties_server.cpp
brushengine/kis_paintop_config_widget.cpp
brushengine/kis_uniform_paintop_property.cpp
brushengine/kis_combo_based_paintop_property.cpp
brushengine/kis_slider_based_paintop_property.cpp
brushengine/kis_standard_uniform_properties_factory.cpp
commands/kis_deselect_global_selection_command.cpp
commands/kis_image_change_layers_command.cpp
commands/kis_image_command.cpp
commands/kis_image_set_projection_color_space_command.cpp
commands/kis_image_layer_add_command.cpp
commands/kis_image_layer_move_command.cpp
commands/kis_image_layer_remove_command.cpp
commands/kis_image_layer_remove_command_impl.cpp
commands/kis_image_lock_command.cpp
commands/kis_layer_command.cpp
commands/kis_node_command.cpp
commands/kis_node_compositeop_command.cpp
commands/kis_node_opacity_command.cpp
commands/kis_node_property_list_command.cpp
commands/kis_reselect_global_selection_command.cpp
commands/kis_set_global_selection_command.cpp
commands_new/kis_saved_commands.cpp
commands_new/kis_processing_command.cpp
commands_new/kis_image_resize_command.cpp
commands_new/kis_image_set_resolution_command.cpp
commands_new/kis_node_move_command2.cpp
commands_new/kis_set_layer_style_command.cpp
commands_new/kis_selection_move_command2.cpp
commands_new/kis_update_command.cpp
commands_new/kis_switch_current_time_command.cpp
commands_new/kis_change_projection_color_command.cpp
commands_new/kis_activate_selection_mask_command.cpp
processing/kis_do_nothing_processing_visitor.cpp
processing/kis_simple_processing_visitor.cpp
processing/kis_crop_processing_visitor.cpp
processing/kis_crop_selections_processing_visitor.cpp
processing/kis_transform_processing_visitor.cpp
processing/kis_mirror_processing_visitor.cpp
filter/kis_filter.cc
filter/kis_filter_configuration.cc
filter/kis_color_transformation_configuration.cc
filter/kis_filter_registry.cc
filter/kis_color_transformation_filter.cc
generator/kis_generator.cpp
generator/kis_generator_layer.cpp
generator/kis_generator_registry.cpp
floodfill/kis_fill_interval_map.cpp
floodfill/kis_scanline_fill.cpp
lazybrush/kis_min_cut_worker.cpp
lazybrush/kis_lazy_fill_tools.cpp
lazybrush/kis_multiway_cut.cpp
lazybrush/kis_colorize_mask.cpp
lazybrush/kis_colorize_stroke_strategy.cpp
+ KisDelayedUpdateNodeInterface.cpp
kis_adjustment_layer.cc
kis_selection_based_layer.cpp
kis_node_filter_interface.cpp
kis_base_accessor.cpp
kis_base_node.cpp
kis_base_processor.cpp
kis_bookmarked_configuration_manager.cc
kis_clone_info.cpp
kis_clone_layer.cpp
kis_colorspace_convert_visitor.cpp
kis_config_widget.cpp
kis_convolution_kernel.cc
kis_convolution_painter.cc
kis_gaussian_kernel.cpp
kis_cubic_curve.cpp
kis_default_bounds.cpp
kis_default_bounds_base.cpp
kis_effect_mask.cc
kis_fast_math.cpp
kis_fill_painter.cc
kis_filter_mask.cpp
kis_filter_strategy.cc
kis_transform_mask.cpp
kis_transform_mask_params_interface.cpp
kis_recalculate_transform_mask_job.cpp
kis_recalculate_generator_layer_job.cpp
kis_transform_mask_params_factory_registry.cpp
kis_safe_transform.cpp
kis_gradient_painter.cc
kis_gradient_shape_strategy.cpp
kis_cached_gradient_shape_strategy.cpp
kis_polygonal_gradient_shape_strategy.cpp
kis_iterator_ng.cpp
kis_async_merger.cpp
kis_merge_walker.cc
kis_updater_context.cpp
kis_update_job_item.cpp
kis_stroke_strategy_undo_command_based.cpp
kis_simple_stroke_strategy.cpp
kis_stroke_job_strategy.cpp
kis_stroke_strategy.cpp
kis_stroke.cpp
kis_strokes_queue.cpp
kis_simple_update_queue.cpp
kis_update_scheduler.cpp
kis_queues_progress_updater.cpp
kis_composite_progress_proxy.cpp
kis_sync_lod_cache_stroke_strategy.cpp
kis_lod_capable_layer_offset.cpp
kis_update_time_monitor.cpp
kis_group_layer.cc
kis_count_visitor.cpp
kis_histogram.cc
kis_image_interfaces.cpp
kis_image_animation_interface.cpp
kis_time_range.cpp
kis_node_graph_listener.cpp
kis_image.cc
kis_image_signal_router.cpp
kis_image_config.cpp
kis_projection_updates_filter.cpp
kis_suspend_projection_updates_stroke_strategy.cpp
kis_regenerate_frame_stroke_strategy.cpp
kis_switch_time_stroke_strategy.cpp
kis_crop_saved_extra_data.cpp
- kis_signal_compressor.cpp
- kis_signal_compressor_with_param.cpp
kis_thread_safe_signal_compressor.cpp
- kis_acyclic_signal_connector.cpp
kis_timed_signal_threshold.cpp
kis_layer.cc
kis_indirect_painting_support.cpp
kis_abstract_projection_plane.cpp
kis_layer_projection_plane.cpp
- kis_command_utils.cpp
kis_layer_utils.cpp
kis_mask_projection_plane.cpp
kis_projection_leaf.cpp
kis_mask.cc
kis_base_mask_generator.cpp
kis_rect_mask_generator.cpp
kis_circle_mask_generator.cpp
kis_gauss_circle_mask_generator.cpp
kis_gauss_rect_mask_generator.cpp
${__per_arch_circle_mask_generator_objs}
kis_curve_circle_mask_generator.cpp
kis_curve_rect_mask_generator.cpp
kis_math_toolbox.cpp
kis_memory_statistics_server.cpp
kis_name_server.cpp
kis_node.cpp
kis_node_facade.cpp
kis_node_progress_proxy.cpp
kis_busy_progress_indicator.cpp
kis_node_visitor.cpp
kis_paint_device.cc
kis_paint_device_debug_utils.cpp
kis_fixed_paint_device.cpp
kis_paint_layer.cc
kis_perspective_math.cpp
kis_pixel_selection.cpp
kis_processing_information.cpp
kis_properties_configuration.cc
kis_random_accessor_ng.cpp
kis_random_generator.cc
kis_random_sub_accessor.cpp
kis_wrapped_random_accessor.cpp
kis_selection.cc
kis_selection_mask.cpp
kis_update_outline_job.cpp
kis_update_selection_job.cpp
kis_serializable_configuration.cc
kis_transaction_data.cpp
kis_transform_worker.cc
kis_perspectivetransform_worker.cpp
bsplines/kis_bspline_1d.cpp
bsplines/kis_bspline_2d.cpp
bsplines/kis_nu_bspline_2d.cpp
kis_warptransform_worker.cc
kis_cage_transform_worker.cpp
kis_liquify_transform_worker.cpp
kis_green_coordinates_math.cpp
- kis_algebra_2d.cpp
kis_transparency_mask.cc
kis_inpaint_mask.cpp
- kis_undo_store.cpp
- kis_undo_stores.cpp
kis_undo_adapter.cpp
kis_macro_based_undo_store.cpp
kis_surrogate_undo_adapter.cpp
kis_legacy_undo_adapter.cpp
kis_post_execution_undo_adapter.cpp
kis_processing_visitor.cpp
kis_processing_applicator.cpp
krita_utils.cpp
kis_outline_generator.cpp
kis_layer_composition.cpp
kis_selection_filters.cpp
KisProofingConfiguration.h
metadata/kis_meta_data_entry.cc
metadata/kis_meta_data_filter.cc
metadata/kis_meta_data_filter_p.cc
metadata/kis_meta_data_filter_registry.cc
metadata/kis_meta_data_filter_registry_model.cc
metadata/kis_meta_data_io_backend.cc
metadata/kis_meta_data_merge_strategy.cc
metadata/kis_meta_data_merge_strategy_p.cc
metadata/kis_meta_data_merge_strategy_registry.cc
metadata/kis_meta_data_parser.cc
metadata/kis_meta_data_schema.cc
metadata/kis_meta_data_schema_registry.cc
metadata/kis_meta_data_store.cc
metadata/kis_meta_data_type_info.cc
metadata/kis_meta_data_validator.cc
metadata/kis_meta_data_value.cc
recorder/kis_action_recorder.cc
recorder/kis_macro.cc
recorder/kis_macro_player.cc
recorder/kis_node_query_path.cc
recorder/kis_play_info.cc
recorder/kis_recorded_action.cc
recorder/kis_recorded_action_factory_registry.cc
recorder/kis_recorded_action_load_context.cpp
recorder/kis_recorded_action_save_context.cpp
recorder/kis_recorded_filter_action.cpp
recorder/kis_recorded_fill_paint_action.cpp
recorder/kis_recorded_node_action.cc
recorder/kis_recorded_paint_action.cpp
recorder/kis_recorded_path_paint_action.cpp
recorder/kis_recorded_shape_paint_action.cpp
kis_keyframe.cpp
kis_keyframe_channel.cpp
kis_keyframe_commands.cpp
kis_scalar_keyframe_channel.cpp
kis_raster_keyframe_channel.cpp
kis_onion_skin_compositor.cpp
kis_onion_skin_cache.cpp
kis_idle_watcher.cpp
kis_psd_layer_style.cpp
kis_layer_properties_icons.cpp
layerstyles/kis_multiple_projection.cpp
layerstyles/kis_layer_style_filter.cpp
layerstyles/kis_layer_style_filter_environment.cpp
layerstyles/kis_layer_style_filter_projection_plane.cpp
layerstyles/kis_layer_style_projection_plane.cpp
layerstyles/kis_ls_drop_shadow_filter.cpp
layerstyles/kis_ls_satin_filter.cpp
layerstyles/kis_ls_stroke_filter.cpp
layerstyles/kis_ls_bevel_emboss_filter.cpp
layerstyles/kis_ls_overlay_filter.cpp
layerstyles/kis_ls_utils.cpp
layerstyles/gimp_bump_map.cpp
KisProofingConfiguration.cpp
)
set(einspline_SRCS
3rdparty/einspline/bspline_create.cpp
3rdparty/einspline/bspline_data.cpp
3rdparty/einspline/multi_bspline_create.cpp
3rdparty/einspline/nubasis.cpp
3rdparty/einspline/nubspline_create.cpp
3rdparty/einspline/nugrid.cpp
)
add_library(kritaimage SHARED ${kritaimage_LIB_SRCS} ${einspline_SRCS})
generate_export_header(kritaimage BASE_NAME kritaimage)
target_link_libraries(kritaimage
PUBLIC
kritaversion
kritawidgets
kritaglobal kritapsd
kritaodf kritapigment
- kritaundo2
+ kritacommand
kritawidgetutils
Qt5::Concurrent
)
target_link_libraries(kritaimage PUBLIC ${Boost_SYSTEM_LIBRARY})
if(OPENEXR_FOUND)
target_link_libraries(kritaimage PUBLIC ${OPENEXR_LIBRARIES})
endif()
if(FFTW3_FOUND)
target_link_libraries(kritaimage PRIVATE ${FFTW3_LIBRARIES})
endif()
if(HAVE_VC)
target_link_libraries(kritaimage PUBLIC ${Vc_LIBRARIES})
endif()
if (NOT GSL_FOUND)
message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Shaped Gradients might be non-normalized! Please install GSL library.")
else ()
target_link_libraries(kritaimage PRIVATE ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES})
endif ()
target_include_directories(kritaimage
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/brushengine>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/filter>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/generator>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/layerstyles>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/metadata>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/processing>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/recorder>
)
set_target_properties(kritaimage PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaimage ${INSTALL_TARGETS_DEFAULT_ARGS})
########### install schemas #############
install( FILES
metadata/schemas/dc.schema
metadata/schemas/exif.schema
metadata/schemas/tiff.schema
metadata/schemas/mkn.schema
metadata/schemas/xmp.schema
metadata/schemas/xmpmm.schema
metadata/schemas/xmprights.schema
DESTINATION ${DATA_INSTALL_DIR}/krita/metadata/schemas)
diff --git a/libs/image/KisDelayedUpdateNodeInterface.cpp b/libs/image/KisDelayedUpdateNodeInterface.cpp
new file mode 100644
index 0000000000..f5fca71de4
--- /dev/null
+++ b/libs/image/KisDelayedUpdateNodeInterface.cpp
@@ -0,0 +1,24 @@
+/*
+ * 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 "KisDelayedUpdateNodeInterface.h"
+
+KisDelayedUpdateNodeInterface::~KisDelayedUpdateNodeInterface()
+{
+}
+
diff --git a/libs/image/KisDelayedUpdateNodeInterface.h b/libs/image/KisDelayedUpdateNodeInterface.h
new file mode 100644
index 0000000000..13af8d18f5
--- /dev/null
+++ b/libs/image/KisDelayedUpdateNodeInterface.h
@@ -0,0 +1,42 @@
+/*
+ * 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 KISDELAYEDUPDATENODEINTERFACE_H
+#define KISDELAYEDUPDATENODEINTERFACE_H
+
+#include "kritaimage_export.h"
+
+
+/**
+ * @brief The KisDelayedUpdateNodeInterface class is an interface for
+ * nodes that dealy their real updates with KisSignalCompressor. Some
+ * operations need explicit regeneration before they can proceed.
+ */
+class KRITAIMAGE_EXPORT KisDelayedUpdateNodeInterface
+{
+public:
+ virtual ~KisDelayedUpdateNodeInterface();
+
+ /**
+ * @brief forceUpdateTimedNode forrces the node to regenerate its project. The update might
+ * be asynchronous, so you chould call image->waitForDone() after that.
+ */
+ virtual void forceUpdateTimedNode() = 0;
+};
+
+#endif // KISDELAYEDUPDATENODEINTERFACE_H
diff --git a/libs/image/brushengine/kis_paint_information.cc b/libs/image/brushengine/kis_paint_information.cc
index 6e6eb8e711..2a6deea6a3 100644
--- a/libs/image/brushengine/kis_paint_information.cc
+++ b/libs/image/brushengine/kis_paint_information.cc
@@ -1,570 +1,572 @@
/*
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <brushengine/kis_paint_information.h>
#include <QDomElement>
#include <QScopedPointer>
+#include <Eigen/Core>
+
#include "kis_paintop.h"
#include "kis_algebra_2d.h"
#include "kis_lod_transform.h"
#include <kis_dom_utils.h>
struct KisPaintInformation::Private {
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
Private(const QPointF & pos_,
qreal pressure_,
qreal xTilt_, qreal yTilt_,
qreal rotation_,
qreal tangentialPressure_,
qreal perspective_,
qreal time_,
qreal speed_,
bool isHoveringMode_)
:
pos(pos_),
pressure(pressure_),
xTilt(xTilt_),
yTilt(yTilt_),
rotation(rotation_),
tangentialPressure(tangentialPressure_),
perspective(perspective_),
time(time_),
speed(speed_),
isHoveringMode(isHoveringMode_),
randomSource(0),
currentDistanceInfo(0),
levelOfDetail(0)
{
}
~Private() {
KIS_ASSERT_RECOVER_NOOP(!currentDistanceInfo);
}
Private(const Private &rhs) {
copy(rhs);
}
Private& operator=(const Private &rhs) {
copy(rhs);
return *this;
}
void copy(const Private &rhs) {
pos = rhs.pos;
pressure = rhs.pressure;
xTilt = rhs.xTilt;
yTilt = rhs.yTilt;
rotation = rhs.rotation;
tangentialPressure = rhs.tangentialPressure;
perspective = rhs.perspective;
time = rhs.time;
speed = rhs.speed;
isHoveringMode = rhs.isHoveringMode;
randomSource = rhs.randomSource;
currentDistanceInfo = rhs.currentDistanceInfo;
canvasRotation = rhs.canvasRotation;
canvasMirroredH = rhs.canvasMirroredH;
if (rhs.drawingAngleOverride) {
drawingAngleOverride.reset(new qreal(*rhs.drawingAngleOverride));
}
levelOfDetail = rhs.levelOfDetail;
}
QPointF pos;
qreal pressure;
qreal xTilt;
qreal yTilt;
qreal rotation;
qreal tangentialPressure;
qreal perspective;
qreal time;
qreal speed;
bool isHoveringMode;
KisRandomSourceSP randomSource;
int canvasRotation;
bool canvasMirroredH;
QScopedPointer<qreal> drawingAngleOverride;
KisDistanceInformation *currentDistanceInfo;
int levelOfDetail;
void registerDistanceInfo(KisDistanceInformation *di) {
currentDistanceInfo = di;
}
void unregisterDistanceInfo() {
currentDistanceInfo = 0;
}
};
KisPaintInformation::DistanceInformationRegistrar::
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo)
: p(_p)
{
p->d->registerDistanceInfo(distanceInfo);
}
KisPaintInformation::DistanceInformationRegistrar::
~DistanceInformationRegistrar()
{
p->d->unregisterDistanceInfo();
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective,
time,
speed,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation)
: d(new Private(pos,
pressure,
xTilt, yTilt,
rotation,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const QPointF &pos,
qreal pressure)
: d(new Private(pos,
pressure,
0.0, 0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
false))
{
}
KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs)
: d(new Private(*rhs.d))
{
}
void KisPaintInformation::operator=(const KisPaintInformation & rhs)
{
*d = *rhs.d;
}
KisPaintInformation::~KisPaintInformation()
{
delete d;
}
bool KisPaintInformation::isHoveringMode() const
{
return d->isHoveringMode;
}
KisPaintInformation
KisPaintInformation::createHoveringModeInfo(const QPointF &pos,
qreal pressure,
qreal xTilt, qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal speed,
int canvasrotation,
bool canvasMirroredH)
{
KisPaintInformation info(pos,
pressure,
xTilt, yTilt,
rotation,
tangentialPressure,
perspective, 0, speed);
info.d->isHoveringMode = true;
info.d->canvasRotation = canvasrotation;
info.d->canvasMirroredH = canvasMirroredH;
return info;
}
int KisPaintInformation::canvasRotation() const
{
return d->canvasRotation;
}
void KisPaintInformation::setCanvasRotation(int rotation)
{
if (rotation < 0) {
d->canvasRotation= 360- abs(rotation % 360);
} else {
d->canvasRotation= rotation % 360;
}
}
bool KisPaintInformation::canvasMirroredH() const
{
return d->canvasMirroredH;
}
void KisPaintInformation::setCanvasHorizontalMirrorState(bool mir)
{
d->canvasMirroredH = mir;
}
void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
{
// hovering mode infos are not supposed to be saved
KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode);
e.setAttribute("pointX", QString::number(pos().x(), 'g', 15));
e.setAttribute("pointY", QString::number(pos().y(), 'g', 15));
e.setAttribute("pressure", QString::number(pressure(), 'g', 15));
e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15));
e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15));
e.setAttribute("rotation", QString::number(rotation(), 'g', 15));
e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
e.setAttribute("time", QString::number(d->time, 'g', 15));
e.setAttribute("speed", QString::number(d->speed, 'g', 15));
}
KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
{
qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0")));
qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0")));
qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0")));
qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0")));
qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0")));
qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0")));
qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0")));
qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0")));
qreal time = KisDomUtils::toDouble(e.attribute("time", "0"));
qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0"));
return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
rotation, tangentialPressure, perspective, time, speed);
}
const QPointF& KisPaintInformation::pos() const
{
return d->pos;
}
void KisPaintInformation::setPos(const QPointF& p)
{
d->pos = p;
}
qreal KisPaintInformation::pressure() const
{
return d->pressure;
}
void KisPaintInformation::setPressure(qreal p)
{
d->pressure = p;
}
qreal KisPaintInformation::xTilt() const
{
return d->xTilt;
}
qreal KisPaintInformation::yTilt() const
{
return d->yTilt;
}
void KisPaintInformation::overrideDrawingAngle(qreal angle)
{
d->drawingAngleOverride.reset(new qreal(angle));
}
qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const
{
if (d->drawingAngleOverride) return *d->drawingAngleOverride;
QVector2D diff(pos() - distance.lastPosition());
return atan2(diff.y(), diff.x());
}
KisPaintInformation::DistanceInformationRegistrar
KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance)
{
return DistanceInformationRegistrar(this, distance);
}
qreal KisPaintInformation::drawingAngle() const
{
if (d->drawingAngleOverride) return *d->drawingAngleOverride;
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingAngle()" << "Cannot access Distance Info last dab data";
return 0.0;
}
if (d->currentDistanceInfo->hasLockedDrawingAngle()) {
return d->currentDistanceInfo->lockedDrawingAngle();
}
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
return atan2(diff.y(), diff.x());
}
void KisPaintInformation::lockCurrentDrawingAngle(qreal alpha_unused) const
{
Q_UNUSED(alpha_unused);
if (!d->currentDistanceInfo) {
warnKrita << "KisPaintInformation::lockCurrentDrawingAngle()" << "Cannot access Distance Info last dab data";
return;
}
const QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
const qreal angle = atan2(diff.y(), diff.x());
qreal newAngle = angle;
if (d->currentDistanceInfo->hasLockedDrawingAngle()) {
const qreal stabilizingCoeff = 20.0;
const qreal dist = stabilizingCoeff * d->currentDistanceInfo->currentSpacing().scalarApprox();
const qreal alpha = qMax(0.0, dist - d->currentDistanceInfo->scalarDistanceApprox()) / dist;
const qreal oldAngle = d->currentDistanceInfo->lockedDrawingAngle();
if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) {
newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle;
} else {
newAngle = oldAngle;
}
}
d->currentDistanceInfo->setLockedDrawingAngle(newAngle);
}
QPointF KisPaintInformation::drawingDirectionVector() const
{
if (d->drawingAngleOverride) {
qreal angle = *d->drawingAngleOverride;
return QPointF(cos(angle), sin(angle));
}
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingDirectionVector()" << "Cannot access Distance Info last dab data";
return QPointF(1.0, 0.0);
}
QPointF diff(pos() - d->currentDistanceInfo->lastPosition());
return KisAlgebra2D::normalize(diff);
}
qreal KisPaintInformation::drawingDistance() const
{
if (!d->currentDistanceInfo || !d->currentDistanceInfo->hasLastDabInformation()) {
warnKrita << "KisPaintInformation::drawingDistance()" << "Cannot access Distance Info last dab data";
return 1.0;
}
QVector2D diff(pos() - d->currentDistanceInfo->lastPosition());
qreal length = diff.length();
if (d->levelOfDetail) {
length *= KisLodTransform::lodToInvScale(d->levelOfDetail);
}
return length;
}
qreal KisPaintInformation::drawingSpeed() const
{
return d->speed;
}
qreal KisPaintInformation::rotation() const
{
return d->rotation;
}
qreal KisPaintInformation::tangentialPressure() const
{
return d->tangentialPressure;
}
qreal KisPaintInformation::perspective() const
{
return d->perspective;
}
qreal KisPaintInformation::currentTime() const
{
return d->time;
}
KisRandomSourceSP KisPaintInformation::randomSource() const
{
if (!d->randomSource) {
d->randomSource = new KisRandomSource();
}
return d->randomSource;
}
void KisPaintInformation::setRandomSource(KisRandomSourceSP value)
{
d->randomSource = value;
}
void KisPaintInformation::setLevelOfDetail(int levelOfDetail) const
{
d->levelOfDetail = levelOfDetail;
}
QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
{
#ifdef NDEBUG
Q_UNUSED(info);
#else
dbg.nospace() << "Position: " << info.pos();
dbg.nospace() << ", Pressure: " << info.pressure();
dbg.nospace() << ", X Tilt: " << info.xTilt();
dbg.nospace() << ", Y Tilt: " << info.yTilt();
dbg.nospace() << ", Rotation: " << info.rotation();
dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure();
dbg.nospace() << ", Perspective: " << info.perspective();
dbg.nospace() << ", Drawing Angle: " << info.drawingAngle();
dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed();
dbg.nospace() << ", Drawing Distance: " << info.drawingDistance();
dbg.nospace() << ", Time: " << info.currentTime();
#endif
return dbg.space();
}
KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi)
{
QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos();
KisPaintInformation result(pt,
basePi.pressure(),
basePi.xTilt(),
basePi.yTilt(),
basePi.rotation(),
basePi.tangentialPressure(),
basePi.perspective(),
basePi.currentTime(),
basePi.drawingSpeed());
result.setRandomSource(basePi.randomSource());
return result;
}
KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
return mix(pt, t, pi1, pi2);
}
KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
{
qreal pressure = (1 - t) * pi1.pressure() + t * pi2.pressure();
qreal xTilt = (1 - t) * pi1.xTilt() + t * pi2.xTilt();
qreal yTilt = (1 - t) * pi1.yTilt() + t * pi2.yTilt();
qreal rotation = pi1.rotation();
if (pi1.rotation() != pi2.rotation()) {
qreal a1 = kisDegreesToRadians(pi1.rotation());
qreal a2 = kisDegreesToRadians(pi2.rotation());
qreal distance = shortestAngularDistance(a2, a1);
rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2));
}
qreal tangentialPressure = (1 - t) * pi1.tangentialPressure() + t * pi2.tangentialPressure();
qreal perspective = (1 - t) * pi1.perspective() + t * pi2.perspective();
qreal time = (1 - t) * pi1.currentTime() + t * pi2.currentTime();
qreal speed = (1 - t) * pi1.drawingSpeed() + t * pi2.drawingSpeed();
KisPaintInformation result(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed);
KIS_ASSERT_RECOVER_NOOP(pi1.isHoveringMode() == pi2.isHoveringMode());
result.d->isHoveringMode = pi1.isHoveringMode();
result.d->levelOfDetail = pi1.d->levelOfDetail;
result.d->randomSource = pi1.d->randomSource;
result.d->canvasRotation = pi2.canvasRotation();
result.d->canvasMirroredH = pi2.canvasMirroredH();
return result;
}
qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize)
{
qreal xTilt = info.xTilt();
qreal yTilt = info.yTilt();
// radians -PI, PI
qreal tiltDirection = atan2(-xTilt, yTilt);
// if normalize is true map to 0.0..1.0
return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection;
}
qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize)
{
qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0));
qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0));
qreal e;
if (fabs(xTilt) > fabs(yTilt)) {
e = sqrt(qreal(1.0) + yTilt * yTilt);
} else {
e = sqrt(qreal(1.0) + xTilt * xTilt);
}
qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e;
qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI]
// mapping to 0.0..1.0 if normalize is true
return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation;
}
diff --git a/libs/image/brushengine/kis_paint_information.h b/libs/image/brushengine/kis_paint_information.h
index 15d30b9e04..06073bbedc 100644
--- a/libs/image/brushengine/kis_paint_information.h
+++ b/libs/image/brushengine/kis_paint_information.h
@@ -1,272 +1,271 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_PAINT_INFORMATION_
#define _KIS_PAINT_INFORMATION_
#include <kis_debug.h>
#include <QTime>
#include "kis_global.h"
-#include "kis_vec.h"
#include "kritaimage_export.h"
#include <kis_distance_information.h>
#include "kis_random_source.h"
class QDomDocument;
class QDomElement;
class KisDistanceInformation;
/**
* KisPaintInformation contains information about the input event that
* causes the brush action to happen to the brush engine's paint
* methods.
*
* XXX: we directly pass the KoPointerEvent x and y tilt to
* KisPaintInformation, and their range is -60 to +60!
*
* @param pos: the position of the paint event in subpixel accuracy
* @param pressure: the pressure of the stylus
* @param xTilt: the angle between the device (a pen, for example) and
* the perpendicular in the direction of the x axis. Positive values
* are towards the bottom of the tablet. The angle is within the range
* 0 to 1
* @param yTilt: the angle between the device (a pen, for example) and
* the perpendicular in the direction of the y axis. Positive values
* are towards the bottom of the tablet. The angle is within the range
* 0 to .
* @param movement: current position minus the last position of the call to paintAt
* @param rotation
* @param tangentialPressure
* @param perspective
**/
class KRITAIMAGE_EXPORT KisPaintInformation
{
public:
/**
* Note, that this class is relied on the compiler optimization
* of the return value. So if it doesn't work for some reason,
* please implement a proper copy c-tor
*/
class KRITAIMAGE_EXPORT DistanceInformationRegistrar
{
public:
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo);
~DistanceInformationRegistrar();
private:
KisPaintInformation *p;
};
public:
/**
* Create a new KisPaintInformation object.
*/
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation,
qreal tangentialPressure,
qreal perspective,
qreal time,
qreal speed);
KisPaintInformation(const QPointF & pos,
qreal pressure,
qreal xTilt,
qreal yTilt,
qreal rotation);
KisPaintInformation(const QPointF & pos = QPointF(),
qreal pressure = PRESSURE_DEFAULT);
KisPaintInformation(const KisPaintInformation& rhs);
void operator=(const KisPaintInformation& rhs);
~KisPaintInformation();
template <class PaintOp>
void paintAt(PaintOp &op, KisDistanceInformation *distanceInfo) {
KisSpacingInformation spacingInfo;
{
DistanceInformationRegistrar r = registerDistanceInformation(distanceInfo);
spacingInfo = op.paintAt(*this);
}
distanceInfo->registerPaintedDab(*this, spacingInfo);
}
const QPointF& pos() const;
void setPos(const QPointF& p);
/// The pressure of the value (from 0.0 to 1.0)
qreal pressure() const;
/// Set the pressure
void setPressure(qreal p);
/// The tilt of the pen on the horizontal axis (from 0.0 to 1.0)
qreal xTilt() const;
/// The tilt of the pen on the vertical axis (from 0.0 to 1.0)
qreal yTilt() const;
/// XXX !!! :-| Please add dox!
void overrideDrawingAngle(qreal angle);
/// XXX !!! :-| Please add dox!
qreal drawingAngleSafe(const KisDistanceInformation &distance) const;
/// XXX !!! :-| Please add dox!
DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance);
/**
* Current brush direction computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingAngle() const;
/**
* Lock current drawing angle for the rest of the stroke. If some
* value has already been locked, \p alpha shown the coefficient
* with which the new velue should be blended in.
*/
void lockCurrentDrawingAngle(qreal alpha) const;
/**
* Current brush direction vector computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
QPointF drawingDirectionVector() const;
/**
* Current brush speed computed from the cursor movement
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingSpeed() const;
/**
* Current distance from the previous dab
*
* WARNING: this method is available *only* inside paintAt() call,
* that is when the distance information is registered.
*/
qreal drawingDistance() const;
/// rotation as given by the tablet event
qreal rotation() const;
/// tangential pressure (i.e., rate for an airbrush device)
qreal tangentialPressure() const;
/// reciprocal of distance on the perspective grid
qreal perspective() const;
/// Number of ms since the beginning of the stroke
qreal currentTime() const;
// random source for generating in-stroke effects
KisRandomSourceSP randomSource() const;
// the stroke should initialize random source of all the used
// paint info objects, otherwise it shows a warning
void setRandomSource(KisRandomSourceSP value);
// set level of detail which info object has been generated for
void setLevelOfDetail(int levelOfDetail) const;
/**
* The paint information may be generated not only during real
* stroke when the actual painting is happening, but also when the
* cursor is hovering the canvas. In this mode some of the sensors
* work a bit differently. The most outstanding example is Fuzzy
* sensor, which returns unit value in this mode, otherwise it is
* too irritating for a user.
*
* This value is true only for paint information objects created with
* createHoveringModeInfo() constructor.
*
* \see createHoveringModeInfo()
*/
bool isHoveringMode() const;
/**
* Create a fake info object with isHoveringMode() property set to
* true.
*
* \see isHoveringMode()
*/
static KisPaintInformation createHoveringModeInfo(const QPointF &pos,
qreal pressure = PRESSURE_DEFAULT,
qreal xTilt = 0.0, qreal yTilt = 0.0,
qreal rotation = 0.0,
qreal tangentialPressure = 0.0,
qreal perspective = 1.0,
qreal speed = 0.0,
int canvasrotation = 0,
bool canvasMirroredH = false);
/**
*Returns the canvas rotation if that has been given to the kispaintinformation.
*/
int canvasRotation() const;
/**
*set the canvas rotation.
*/
void setCanvasRotation(int rotation);
/*
*Whether the canvas is mirrored for the paint-operation.
*/
bool canvasMirroredH() const;
/*
*Set whether the canvas is mirrored for the paint-operation.
*/
void setCanvasHorizontalMirrorState(bool mir);
void toXML(QDomDocument&, QDomElement&) const;
static KisPaintInformation fromXML(const QDomElement&);
/// (1-t) * p1 + t * p2
static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi);
static KisPaintInformation mix(const QPointF& p, qreal t, const KisPaintInformation& p1, const KisPaintInformation& p2);
static KisPaintInformation mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2);
static qreal tiltDirection(const KisPaintInformation& info, bool normalize = true);
static qreal tiltElevation(const KisPaintInformation& info, qreal maxTiltX = 60.0, qreal maxTiltY = 60.0, bool normalize = true);
private:
struct Private;
Private* const d;
};
KRITAIMAGE_EXPORT QDebug operator<<(QDebug debug, const KisPaintInformation& info);
#endif
diff --git a/libs/image/commands/kis_node_property_list_command.cpp b/libs/image/commands/kis_node_property_list_command.cpp
index 070df0762e..801fcdf2ad 100644
--- a/libs/image/commands/kis_node_property_list_command.cpp
+++ b/libs/image/commands/kis_node_property_list_command.cpp
@@ -1,121 +1,147 @@
/*
* Copyright (c) 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 <klocalizedstring.h>
#include "kis_node.h"
#include "kis_layer.h"
#include "kis_image.h"
#include "kis_selection_mask.h"
#include "kis_paint_layer.h"
#include "commands/kis_node_property_list_command.h"
#include "kis_undo_adapter.h"
-
+#include "kis_layer_properties_icons.h"
KisNodePropertyListCommand::KisNodePropertyListCommand(KisNodeSP node, KisBaseNode::PropertyList newPropertyList)
: KisNodeCommand(kundo2_i18n("Property Changes"), node),
m_newPropertyList(newPropertyList),
m_oldPropertyList(node->sectionModelProperties())
/**
* TODO instead of "Property Changes" check which property
* has been changed and display either lock/unlock, visible/hidden
* or "Property Changes" (this require new strings)
*/
{
}
void KisNodePropertyListCommand::redo()
{
m_node->setSectionModelProperties(m_newPropertyList);
doUpdate(m_oldPropertyList, m_newPropertyList);
}
void KisNodePropertyListCommand::undo()
{
m_node->setSectionModelProperties(m_oldPropertyList);
doUpdate(m_newPropertyList, m_oldPropertyList);
}
+bool checkOnionSkinChanged(const KisBaseNode::PropertyList &oldPropertyList,
+ const KisBaseNode::PropertyList &newPropertyList)
+{
+ if (oldPropertyList.size() != newPropertyList.size()) return false;
+
+ bool oldOnionSkinsValue = false;
+ bool newOnionSkinsValue = false;
+
+ Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) {
+ if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) {
+ oldOnionSkinsValue = prop.state.toBool();
+ }
+ }
+
+ Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) {
+ if (prop.id == KisLayerPropertiesIcons::onionSkins.id()) {
+ newOnionSkinsValue = prop.state.toBool();
+ }
+ }
+
+ return oldOnionSkinsValue != newOnionSkinsValue;
+}
+
+
void KisNodePropertyListCommand::doUpdate(const KisBaseNode::PropertyList &oldPropertyList,
const KisBaseNode::PropertyList &newPropertyList)
{
bool oldPassThroughValue = false;
bool newPassThroughValue = false;
Q_FOREACH (const KisBaseNode::Property &prop, oldPropertyList) {
if (prop.name == i18n("Pass Through")) {
oldPassThroughValue = prop.state.toBool();
}
}
Q_FOREACH (const KisBaseNode::Property &prop, newPropertyList) {
if (prop.name == i18n("Pass Through")) {
newPassThroughValue = prop.state.toBool();
}
}
if (oldPassThroughValue && !newPassThroughValue) {
KisLayerSP layer(qobject_cast<KisLayer*>(m_node.data()));
KisImageSP image = layer->image().toStrongRef();
if (image) {
image->refreshGraphAsync(layer);
}
} else if (m_node->parent() && !oldPassThroughValue && newPassThroughValue) {
KisLayerSP layer(qobject_cast<KisLayer*>(m_node->parent().data()));
KisImageSP image = layer->image().toStrongRef();
if (image) {
image->refreshGraphAsync(layer);
}
+ } else if (checkOnionSkinChanged(oldPropertyList, newPropertyList)) {
+ m_node->setDirtyDontResetAnimationCache();
} else {
m_node->setDirty(); // TODO check if visibility was changed or not
}
}
void KisNodePropertyListCommand::setNodePropertiesNoUndo(KisNodeSP node, KisImageSP image, PropertyList proplist)
{
bool undo = true;
Q_FOREACH (const KisBaseNode::Property &prop, proplist) {
if (prop.isInStasis) undo = false;
if (prop.name == i18n("Visible") && node->visible() != prop.state.toBool()) undo = false;
if (prop.name == i18n("Locked") && node->userLocked() != prop.state.toBool()) undo = false;
if (prop.name == i18n("Active")) {
if (KisSelectionMask *m = dynamic_cast<KisSelectionMask*>(node.data())) {
if (m->active() != prop.state.toBool()) {
undo = false;
}
}
}
if (prop.name == i18n("Alpha Locked")) {
if (KisPaintLayer* l = dynamic_cast<KisPaintLayer*>(node.data())) {
if (l->alphaLocked() != prop.state.toBool()) {
undo = false;
}
}
}
}
QScopedPointer<KUndo2Command> cmd(new KisNodePropertyListCommand(node, proplist));
if (undo) {
image->undoAdapter()->addCommand(cmd.take());
}
else {
image->setModified();
cmd->redo();
}
}
diff --git a/libs/image/kis_acyclic_signal_connector.cpp b/libs/image/kis_acyclic_signal_connector.cpp
deleted file mode 100644
index e1d620d29b..0000000000
--- a/libs/image/kis_acyclic_signal_connector.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * 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_acyclic_signal_connector.h"
-
-#include "kis_debug.h"
-
-
-KisAcyclicSignalConnector::KisAcyclicSignalConnector(QObject *parent)
- : QObject(parent),
- m_signalsBlocked(0)
-{
-}
-
-void KisAcyclicSignalConnector::connectForwardDouble(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(forwardSlotDouble(double)));
- connect(this, SIGNAL(forwardSignalDouble(double)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectBackwardDouble(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(backwardSlotDouble(double)));
- connect(this, SIGNAL(backwardSignalDouble(double)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectForwardInt(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(forwardSlotInt(int)));
- connect(this, SIGNAL(forwardSignalInt(int)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectBackwardInt(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(backwardSlotInt(int)));
- connect(this, SIGNAL(backwardSignalInt(int)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectForwardBool(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(forwardSlotBool(bool)));
- connect(this, SIGNAL(forwardSignalBool(bool)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectBackwardBool(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(backwardSlotBool(bool)));
- connect(this, SIGNAL(backwardSignalBool(bool)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectForwardVoid(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(forwardSlotVoid()));
- connect(this, SIGNAL(forwardSignalVoid()), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectBackwardVoid(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(backwardSlotVoid()));
- connect(this, SIGNAL(backwardSignalVoid()), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectForwardVariant(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
-
- connect(sender, signal, this, SLOT(forwardSlotVariant(const QVariant&)));
- connect(this, SIGNAL(forwardSignalVariant(const QVariant&)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::connectBackwardVariant(QObject *sender, const char *signal,
- QObject *receiver, const char *method)
-{
- connect(sender, signal, this, SLOT(backwardSlotVariant(const QVariant&)));
- connect(this, SIGNAL(backwardSignalVariant(const QVariant&)), receiver, method);
-}
-
-void KisAcyclicSignalConnector::forwardSlotDouble(double value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit forwardSignalDouble(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::backwardSlotDouble(double value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit backwardSignalDouble(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::forwardSlotInt(int value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit forwardSignalInt(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::backwardSlotInt(int value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit backwardSignalInt(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::forwardSlotBool(bool value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit forwardSignalBool(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::backwardSlotBool(bool value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit backwardSignalBool(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::forwardSlotVoid()
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit forwardSignalVoid();
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::backwardSlotVoid()
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit backwardSignalVoid();
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::forwardSlotVariant(const QVariant &value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit forwardSignalVariant(value);
- m_signalsBlocked--;
-}
-
-void KisAcyclicSignalConnector::backwardSlotVariant(const QVariant &value)
-{
- if (m_signalsBlocked) return;
-
- m_signalsBlocked++;
- emit backwardSignalVariant(value);
- m_signalsBlocked--;
-}
diff --git a/libs/image/kis_acyclic_signal_connector.h b/libs/image/kis_acyclic_signal_connector.h
deleted file mode 100644
index 84b2d124e0..0000000000
--- a/libs/image/kis_acyclic_signal_connector.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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_ACYCLIC_SIGNAL_CONNECTOR_H
-#define __KIS_ACYCLIC_SIGNAL_CONNECTOR_H
-
-#include <QObject>
-#include "kritaimage_export.h"
-
-
-/**
- * A special class for connecting UI elements to manager classes.
- * It allows to avoid direct calling blockSignals() for the sender UI
- * element all the time. This is the most important when the measured
- * value can be changed not only by the user through the UI, but also
- * by the manager according to some internal rules.
- *
- * Example:
- *
- * Suppose we have the following connections:
- *
- * 1) QDoubleSpinBox::valueChanged(double) -> Manager::slotSetValue(double)
- * 2) Manager::valueChanged(double) -> QDoubleSpinBox::setValue(double)
- *
- * Now if the manager decides to change/correct the value, the spinbox
- * will go into an infinite loop.
- *
- * See an example in KisToolCropConfigWidget.
- */
-
-class KRITAIMAGE_EXPORT KisAcyclicSignalConnector : public QObject
-{
- Q_OBJECT
-public:
- KisAcyclicSignalConnector(QObject *parent);
-
- void connectForwardDouble(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectBackwardDouble(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectForwardInt(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectBackwardInt(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectForwardBool(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectBackwardBool(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectForwardVoid(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectBackwardVoid(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectForwardVariant(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
- void connectBackwardVariant(QObject *sender, const char *signal,
- QObject *receiver, const char *method);
-
-private Q_SLOTS:
- void forwardSlotDouble(double value);
- void backwardSlotDouble(double value);
-
- void forwardSlotInt(int value);
- void backwardSlotInt(int value);
-
- void forwardSlotBool(bool value);
- void backwardSlotBool(bool value);
-
- void forwardSlotVoid();
- void backwardSlotVoid();
-
- void forwardSlotVariant(const QVariant &value);
- void backwardSlotVariant(const QVariant &value);
-
-Q_SIGNALS:
- void forwardSignalDouble(double value);
- void backwardSignalDouble(double value);
-
- void forwardSignalInt(int value);
- void backwardSignalInt(int value);
-
- void forwardSignalBool(bool value);
- void backwardSignalBool(bool value);
-
- void forwardSignalVoid();
- void backwardSignalVoid();
-
- void forwardSignalVariant(const QVariant &value);
- void backwardSignalVariant(const QVariant &value);
-
-private:
- int m_signalsBlocked;
-};
-
-#endif /* __KIS_ACYCLIC_SIGNAL_CONNECTOR_H */
diff --git a/libs/image/kis_algebra_2d.cpp b/libs/image/kis_algebra_2d.cpp
deleted file mode 100644
index 5c92866b6a..0000000000
--- a/libs/image/kis_algebra_2d.cpp
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
- * 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_algebra_2d.h"
-
-#include <QPainterPath>
-#include <kis_debug.h>
-#include "krita_utils.h"
-
-#define SANITY_CHECKS
-
-namespace KisAlgebra2D {
-
-void adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt)
-{
- const int numPoints = poly.size();
- for (int i = 0; i < numPoints; i++) {
- int nextI = i + 1;
- if (nextI >= numPoints) {
- nextI = 0;
- }
-
- const QPointF &p0 = poly[i];
- const QPointF &p1 = poly[nextI];
-
- QPointF edge = p1 - p0;
-
- qreal cross = crossProduct(edge, *pt - p0)
- / (0.5 * edge.manhattanLength());
-
- if (cross < 1.0 &&
- isInRange(pt->x(), p0.x(), p1.x()) &&
- isInRange(pt->y(), p0.y(), p1.y())) {
-
- QPointF salt = 1.0e-3 * inwardUnitNormal(edge, polygonDirection);
-
- QPointF adjustedPoint = *pt + salt;
-
- // in case the polygon is self-intersecting, polygon direction
- // might not help
- if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
- adjustedPoint = *pt - salt;
-
-#ifdef SANITY_CHECKS
- if (kisDistanceToLine(adjustedPoint, QLineF(p0, p1)) < 1e-4) {
- dbgKrita << ppVar(*pt);
- dbgKrita << ppVar(adjustedPoint);
- dbgKrita << ppVar(QLineF(p0, p1));
- dbgKrita << ppVar(salt);
-
- dbgKrita << ppVar(poly.containsPoint(*pt, Qt::OddEvenFill));
-
- dbgKrita << ppVar(kisDistanceToLine(*pt, QLineF(p0, p1)));
- dbgKrita << ppVar(kisDistanceToLine(adjustedPoint, QLineF(p0, p1)));
- }
-
- *pt = adjustedPoint;
-
- KIS_ASSERT_RECOVER_NOOP(kisDistanceToLine(*pt, QLineF(p0, p1)) > 1e-4);
-#endif /* SANITY_CHECKS */
- }
- }
- }
-}
-
-QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2) {
- qreal len1 = norm(base1);
- if (len1 < 1e-5) return pt;
- qreal sin1 = base1.y() / len1;
- qreal cos1 = base1.x() / len1;
-
- qreal len2 = norm(base2);
- if (len2 < 1e-5) return QPointF();
- qreal sin2 = base2.y() / len2;
- qreal cos2 = base2.x() / len2;
-
- qreal sinD = sin2 * cos1 - cos2 * sin1;
- qreal cosD = cos1 * cos2 + sin1 * sin2;
- qreal scaleD = len2 / len1;
-
- QPointF result;
- result.rx() = scaleD * (pt.x() * cosD - pt.y() * sinD);
- result.ry() = scaleD * (pt.x() * sinD + pt.y() * cosD);
-
- return result;
-}
-
-qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
-{
- qreal a1 = std::atan2(v1.y(), v1.x());
- qreal a2 = std::atan2(v2.y(), v2.x());
-
- return a2 - a1;
-}
-
-QPainterPath smallArrow()
-{
- QPainterPath p;
-
- p.moveTo(5, 2);
- p.lineTo(-3, 8);
- p.lineTo(-5, 5);
- p.lineTo( 2, 0);
- p.lineTo(-5,-5);
- p.lineTo(-3,-8);
- p.lineTo( 5,-2);
- p.arcTo(QRectF(3, -2, 4, 4), 90, -180);
-
- return p;
-}
-
-template <class Point, class Rect>
-inline Point ensureInRectImpl(Point pt, const Rect &bounds)
-{
- if (pt.x() > bounds.right()) {
- pt.rx() = bounds.right();
- } else if (pt.x() < bounds.left()) {
- pt.rx() = bounds.left();
- }
-
- if (pt.y() > bounds.bottom()) {
- pt.ry() = bounds.bottom();
- } else if (pt.y() < bounds.top()) {
- pt.ry() = bounds.top();
- }
-
- return pt;
-}
-
-QPoint ensureInRect(QPoint pt, const QRect &bounds)
-{
- return ensureInRectImpl(pt, bounds);
-}
-
-QPointF ensureInRect(QPointF pt, const QRectF &bounds)
-{
- return ensureInRectImpl(pt, bounds);
-}
-
-QRect ensureRectNotSmaller(QRect rc, const QSize &size)
-{
- if (rc.width() < size.width() ||
- rc.height() < size.height()) {
-
- int width = qMax(rc.width(), size.width());
- int height = qMax(rc.height(), size.height());
-
- rc = QRect(rc.topLeft(), QSize(width, height));
- }
-
- return rc;
-}
-
-bool intersectLineRect(QLineF &line, const QRect rect)
-{
- QPointF pt1 = QPointF(), pt2 = QPointF();
- QPointF tmp;
-
- if (line.intersect(QLineF(rect.topLeft(), rect.topRight()), &tmp) != QLineF::NoIntersection) {
- if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) {
- pt1 = tmp;
- }
- }
-
- if (line.intersect(QLineF(rect.topRight(), rect.bottomRight()), &tmp) != QLineF::NoIntersection) {
- if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) {
- if (pt1.isNull()) pt1 = tmp;
- else pt2 = tmp;
- }
- }
- if (line.intersect(QLineF(rect.bottomRight(), rect.bottomLeft()), &tmp) != QLineF::NoIntersection) {
- if (tmp.x() >= rect.left() && tmp.x() <= rect.right()) {
- if (pt1.isNull()) pt1 = tmp;
- else pt2 = tmp;
- }
- }
- if (line.intersect(QLineF(rect.bottomLeft(), rect.topLeft()), &tmp) != QLineF::NoIntersection) {
- if (tmp.y() >= rect.top() && tmp.y() <= rect.bottom()) {
- if (pt1.isNull()) pt1 = tmp;
- else pt2 = tmp;
- }
- }
-
- if (pt1.isNull() || pt2.isNull()) return false;
-
- // Attempt to retain polarity of end points
- if ((line.x1() < line.x2()) != (pt1.x() > pt2.x()) || (line.y1() < line.y2()) != (pt1.y() > pt2.y())) {
- tmp = pt1;
- pt1 = pt2;
- pt2 = tmp;
- }
-
- line.setP1(pt1);
- line.setP2(pt2);
-
- return true;
-}
-
-QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p)
-{
- QVector<QPointF> points;
-
- const QLineF cutLine = p.getLine();
-
- points << rc.topLeft();
- points << rc.topRight();
- points << rc.bottomRight();
- points << rc.bottomLeft();
-
- QPointF p1 = points[3];
- bool p1Valid = p.pos(p1) >= 0;
-
- QVector<QPointF> resultPoints;
-
- for (int i = 0; i < 4; i++) {
- const QPointF p2 = points[i];
- const bool p2Valid = p.pos(p2) >= 0;
-
- if (p1Valid != p2Valid) {
- QPointF intersection;
- cutLine.intersect(QLineF(p1, p2), &intersection);
- resultPoints << intersection;
- }
-
- if (p2Valid) {
- resultPoints << p2;
- }
-
- p1 = p2;
- p1Valid = p2Valid;
- }
-
- return KritaUtils::approximateRectFromPoints(resultPoints);
-}
-
-int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2)
-{
- int numSolutions = 0;
-
- const qreal D = pow2(b) - 4 * a * c;
-
- if (D < 0) {
- return 0;
- } else if (qFuzzyCompare(D, 0)) {
- *x1 = -b / (2 * a);
- numSolutions = 1;
- } else {
- const qreal sqrt_D = std::sqrt(D);
-
- *x1 = (-b + sqrt_D) / (2 * a);
- *x2 = (-b - sqrt_D) / (2 * a);
- numSolutions = 2;
- }
-
- return numSolutions;
-}
-
-QVector<QPointF> intersectTwoCircles(const QPointF &center1, qreal r1,
- const QPointF &center2, qreal r2)
-{
- QVector<QPointF> points;
-
- const QPointF diff = (center2 - center1);
- const QPointF c1;
- const QPointF c2 = diff;
-
- const qreal centerDistance = norm(diff);
-
- if (centerDistance > r1 + r2) return points;
- if (centerDistance < qAbs(r1 - r2)) return points;
-
- if (centerDistance < qAbs(r1 - r2) + 0.001) {
- qDebug() << "Skipping intersection" << ppVar(center1) << ppVar(center2) << ppVar(r1) << ppVar(r2) << ppVar(centerDistance) << ppVar(qAbs(r1-r2));
- return points;
- }
-
- const qreal x_kp1 = diff.x();
- const qreal y_kp1 = diff.y();
-
- const qreal F2 =
- 0.5 * (pow2(x_kp1) +
- pow2(y_kp1) + pow2(r1) - pow2(r2));
-
- if (qFuzzyCompare(diff.y(), 0)) {
- qreal x = F2 / diff.x();
- qreal y1, y2;
- int result = KisAlgebra2D::quadraticEquation(
- 1, 0,
- pow2(x) - pow2(r2),
- &y1, &y2);
-
- KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
-
- if (result == 1) {
- points << QPointF(x, y1);
- } else if (result == 2) {
- KisAlgebra2D::RightHalfPlane p(c1, c2);
-
- QPointF p1(x, y1);
- QPointF p2(x, y2);
-
- if (p.pos(p1) >= 0) {
- points << p1;
- points << p2;
- } else {
- points << p2;
- points << p1;
- }
- }
- } else {
- const qreal A = diff.x() / diff.y();
- const qreal C = F2 / diff.y();
-
- qreal x1, x2;
- int result = KisAlgebra2D::quadraticEquation(
- 1 + pow2(A), -2 * A * C,
- pow2(C) - pow2(r1),
- &x1, &x2);
-
- KIS_SAFE_ASSERT_RECOVER(result > 0) { return points; }
-
- if (result == 1) {
- points << QPointF(x1, C - x1 * A);
- } else if (result == 2) {
- KisAlgebra2D::RightHalfPlane p(c1, c2);
-
- QPointF p1(x1, C - x1 * A);
- QPointF p2(x2, C - x2 * A);
-
- if (p.pos(p1) >= 0) {
- points << p1;
- points << p2;
- } else {
- points << p2;
- points << p1;
- }
- }
- }
-
- for (int i = 0; i < points.size(); i++) {
- points[i] = center1 + points[i];
- }
-
- return points;
-}
-
-}
diff --git a/libs/image/kis_algebra_2d.h b/libs/image/kis_algebra_2d.h
deleted file mode 100644
index ffdb84a62f..0000000000
--- a/libs/image/kis_algebra_2d.h
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * 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_ALGEBRA_2D_H
-#define __KIS_ALGEBRA_2D_H
-
-#include <QPoint>
-#include <QPointF>
-#include <QVector>
-#include <QPolygonF>
-#include <cmath>
-#include <kis_global.h>
-#include <kritaimage_export.h>
-
-class QPainterPath;
-
-namespace KisAlgebra2D {
-
-template <class T>
-struct PointTypeTraits
-{
-};
-
-template <>
-struct PointTypeTraits<QPoint>
-{
- typedef int value_type;
- typedef qreal calculation_type;
- typedef QRect rect_type;
-};
-
-template <>
-struct PointTypeTraits<QPointF>
-{
- typedef qreal value_type;
- typedef qreal calculation_type;
- typedef QRectF rect_type;
-};
-
-
-template <class T>
-typename PointTypeTraits<T>::value_type dotProduct(const T &a, const T &b)
-{
- return a.x() * b.x() + a.y() * b.y();
-}
-
-template <class T>
-typename PointTypeTraits<T>::value_type crossProduct(const T &a, const T &b)
-{
- return a.x() * b.y() - a.y() * b.x();
-}
-
-template <class T>
-qreal norm(const T &a)
-{
- return std::sqrt(pow2(a.x()) + pow2(a.y()));
-}
-
-template <class Point>
-Point normalize(const Point &a)
-{
- const qreal length = norm(a);
- return (1.0 / length) * a;
-}
-
-/**
- * Usual sign() function with positive zero
- */
-template <typename T>
-T signPZ(T x) {
- return x >= T(0) ? T(1) : T(-1);
-}
-
-/**
- * Usual sign() function with zero returning zero
- */
-template <typename T>
-T signZZ(T x) {
- return x == T(0) ? T(0) : x > T(0) ? T(1) : T(-1);
-}
-
-/**
- * Copies the sign of \p y into \p x and returns the result
- */
-template <typename T>
- inline T copysign(T x, T y) {
- T strippedX = qAbs(x);
- return y >= T(0) ? strippedX : -strippedX;
-}
-
-template <class T>
-T leftUnitNormal(const T &a)
-{
- T result = a.x() != 0 ? T(-a.y() / a.x(), 1) : T(-1, 0);
- qreal length = norm(result);
- result *= (crossProduct(a, result) >= 0 ? 1 : -1) / length;
-
- return -result;
-}
-
-template <class T>
-T rightUnitNormal(const T &a)
-{
- return -leftUnitNormal(a);
-}
-
-template <class T>
-T inwardUnitNormal(const T &a, int polygonDirection)
-{
- return polygonDirection * leftUnitNormal(a);
-}
-
-/**
- * \return 1 if the polygon is counterclockwise
- * -1 if the polygon is clockwise
- *
- * Note: the sign is flipped because our 0y axis
- * is reversed
- */
-template <class T>
-int polygonDirection(const QVector<T> &polygon) {
-
- typename PointTypeTraits<T>::value_type doubleSum = 0;
-
- const int numPoints = polygon.size();
- for (int i = 1; i <= numPoints; i++) {
- int prev = i - 1;
- int next = i == numPoints ? 0 : i;
-
- doubleSum +=
- (polygon[next].x() - polygon[prev].x()) *
- (polygon[next].y() + polygon[prev].y());
- }
-
- return doubleSum >= 0 ? 1 : -1;
-}
-
-template <typename T>
-bool isInRange(T x, T a, T b) {
- T length = qAbs(a - b);
- return qAbs(x - a) <= length && qAbs(x - b) <= length;
-}
-
-void KRITAIMAGE_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int polygonDirection, QPointF *pt);
-
-/**
- * Let \p pt, \p base1 are two vectors. \p base1 is uniformly scaled
- * and then rotated into \p base2 using transformation matrix S *
- * R. The function applies the same transformation to \pt and returns
- * the result.
- **/
-QPointF KRITAIMAGE_EXPORT transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2);
-
-qreal KRITAIMAGE_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2);
-
-namespace Private {
- inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) {
- *rc = QRect(pt, QSize(1, 1));
- }
-
- inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) {
- static const qreal eps = 1e-10;
- *rc = QRectF(pt, QSizeF(eps, eps));
- }
-}
-
-template <class Point, class Rect>
-inline void accumulateBounds(const Point &pt, Rect *bounds)
-{
- if (bounds->isEmpty()) {
- Private::resetEmptyRectangle(pt, bounds);
- }
-
- if (pt.x() > bounds->right()) {
- bounds->setRight(pt.x());
- }
-
- if (pt.x() < bounds->left()) {
- bounds->setLeft(pt.x());
- }
-
- if (pt.y() > bounds->bottom()) {
- bounds->setBottom(pt.y());
- }
-
- if (pt.y() < bounds->top()) {
- bounds->setTop(pt.y());
- }
-}
-
-template <template <class T> class Container, class Point, class Rect>
-inline void accumulateBounds(const Container<Point> &points, Rect *bounds)
-{
- Q_FOREACH (const Point &pt, points) {
- accumulateBounds(pt, bounds);
- }
-}
-
-template <template <class T> class Container, class Point>
-inline typename PointTypeTraits<Point>::rect_type
-accumulateBounds(const Container<Point> &points)
-{
- typename PointTypeTraits<Point>::rect_type result;
-
- Q_FOREACH (const Point &pt, points) {
- accumulateBounds(pt, &result);
- }
-
- return result;
-}
-
-template <class Point, class Rect>
-inline Point clampPoint(Point pt, const Rect &bounds)
-{
- if (pt.x() > bounds.right()) {
- pt.rx() = bounds.right();
- }
-
- if (pt.x() < bounds.left()) {
- pt.rx() = bounds.left();
- }
-
- if (pt.y() > bounds.bottom()) {
- pt.ry() = bounds.bottom();
- }
-
- if (pt.y() < bounds.top()) {
- pt.ry() = bounds.top();
- }
-
- return pt;
-}
-
-template <class Size>
-auto maxDimension(Size size) -> decltype(size.width()) {
- return qMax(size.width(), size.height());
-}
-
-QPainterPath KRITAGLOBAL_EXPORT smallArrow();
-
-/**
- * Multiply width and height of \p rect by \p coeff keeping the
- * center of the rectangle pinned
- */
-template <class Rect>
-Rect blowRect(const Rect &rect, qreal coeff)
-{
- typedef decltype(rect.x()) CoordType;
-
- CoordType w = rect.width() * coeff;
- CoordType h = rect.height() * coeff;
-
- return rect.adjusted(-w, -h, w, h);
-}
-
-QPoint KRITAIMAGE_EXPORT ensureInRect(QPoint pt, const QRect &bounds);
-QPointF KRITAIMAGE_EXPORT ensureInRect(QPointF pt, const QRectF &bounds);
-
-QRect KRITAIMAGE_EXPORT ensureRectNotSmaller(QRect rc, const QSize &size);
-
-/**
- * Attempt to intersect a line to the area of the a rectangle.
- *
- * If the line intersects the rectange, it will be modified to represent the intersecting line segment and true is returned.
- * If the line does not intersect the area, it remains unmodified and false will be returned.
- *
- * @param segment
- * @param area
- * @return true if successful
- */
-bool KRITAIMAGE_EXPORT intersectLineRect(QLineF &line, const QRect rect);
-
-
-template <class Point>
-inline Point abs(const Point &pt) {
- return Point(qAbs(pt.x()), qAbs(pt.y()));
-}
-
-
-class RightHalfPlane {
-public:
-
- RightHalfPlane(const QPointF &a, const QPointF &b)
- : m_a(a), m_p(b - a), m_norm_p_inv(1.0 / norm(m_p))
- {
- }
-
- RightHalfPlane(const QLineF &line)
- : RightHalfPlane(line.p1(), line.p2())
- {
- }
-
- qreal valueSq(const QPointF &pt) const {
- const qreal val = value(pt);
- return signZZ(val) * pow2(val);
- }
-
- qreal value(const QPointF &pt) const {
- return crossProduct(m_p, pt - m_a) * m_norm_p_inv;
- }
-
- int pos(const QPointF &pt) const {
- return signZZ(value(pt));
- }
-
- QLineF getLine() const {
- return QLineF(m_a, m_a + m_p);
- }
-
-private:
- const QPointF m_a;
- const QPointF m_p;
- const qreal m_norm_p_inv;
-};
-
-class OuterCircle {
-public:
-
- OuterCircle(const QPointF &c, qreal radius)
- : m_c(c),
- m_radius(radius),
- m_radius_sq(pow2(radius)),
- m_fadeCoeff(1.0 / (pow2(radius + 1.0) - m_radius_sq))
- {
- }
-
- qreal valueSq(const QPointF &pt) const {
- const qreal val = value(pt);
-
- return signZZ(val) * pow2(val);
- }
-
- qreal value(const QPointF &pt) const {
- return kisDistance(pt, m_c) - m_radius;
- }
-
- int pos(const QPointF &pt) const {
- return signZZ(valueSq(pt));
- }
-
- qreal fadeSq(const QPointF &pt) const {
- const qreal valSq = kisSquareDistance(pt, m_c);
- return (valSq - m_radius_sq) * m_fadeCoeff;
- }
-
-private:
- const QPointF m_c;
- const qreal m_radius;
- const qreal m_radius_sq;
- const qreal m_fadeCoeff;
-};
-
-
-/**
- * Cuts off a portion of a rect \p rc defined by a half-plane \p p
- * \return the bounding rect of the resulting polygon
- */
-KRITAIMAGE_EXPORT
-QRectF cutOffRect(const QRectF &rc, const KisAlgebra2D::RightHalfPlane &p);
-
-
-/**
- * Solves a quadratic equation in a form:
- *
- * a * x^2 + b * x + c = 0
- *
- * WARNING: Please note that \p a *must* be nonzero! Otherwise the
- * equation is not quadratic! And this function doesn't check that!
- *
- * \return the number of solutions. It can be 0, 1 or 2.
- *
- * \p x1, \p x2 --- the found solution. The variables are filled with
- * data iff the corresponding solution is found. That
- * is: 0 solutions --- variabled are not touched, 1
- * solution --- x1 is filled with the result, 2
- * solutions --- x1 and x2 are filled.
- */
-KRITAIMAGE_EXPORT
-int quadraticEquation(qreal a, qreal b, qreal c, qreal *x1, qreal *x2);
-
-/**
- * Finds the points of intersections between two circles
- * \return the found circles, the result can have 0, 1 or 2 points
- */
-KRITAIMAGE_EXPORT
-QVector<QPointF> intersectTwoCircles(const QPointF &c1, qreal r1,
- const QPointF &c2, qreal r2);
-
-
-/**
- * Scale the relative point \pt into the bounds of \p rc. The point might be
- * outside the rectangle.
- */
-inline QPointF relativeToAbsolute(const QPointF &pt, const QRectF &rc) {
- return rc.topLeft() + QPointF(pt.x() * rc.width(), pt.y() * rc.height());
-}
-
-/**
- * Get the relative position of \p pt inside rectangle \p rc. The point can be
- * outside the rectangle.
- */
-inline QPointF absoluteToRelative(const QPointF &pt, const QRectF &rc) {
- if (!rc.isValid()) return QPointF();
-
- const QPointF rel = pt - rc.topLeft();
- return QPointF(rel.x() / rc.width(), rel.y() / rc.height());
-
-}
-
-
-}
-
-
-#endif /* __KIS_ALGEBRA_2D_H */
diff --git a/libs/image/kis_cage_transform_worker.cpp b/libs/image/kis_cage_transform_worker.cpp
index 0c0bd4017a..97b1209738 100644
--- a/libs/image/kis_cage_transform_worker.cpp
+++ b/libs/image/kis_cage_transform_worker.cpp
@@ -1,449 +1,449 @@
/*
* 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_cage_transform_worker.h"
#include "kis_grid_interpolation_tools.h"
#include "kis_green_coordinates_math.h"
#include <QPainter>
#include "KoColor.h"
#include "kis_selection.h"
#include "kis_painter.h"
#include "kis_image.h"
#include "krita_utils.h"
#include <qnumeric.h>
struct Q_DECL_HIDDEN KisCageTransformWorker::Private
{
Private(KisPaintDeviceSP _dev,
const QVector<QPointF> &_origCage,
KoUpdater *_progress,
int _pixelPrecision)
: dev(_dev),
origCage(_origCage),
progress(_progress),
pixelPrecision(_pixelPrecision)
{
}
KisPaintDeviceSP dev;
QImage srcImage;
QPointF srcImageOffset;
QVector<QPointF> origCage;
QVector<QPointF> transfCage;
KoUpdater *progress;
int pixelPrecision;
QVector<int> allToValidPointsMap;
QVector<QPointF> validPoints;
/**
* Contains all points fo the grid including non-defined
* points (the ones which are placed outside the cage).
*/
QVector<QPointF> allSrcPoints;
KisGreenCoordinatesMath cage;
QSize gridSize;
bool isGridEmpty() const {
return allSrcPoints.isEmpty();
}
QVector<QPointF> calculateTransformedPoints();
inline QVector<int> calculateMappedIndexes(int col, int row,
int *numExistingPoints);
int tryGetValidIndex(const QPoint &cellPt);
struct MapIndexesOp;
};
KisCageTransformWorker::KisCageTransformWorker(KisPaintDeviceSP dev,
const QVector<QPointF> &origCage,
KoUpdater *progress,
int pixelPrecision)
: m_d(new Private(dev, origCage, progress, pixelPrecision))
{
}
KisCageTransformWorker::KisCageTransformWorker(const QImage &srcImage,
const QPointF &srcImageOffset,
const QVector<QPointF> &origCage,
KoUpdater *progress,
int pixelPrecision)
: m_d(new Private(0, origCage, progress, pixelPrecision))
{
m_d->srcImage = srcImage;
m_d->srcImageOffset = srcImageOffset;
}
KisCageTransformWorker::~KisCageTransformWorker()
{
}
void KisCageTransformWorker::setTransformedCage(const QVector<QPointF> &transformedCage)
{
m_d->transfCage = transformedCage;
}
struct PointsFetcherOp
{
PointsFetcherOp(const QPolygonF &cagePolygon)
: m_cagePolygon(cagePolygon),
m_numValidPoints(0)
{
m_polygonDirection = KisAlgebra2D::polygonDirection(cagePolygon);
}
inline void processPoint(int col, int row,
int prevCol, int prevRow,
int colIndex, int rowIndex) {
Q_UNUSED(prevCol);
Q_UNUSED(prevRow);
Q_UNUSED(colIndex);
Q_UNUSED(rowIndex);
QPointF pt(col, row);
if (m_cagePolygon.containsPoint(pt, Qt::OddEvenFill)) {
KisAlgebra2D::adjustIfOnPolygonBoundary(m_cagePolygon, m_polygonDirection, &pt);
m_points << pt;
m_pointValid << true;
m_numValidPoints++;
} else {
m_points << pt;
m_pointValid << false;
}
}
inline void nextLine() {
}
QVector<bool> m_pointValid;
QVector<QPointF> m_points;
QPolygonF m_cagePolygon;
int m_polygonDirection;
int m_numValidPoints;
};
void KisCageTransformWorker::prepareTransform()
{
if (m_d->origCage.size() < 3) return;
const QPolygonF srcPolygon(m_d->origCage);
QRect srcBounds = m_d->dev ? m_d->dev->region().boundingRect() :
QRectF(m_d->srcImageOffset, m_d->srcImage.size()).toAlignedRect();
srcBounds &= srcPolygon.boundingRect().toAlignedRect();
// no need to process empty devices
if (srcBounds.isEmpty()) return;
m_d->gridSize =
GridIterationTools::calcGridSize(srcBounds, m_d->pixelPrecision);
PointsFetcherOp pointsOp(srcPolygon);
GridIterationTools::processGrid(pointsOp, srcBounds, m_d->pixelPrecision);
const int numPoints = pointsOp.m_points.size();
KIS_ASSERT_RECOVER_RETURN(numPoints == m_d->gridSize.width() * m_d->gridSize.height());
m_d->allSrcPoints = pointsOp.m_points;
m_d->allToValidPointsMap.resize(pointsOp.m_points.size());
m_d->validPoints.resize(pointsOp.m_numValidPoints);
{
int validIdx = 0;
for (int i = 0; i < numPoints; i++) {
const QPointF &pt = pointsOp.m_points[i];
const bool pointValid = pointsOp.m_pointValid[i];
if (pointValid) {
m_d->validPoints[validIdx] = pt;
m_d->allToValidPointsMap[i] = validIdx;
validIdx++;
} else {
m_d->allToValidPointsMap[i] = -1;
}
}
KIS_ASSERT_RECOVER_NOOP(validIdx == m_d->validPoints.size());
}
m_d->cage.precalculateGreenCoordinates(m_d->origCage, m_d->validPoints);
}
QVector<QPointF> KisCageTransformWorker::Private::calculateTransformedPoints()
{
cage.generateTransformedCageNormals(transfCage);
const int numValidPoints = validPoints.size();
QVector<QPointF> transformedPoints(numValidPoints);
for (int i = 0; i < numValidPoints; i++) {
transformedPoints[i] = cage.transformedPoint(i, transfCage);
if (qIsNaN(transformedPoints[i].x()) ||
qIsNaN(transformedPoints[i].y())) {
warnKrita << "WARNING: One grid point has been removed from consideration" << validPoints[i];
transformedPoints[i] = validPoints[i];
}
}
return transformedPoints;
}
inline QVector<int> KisCageTransformWorker::Private::
calculateMappedIndexes(int col, int row,
int *numExistingPoints)
{
*numExistingPoints = 0;
QVector<int> cellIndexes =
GridIterationTools::calculateCellIndexes(col, row, gridSize);
for (int i = 0; i < 4; i++) {
cellIndexes[i] = allToValidPointsMap[cellIndexes[i]];
*numExistingPoints += cellIndexes[i] >= 0;
}
return cellIndexes;
}
int KisCageTransformWorker::Private::
tryGetValidIndex(const QPoint &cellPt)
{
int index = -1;
if (cellPt.x() >= 0 &&
cellPt.y() >= 0 &&
cellPt.x() < gridSize.width() - 1 &&
cellPt.y() < gridSize.height() - 1) {
index = allToValidPointsMap[GridIterationTools::pointToIndex(cellPt, gridSize)];
}
return index;
}
struct KisCageTransformWorker::Private::MapIndexesOp {
MapIndexesOp(KisCageTransformWorker::Private *d)
: m_d(d),
m_srcCagePolygon(QPolygonF(m_d->origCage))
{
}
inline QVector<int> calculateMappedIndexes(int col, int row,
int *numExistingPoints) const {
return m_d->calculateMappedIndexes(col, row, numExistingPoints);
}
inline int tryGetValidIndex(const QPoint &cellPt) const {
return m_d->tryGetValidIndex(cellPt);
}
inline QPointF getSrcPointForce(const QPoint &cellPt) const {
return m_d->allSrcPoints[GridIterationTools::pointToIndex(cellPt, m_d->gridSize)];
}
inline const QPolygonF srcCropPolygon() const {
return m_srcCagePolygon;
}
KisCageTransformWorker::Private *m_d;
QPolygonF m_srcCagePolygon;
};
QRect KisCageTransformWorker::approxChangeRect(const QRect &rc)
{
const qreal margin = 0.30;
QVector<QPointF> cageSamplePoints;
const int minStep = 3;
const int maxSamples = 200;
const int totalPixels = rc.width() * rc.height();
const int realStep = qMax(minStep, totalPixels / maxSamples);
const QPolygonF cagePolygon(m_d->origCage);
for (int i = 0; i < totalPixels; i += realStep) {
const int x = rc.x() + i % rc.width();
const int y = rc.y() + i / rc.width();
const QPointF pt(x, y);
if (cagePolygon.containsPoint(pt, Qt::OddEvenFill)) {
cageSamplePoints << pt;
}
}
if (cageSamplePoints.isEmpty()) {
return rc;
}
KisGreenCoordinatesMath cage;
cage.precalculateGreenCoordinates(m_d->origCage, cageSamplePoints);
cage.generateTransformedCageNormals(m_d->transfCage);
const int numValidPoints = cageSamplePoints.size();
QVector<QPointF> transformedPoints(numValidPoints);
int failedPoints = 0;
for (int i = 0; i < numValidPoints; i++) {
transformedPoints[i] = cage.transformedPoint(i, m_d->transfCage);
if (qIsNaN(transformedPoints[i].x()) ||
qIsNaN(transformedPoints[i].y())) {
transformedPoints[i] = cageSamplePoints[i];
failedPoints++;
}
}
QRect resultRect =
- KritaUtils::approximateRectFromPoints(transformedPoints).toAlignedRect();
+ KisAlgebra2D::approximateRectFromPoints(transformedPoints).toAlignedRect();
return KisAlgebra2D::blowRect(resultRect | rc, margin);
}
QRect KisCageTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
{
Q_UNUSED(rc);
return fullBounds;
}
void KisCageTransformWorker::run()
{
if (m_d->isGridEmpty()) return;
KIS_ASSERT_RECOVER_RETURN(m_d->origCage.size() >= 3);
KIS_ASSERT_RECOVER_RETURN(m_d->origCage.size() == m_d->transfCage.size());
QVector<QPointF> transformedPoints = m_d->calculateTransformedPoints();
KisPaintDeviceSP srcDev = new KisPaintDevice(*m_d->dev.data());
KisPaintDeviceSP tempDevice = new KisPaintDevice(m_d->dev->colorSpace());
{
KisSelectionSP selection = new KisSelection();
KisPainter painter(selection->pixelSelection());
painter.setPaintColor(KoColor(Qt::black, selection->pixelSelection()->colorSpace()));
painter.setAntiAliasPolygonFill(true);
painter.setFillStyle(KisPainter::FillStyleForegroundColor);
painter.setStrokeStyle(KisPainter::StrokeStyleNone);
painter.paintPolygon(m_d->origCage);
m_d->dev->clearSelection(selection);
}
GridIterationTools::PaintDevicePolygonOp polygonOp(srcDev, tempDevice);
Private::MapIndexesOp indexesOp(m_d.data());
GridIterationTools::iterateThroughGrid
<GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
m_d->gridSize,
m_d->validPoints,
transformedPoints);
QRect rect = tempDevice->extent();
KisPainter gc(m_d->dev);
gc.bitBlt(rect.topLeft(), tempDevice, rect);
}
QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset)
{
if (m_d->isGridEmpty()) return QImage();
KIS_ASSERT_RECOVER(m_d->origCage.size() >= 3 &&
m_d->origCage.size() == m_d->transfCage.size()) {
return QImage();
}
KIS_ASSERT_RECOVER(!m_d->srcImage.isNull()) {
return QImage();
}
KIS_ASSERT_RECOVER(m_d->srcImage.format() == QImage::Format_ARGB32) {
return QImage();
}
QVector<QPointF> transformedPoints = m_d->calculateTransformedPoints();
QRectF dstBounds;
Q_FOREACH (const QPointF &pt, transformedPoints) {
KisAlgebra2D::accumulateBounds(pt, &dstBounds);
}
const QRectF srcBounds(m_d->srcImageOffset, m_d->srcImage.size());
dstBounds |= srcBounds;
QPointF dstQImageOffset = dstBounds.topLeft();
*newOffset = dstQImageOffset;
QRect dstBoundsI = dstBounds.toAlignedRect();
QImage dstImage(dstBoundsI.size(), m_d->srcImage.format());
dstImage.fill(0);
QImage tempImage(dstImage);
{
// we shouldn't create too many painters
QPainter gc(&dstImage);
gc.drawImage(-dstQImageOffset + m_d->srcImageOffset, m_d->srcImage);
gc.setBrush(Qt::black);
gc.setPen(Qt::black);
gc.setCompositionMode(QPainter::CompositionMode_Clear);
gc.drawPolygon(QPolygonF(m_d->origCage).translated(-dstQImageOffset));
gc.end();
}
GridIterationTools::QImagePolygonOp polygonOp(m_d->srcImage, tempImage, m_d->srcImageOffset, dstQImageOffset);
Private::MapIndexesOp indexesOp(m_d.data());
GridIterationTools::iterateThroughGrid
<GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
m_d->gridSize,
m_d->validPoints,
transformedPoints);
{
QPainter gc(&dstImage);
gc.drawImage(QPoint(), tempImage);
}
return dstImage;
}
diff --git a/libs/image/kis_change_profile_visitor.h b/libs/image/kis_change_profile_visitor.h
index 2ded5e0c2b..0042952300 100644
--- a/libs/image/kis_change_profile_visitor.h
+++ b/libs/image/kis_change_profile_visitor.h
@@ -1,138 +1,140 @@
/*
* 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_CHANGE_PROFILE_VISITOR_H_
#define KIS_CHANGE_PROFILE_VISITOR_H_
#include "KoColorSpace.h"
#include "kis_types.h"
#include "kis_node_visitor.h"
#include "kis_paint_layer.h"
#include "lazybrush/kis_colorize_mask.h"
#include "kis_adjustment_layer.h"
#include "generator/kis_generator_layer.h"
#include "kis_group_layer.h"
class KisExternalLayer;
// #include "kis_external_layer_iface.h"
+#include <kritaimage_export.h>
+
/**
* The Change Profile visitor walks over all layers and if the current
* layer has the specified colorspace AND the specified old profile, sets
* the colorspace to the same colorspace with the NEW profile, without doing
* conversions. This is essential if you have loaded an image that didn't
* have an embedded profile to which you want to attach the right profile.
*/
-class KisChangeProfileVisitor : public KisNodeVisitor
+class KRITAIMAGE_EXPORT KisChangeProfileVisitor : public KisNodeVisitor
{
public:
using KisNodeVisitor::visit;
KisChangeProfileVisitor(const KoColorSpace * oldColorSpace,
const KoColorSpace *dstColorSpace)
: KisNodeVisitor()
, m_oldColorSpace(oldColorSpace)
, m_dstColorSpace(dstColorSpace) {
}
~KisChangeProfileVisitor() {
}
bool visit(KisExternalLayer *) {
return true;
}
bool visit(KisGroupLayer * layer) {
// Clear the projection, we will have to re-render everything.
layer->resetCache();
KisLayerSP child = dynamic_cast<KisLayer*>(layer->firstChild().data());
while (child) {
child->accept(*this);
child = dynamic_cast<KisLayer*>(child->nextSibling().data());
}
return true;
}
bool visit(KisPaintLayer *layer) {
return updatePaintDevice(layer);
}
bool visit(KisGeneratorLayer *layer) {
return updatePaintDevice(layer);
}
bool visit(KisAdjustmentLayer * layer) {
layer->resetCache();
return true;
}
bool visit(KisNode*) {
return true;
}
bool visit(KisCloneLayer*) {
return true;
}
bool visit(KisFilterMask*) {
return true;
}
bool visit(KisTransformMask*) {
return true;
}
bool visit(KisTransparencyMask*) {
return true;
}
bool visit(KisSelectionMask*) {
return true;
}
bool visit(KisColorizeMask *mask) {
if (mask->colorSpace()->colorModelId() == m_oldColorSpace->colorModelId()) {
mask->setProfile(m_dstColorSpace->profile());
}
return true;
}
private:
bool updatePaintDevice(KisLayer *layer) {
if (!layer) return false;
if (!layer->paintDevice()) return false;
if (!layer->paintDevice()->colorSpace()) return false;
const KoColorSpace *cs = layer->paintDevice()->colorSpace();
if (cs->colorModelId() == m_oldColorSpace->colorModelId()) {
layer->paintDevice()->setProfile(m_dstColorSpace->profile());
if (layer->projection() != layer->paintDevice()) {
layer->projection()->setProfile(m_dstColorSpace->profile());
}
}
return true;
}
const KoColorSpace *m_oldColorSpace;
const KoColorSpace *m_dstColorSpace;
};
#endif // KIS_CHANGE_PROFILE_VISITOR_H_
diff --git a/libs/image/kis_command_utils.cpp b/libs/image/kis_command_utils.cpp
deleted file mode 100644
index 9a40008062..0000000000
--- a/libs/image/kis_command_utils.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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_command_utils.h"
-
-namespace KisCommandUtils
-{
- AggregateCommand::AggregateCommand()
- : m_firstRedo(true) {}
-
- void AggregateCommand::redo()
- {
- if (m_firstRedo) {
- m_firstRedo = false;
-
- populateChildCommands();
- }
-
- m_store.redoAll();
- }
-
- void AggregateCommand::undo()
- {
- m_store.undoAll();
- }
-
- void AggregateCommand::addCommand(KUndo2Command *cmd)
- {
- m_store.addCommand(cmd);
- }
-
- SkipFirstRedoWrapper::SkipFirstRedoWrapper(KUndo2Command *child, KUndo2Command *parent)
- : KUndo2Command(parent), m_firstRedo(true), m_child(child) {}
-
- void SkipFirstRedoWrapper::redo()
- {
- if (m_firstRedo) {
- m_firstRedo = false;
- } else {
- if (m_child) {
- m_child->redo();
- }
- KUndo2Command::redo();
- }
- }
-
- void SkipFirstRedoWrapper::undo()
- {
- KUndo2Command::undo();
- if (m_child) {
- m_child->undo();
- }
- }
-
- FlipFlopCommand::FlipFlopCommand(bool finalize, KUndo2Command *parent)
- : KUndo2Command(parent),
- m_finalize(finalize),
- m_firstRedo(true)
- {
- }
-
- void FlipFlopCommand::redo()
- {
- if (!m_finalize) {
- init();
- } else {
- end();
- }
-
- m_firstRedo = false;
- }
-
- void FlipFlopCommand::undo()
- {
- if (m_finalize) {
- init();
- } else {
- end();
- }
- }
-
- void FlipFlopCommand::init() {}
- void FlipFlopCommand::end() {}
-
- CompositeCommand::CompositeCommand(KUndo2Command *parent)
- : KUndo2Command(parent) {}
-
- CompositeCommand::~CompositeCommand() {
- qDeleteAll(m_commands);
- }
-
- void CompositeCommand::addCommand(KUndo2Command *cmd) {
- if (cmd) {
- m_commands << cmd;
- }
- }
-
- void CompositeCommand::redo() {
- Q_FOREACH (KUndo2Command *cmd, m_commands) {
- cmd->redo();
- }
- }
-
- void CompositeCommand::undo() {
- Q_FOREACH (KUndo2Command *cmd, m_commands) {
- cmd->undo();
- }
- }
-}
diff --git a/libs/image/kis_command_utils.h b/libs/image/kis_command_utils.h
deleted file mode 100644
index c4169ca343..0000000000
--- a/libs/image/kis_command_utils.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef __KIS_COMMAND_UTILS_H
-#define __KIS_COMMAND_UTILS_H
-
-#include "kundo2command.h"
-#include "kis_undo_stores.h"
-#include "kritaimage_export.h"
-
-namespace KisCommandUtils
-{
- struct KRITAIMAGE_EXPORT AggregateCommand : public KUndo2Command {
- AggregateCommand();
-
- void redo();
- void undo();
-
- protected:
- virtual void populateChildCommands() = 0;
- void addCommand(KUndo2Command *cmd);
-
- private:
- bool m_firstRedo;
- KisSurrogateUndoStore m_store;
- };
-
- struct KRITAIMAGE_EXPORT SkipFirstRedoWrapper : public KUndo2Command {
- SkipFirstRedoWrapper(KUndo2Command *child = 0, KUndo2Command *parent = 0);
- void redo();
- void undo();
-
- private:
- bool m_firstRedo;
- QScopedPointer<KUndo2Command> m_child;
- };
-
- struct KRITAIMAGE_EXPORT FlipFlopCommand : public KUndo2Command {
- FlipFlopCommand(bool finalize, KUndo2Command *parent = 0);
-
- void redo();
- void undo();
-
- protected:
- virtual void init();
- virtual void end();
- bool isFinalizing() const { return m_finalize; }
- bool isFirstRedo() const { return m_firstRedo; }
-
- private:
- bool m_finalize;
- bool m_firstRedo;
- };
-
- struct KRITAIMAGE_EXPORT CompositeCommand : public KUndo2Command {
- CompositeCommand(KUndo2Command *parent = 0);
- ~CompositeCommand();
-
- void addCommand(KUndo2Command *cmd);
-
- void redo();
- void undo();
-
- private:
- QVector<KUndo2Command*> m_commands;
- };
-}
-
-#endif /* __KIS_COMMAND_UTILS_H */
diff --git a/libs/image/kis_cubic_curve.cpp b/libs/image/kis_cubic_curve.cpp
index bb3f872280..43ca6f5d54 100644
--- a/libs/image/kis_cubic_curve.cpp
+++ b/libs/image/kis_cubic_curve.cpp
@@ -1,484 +1,477 @@
/*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2009 Dmitry Kazakov <dimula73@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_cubic_curve.h"
#include <QPointF>
#include <QList>
#include <QSharedData>
#include <QStringList>
#include "kis_dom_utils.h"
template <typename T>
class KisTridiagonalSystem
{
/*
* e.g.
* |b0 c0 0 0 0| |x0| |f0|
* |a0 b1 c1 0 0| |x1| |f1|
* |0 a1 b2 c2 0|*|x2|=|f2|
* |0 0 a2 b3 c3| |x3| |f3|
* |0 0 0 a3 b4| |x4| |f4|
*/
public:
/**
* @return - vector that is storing x[]
*/
static
QVector<T> calculate(QList<T> &a,
QList<T> &b,
QList<T> &c,
QList<T> &f) {
QVector<T> x;
QVector<T> alpha;
QVector<T> beta;
int i;
int size = b.size();
Q_ASSERT(a.size() == size - 1 &&
c.size() == size - 1 &&
f.size() == size);
x.resize(size);
/**
* Check for special case when
* order of the matrix is equal to 1
*/
if (size == 1) {
x[0] = f[0] / b[0];
return x;
}
/**
* Common case
*/
alpha.resize(size);
beta.resize(size);
alpha[1] = -c[0] / b[0];
beta[1] = f[0] / b[0];
for (i = 1; i < size - 1; i++) {
alpha[i+1] = -c[i] /
(a[i-1] * alpha[i] + b[i]);
beta[i+1] = (f[i] - a[i-1] * beta[i])
/
(a[i-1] * alpha[i] + b[i]);
}
x.last() = (f.last() - a.last() * beta.last())
/
(b.last() + a.last() * alpha.last());
for (i = size - 2; i >= 0; i--)
x[i] = alpha[i+1] * x[i+1] + beta[i+1];
return x;
}
};
template <typename T_point, typename T>
class KisCubicSpline
{
/**
* s[i](x)=a[i] +
* b[i] * (x-x[i]) +
* 1/2 * c[i] * (x-x[i])^2 +
* 1/6 * d[i] * (x-x[i])^3
*
* h[i]=x[i+1]-x[i]
*
*/
protected:
QList<T> m_a;
QVector<T> m_b;
QVector<T> m_c;
QVector<T> m_d;
QVector<T> m_h;
T m_begin;
T m_end;
int m_intervals;
public:
KisCubicSpline() {}
KisCubicSpline(const QList<T_point> &a) {
createSpline(a);
}
/**
* Create new spline and precalculate some values
* for future
*
* @a - base points of the spline
*/
void createSpline(const QList<T_point> &a) {
int intervals = m_intervals = a.size() - 1;
int i;
m_begin = a.first().x();
m_end = a.last().x();
m_a.clear();
m_b.resize(intervals);
m_c.clear();
m_d.resize(intervals);
m_h.resize(intervals);
for (i = 0; i < intervals; i++) {
m_h[i] = a[i+1].x() - a[i].x();
m_a.append(a[i].y());
}
m_a.append(a.last().y());
QList<T> tri_b;
QList<T> tri_f;
QList<T> tri_a; /* equals to @tri_c */
for (i = 0; i < intervals - 1; i++) {
tri_b.append(2.*(m_h[i] + m_h[i+1]));
tri_f.append(6.*((m_a[i+2] - m_a[i+1]) / m_h[i+1] - (m_a[i+1] - m_a[i]) / m_h[i]));
}
for (i = 1; i < intervals - 1; i++)
tri_a.append(m_h[i]);
if (intervals > 1) {
m_c = KisTridiagonalSystem<T>::calculate(tri_a, tri_b, tri_a, tri_f);
}
m_c.prepend(0);
m_c.append(0);
for (i = 0; i < intervals; i++)
m_d[i] = (m_c[i+1] - m_c[i]) / m_h[i];
for (i = 0; i < intervals; i++)
m_b[i] = -0.5 * (m_c[i] * m_h[i]) - (1 / 6.0) * (m_d[i] * m_h[i] * m_h[i]) + (m_a[i+1] - m_a[i]) / m_h[i];
}
/**
* Get value of precalculated spline in the point @x
*/
T getValue(T x) const {
T x0;
int i = findRegion(x, x0);
/* TODO: check for asm equivalent */
return m_a[i] +
m_b[i] *(x - x0) +
0.5 * m_c[i] *(x - x0) *(x - x0) +
(1 / 6.0)* m_d[i] *(x - x0) *(x - x0) *(x - x0);
}
T begin() const {
return m_begin;
}
T end() const {
return m_end;
}
protected:
/**
* findRegion - Searches for the region containing @x
* @x0 - out parameter, containing beginning of the region
* @return - index of the region
*/
int findRegion(T x, T &x0) const {
int i;
x0 = m_begin;
for (i = 0; i < m_intervals; i++) {
if (x >= x0 && x < x0 + m_h[i])
return i;
x0 += m_h[i];
}
if (x >= x0) {
x0 -= m_h[m_intervals-1];
return m_intervals - 1;
}
qDebug("X value: %f\n", x);
qDebug("m_begin: %f\n", m_begin);
qDebug("m_end : %f\n", m_end);
Q_ASSERT_X(0, "findRegion", "X value is outside regions");
/* **never reached** */
return -1;
}
};
static bool pointLessThan(const QPointF &a, const QPointF &b)
{
return a.x() < b.x();
}
struct Q_DECL_HIDDEN KisCubicCurve::Data : public QSharedData {
Data() {
init();
}
Data(const Data& data) : QSharedData() {
init();
points = data.points;
name = data.name;
}
void init() {
validSpline = false;
validU16Transfer = false;
validFTransfer = false;
}
~Data() {
}
mutable QString name;
mutable KisCubicSpline<QPointF, qreal> spline;
QList<QPointF> points;
mutable bool validSpline;
mutable QVector<quint8> u8Transfer;
mutable bool validU8Transfer;
mutable QVector<quint16> u16Transfer;
mutable bool validU16Transfer;
mutable QVector<qreal> fTransfer;
mutable bool validFTransfer;
void updateSpline();
void keepSorted();
qreal value(qreal x);
void invalidate();
template<typename _T_, typename _T2_>
void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size);
};
void KisCubicCurve::Data::updateSpline()
{
if (validSpline) return;
validSpline = true;
spline.createSpline(points);
}
void KisCubicCurve::Data::invalidate()
{
validSpline = false;
validFTransfer = false;
validU16Transfer = false;
}
void KisCubicCurve::Data::keepSorted()
{
qSort(points.begin(), points.end(), pointLessThan);
}
qreal KisCubicCurve::Data::value(qreal x)
{
updateSpline();
/* Automatically extend non-existing parts of the curve
* (e.g. before the first point) and cut off big y-values
*/
x = qBound(spline.begin(), x, spline.end());
qreal y = spline.getValue(x);
return qBound(qreal(0.0), y, qreal(1.0));
}
template<typename _T_, typename _T2_>
void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size)
{
if (!valid || transfer->size() != size) {
if (transfer->size() != size) {
transfer->resize(size);
}
qreal end = 1.0 / (size - 1);
for (int i = 0; i < size; ++i) {
/* Direct uncached version */
_T2_ val = value(i * end ) * max;
val = qBound(min, val, max);
(*transfer)[i] = val;
}
valid = true;
}
}
struct Q_DECL_HIDDEN KisCubicCurve::Private {
QSharedDataPointer<Data> data;
};
KisCubicCurve::KisCubicCurve() : d(new Private)
{
d->data = new Data;
QPointF p;
p.rx() = 0.0; p.ry() = 0.0;
d->data->points.append(p);
p.rx() = 1.0; p.ry() = 1.0;
d->data->points.append(p);
}
KisCubicCurve::KisCubicCurve(const QList<QPointF>& points) : d(new Private)
{
d->data = new Data;
d->data->points = points;
d->data->keepSorted();
}
KisCubicCurve::KisCubicCurve(const KisCubicCurve& curve)
: d(new Private(*curve.d))
{
}
KisCubicCurve::~KisCubicCurve()
{
delete d;
}
KisCubicCurve& KisCubicCurve::operator=(const KisCubicCurve & curve)
{
if (&curve != this) {
*d = *curve.d;
}
return *this;
}
bool KisCubicCurve::operator==(const KisCubicCurve& curve) const
{
if (d->data == curve.d->data) return true;
return d->data->points == curve.d->data->points;
}
qreal KisCubicCurve::value(qreal x) const
{
qreal value = d->data->value(x);
return value;
}
QList<QPointF> KisCubicCurve::points() const
{
return d->data->points;
}
void KisCubicCurve::setPoints(const QList<QPointF>& points)
{
d->data.detach();
d->data->points = points;
d->data->invalidate();
}
void KisCubicCurve::setPoint(int idx, const QPointF& point)
{
d->data.detach();
d->data->points[idx] = point;
d->data->keepSorted();
d->data->invalidate();
}
int KisCubicCurve::addPoint(const QPointF& point)
{
d->data.detach();
d->data->points.append(point);
d->data->keepSorted();
d->data->invalidate();
return d->data->points.indexOf(point);
}
void KisCubicCurve::removePoint(int idx)
{
d->data.detach();
d->data->points.removeAt(idx);
d->data->invalidate();
}
bool KisCubicCurve::isNull() const
{
const QList<QPointF> &points = d->data->points;
Q_FOREACH (const QPointF &pt, points) {
if (!qFuzzyCompare(pt.x(), pt.y())) {
return false;
}
}
return true;
}
const QString& KisCubicCurve::name() const
{
return d->data->name;
}
void KisCubicCurve::setName(const QString& name)
{
d->data->name = name;
}
QString KisCubicCurve::toString() const
{
QString sCurve;
if(d->data->points.count() < 1)
return sCurve;
Q_FOREACH (const QPointF & pair, d->data->points) {
sCurve += QString::number(pair.x());
sCurve += ',';
sCurve += QString::number(pair.y());
sCurve += ';';
}
return sCurve;
}
void KisCubicCurve::fromString(const QString& string)
{
QStringList data = string.split(';');
QList<QPointF> points;
Q_FOREACH (const QString & pair, data) {
if (pair.indexOf(',') > -1) {
QPointF p;
p.rx() = KisDomUtils::toDouble(pair.section(',', 0, 0));
p.ry() = KisDomUtils::toDouble(pair.section(',', 1, 1));
points.append(p);
}
}
setPoints(points);
}
-const QVector<quint8> KisCubicCurve::uint8Transfer(int size) const
-{
- d->data->updateTransfer<quint8, int>(&d->data->u8Transfer, d->data->validU8Transfer, 0x0, 0xFF, size);
- return d->data->u8Transfer;
-}
-
-
const QVector<quint16> KisCubicCurve::uint16Transfer(int size) const
{
d->data->updateTransfer<quint16, int>(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size);
return d->data->u16Transfer;
}
const QVector<qreal> KisCubicCurve::floatTransfer(int size) const
{
d->data->updateTransfer<qreal, qreal>(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size);
return d->data->fTransfer;
}
diff --git a/libs/image/kis_cubic_curve.h b/libs/image/kis_cubic_curve.h
index 2a6cf414d3..b5af941559 100644
--- a/libs/image/kis_cubic_curve.h
+++ b/libs/image/kis_cubic_curve.h
@@ -1,81 +1,80 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_CUBIC_CURVE_H_
#define _KIS_CUBIC_CURVE_H_
#include<QList>
#include<QVector>
#include<QVariant>
#include <kritaimage_export.h>
class QPointF;
const QString DEFAULT_CURVE_STRING = "0,0;1,1;";
/**
* Hold the data for a cubic curve.
*/
class KRITAIMAGE_EXPORT KisCubicCurve
{
public:
KisCubicCurve();
KisCubicCurve(const QList<QPointF>& points);
KisCubicCurve(const QVector<QPointF>& points);
KisCubicCurve(const KisCubicCurve& curve);
~KisCubicCurve();
KisCubicCurve& operator=(const KisCubicCurve& curve);
bool operator==(const KisCubicCurve& curve) const;
public:
qreal value(qreal x) const;
QList<QPointF> points() const;
void setPoints(const QList<QPointF>& points);
void setPoint(int idx, const QPointF& point);
/**
* Add a point to the curve, the list of point is always sorted.
* @return the index of the inserted point
*/
int addPoint(const QPointF& point);
void removePoint(int idx);
bool isNull() const;
/**
* This allows us to carry around a display name for the curve internally. It is used
* currently in Sketch for perchannel, but would potentially be useful anywhere
* curves are used in the UI
*/
void setName(const QString& name);
const QString& name() const;
public:
- const QVector<quint8> uint8Transfer(int size = 256) const;
const QVector<quint16> uint16Transfer(int size = 256) const;
const QVector<qreal> floatTransfer(int size = 256) const;
public:
QString toString() const;
void fromString(const QString&);
private:
struct Data;
struct Private;
Private* const d;
};
Q_DECLARE_METATYPE(KisCubicCurve)
#endif
diff --git a/libs/image/kis_external_layer_iface.h b/libs/image/kis_external_layer_iface.h
index d10fdd39e8..22a9f653ca 100644
--- a/libs/image/kis_external_layer_iface.h
+++ b/libs/image/kis_external_layer_iface.h
@@ -1,63 +1,67 @@
/*
* Copyright (c) 2006 Bart Coppens <kde@bartcoppens.be>
* 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_EXTERNAL_LAYER_IFACE_
#define KIS_EXTERNAL_LAYER_IFACE_
#include "kis_icon_utils.h"
#include "kis_types.h"
#include "kis_image.h"
#include "kis_layer.h"
class QString;
class QIcon;
class KUndo2Command;
/**
A base interface for layers that are implemented outside the Krita
core.
*/
class KisExternalLayer : public KisLayer
{
public:
KisExternalLayer(KisImageWSP image, const QString &name, quint8 opacity)
: KisLayer(image, name, opacity) {}
virtual QIcon icon() const {
return KisIconUtils::loadIcon("view-refresh");
}
virtual void resetCache() {
}
virtual KUndo2Command* crop(const QRect & rect) {
Q_UNUSED(rect);
return 0;
}
virtual KUndo2Command* transform(const QTransform &transform) {
Q_UNUSED(transform);
return 0;
}
+ virtual bool supportsPerspectiveTransform() const {
+ return false;
+ }
+
};
#endif // KIS_EXTERNAL_IFACE_LAYER_IFACE_
diff --git a/libs/image/kis_idle_watcher.cpp b/libs/image/kis_idle_watcher.cpp
index cf6ac565b0..b450676e7f 100644
--- a/libs/image/kis_idle_watcher.cpp
+++ b/libs/image/kis_idle_watcher.cpp
@@ -1,128 +1,130 @@
/*
* 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_idle_watcher.h"
#include <QTimer>
#include "kis_image.h"
#include "kis_signal_auto_connection.h"
#include "kis_signal_compressor.h"
struct KisIdleWatcher::Private
{
static const int IDLE_CHECK_PERIOD = 200; /* ms */
static const int IDLE_CHECK_COUNT = 4; /* ticks */
Private(int delay)
: imageModifiedCompressor(delay,
KisSignalCompressor::POSTPONE),
idleCheckCounter(0)
{
idleCheckTimer.setSingleShot(true);
idleCheckTimer.setInterval(IDLE_CHECK_PERIOD);
}
KisSignalAutoConnectionsStore connectionsStore;
QVector<KisImageWSP> trackedImages;
KisSignalCompressor imageModifiedCompressor;
QTimer idleCheckTimer;
int idleCheckCounter;
};
KisIdleWatcher::KisIdleWatcher(int delay, QObject *parent)
: QObject(parent), m_d(new Private(delay))
{
connect(&m_d->imageModifiedCompressor, SIGNAL(timeout()), SLOT(startIdleCheck()));
connect(&m_d->idleCheckTimer, SIGNAL(timeout()), SLOT(slotIdleCheckTick()));
}
KisIdleWatcher::~KisIdleWatcher()
{
}
bool KisIdleWatcher::isIdle() const
{
bool idle = true;
Q_FOREACH (KisImageSP image, m_d->trackedImages) {
if (!image) continue;
if (!image->isIdle()) {
idle = false;
break;
}
}
return idle;
}
void KisIdleWatcher::setTrackedImages(const QVector<KisImageSP> &images)
{
m_d->connectionsStore.clear();
m_d->trackedImages.clear();
Q_FOREACH (KisImageSP image, images) {
- m_d->trackedImages << image;
- m_d->connectionsStore.addConnection(image, SIGNAL(sigImageModified()),
- this, SLOT(slotImageModified()));
+ if (image) {
+ m_d->trackedImages << image;
+ m_d->connectionsStore.addConnection(image, SIGNAL(sigImageModified()),
+ this, SLOT(slotImageModified()));
+ }
}
}
void KisIdleWatcher::setTrackedImage(KisImageSP image)
{
QVector<KisImageSP> images;
images << image;
setTrackedImages(images);
}
void KisIdleWatcher::slotImageModified()
{
stopIdleCheck();
m_d->imageModifiedCompressor.start();
}
void KisIdleWatcher::startIdleCheck()
{
m_d->idleCheckCounter = 0;
m_d->idleCheckTimer.start();
}
void KisIdleWatcher::stopIdleCheck()
{
m_d->idleCheckTimer.stop();
m_d->idleCheckCounter = 0;
}
void KisIdleWatcher::slotIdleCheckTick()
{
if (isIdle()) {
if (m_d->idleCheckCounter >= Private::IDLE_CHECK_COUNT) {
stopIdleCheck();
emit startedIdleMode();
} else {
m_d->idleCheckCounter++;
m_d->idleCheckTimer.start();
}
} else {
slotImageModified();
}
}
diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc
index 98e413a25b..73393e296e 100644
--- a/libs/image/kis_image.cc
+++ b/libs/image/kis_image.cc
@@ -1,1701 +1,1706 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_image.h"
#include <KoConfig.h> // WORDS_BIGENDIAN
#include <stdlib.h>
#include <math.h>
#include <QImage>
#include <QPainter>
#include <QSize>
#include <QDateTime>
#include <QRect>
#include <QRegion>
#include <QtConcurrent>
#include <klocalizedstring.h>
#include "KoColorSpaceRegistry.h"
#include "KoColor.h"
#include "KoColorProfile.h"
#include <KoCompositeOpRegistry.h>
#include "KisProofingConfiguration.h"
#include "recorder/kis_action_recorder.h"
#include "kis_adjustment_layer.h"
#include "kis_annotation.h"
#include "kis_change_profile_visitor.h"
#include "kis_colorspace_convert_visitor.h"
#include "kis_count_visitor.h"
#include "kis_filter_strategy.h"
#include "kis_group_layer.h"
#include "commands/kis_image_commands.h"
#include "kis_layer.h"
#include "kis_meta_data_merge_strategy_registry.h"
#include "kis_name_server.h"
#include "kis_paint_layer.h"
#include "kis_painter.h"
#include "kis_selection.h"
#include "kis_transaction.h"
#include "kis_meta_data_merge_strategy.h"
#include "kis_memory_statistics_server.h"
#include "kis_image_config.h"
#include "kis_update_scheduler.h"
#include "kis_image_signal_router.h"
#include "kis_image_animation_interface.h"
#include "kis_stroke_strategy.h"
#include "kis_image_barrier_locker.h"
#include "kis_undo_stores.h"
#include "kis_legacy_undo_adapter.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_transform_worker.h"
#include "kis_processing_applicator.h"
#include "processing/kis_crop_processing_visitor.h"
#include "processing/kis_crop_selections_processing_visitor.h"
#include "processing/kis_transform_processing_visitor.h"
#include "commands_new/kis_image_resize_command.h"
#include "commands_new/kis_image_set_resolution_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "kis_composite_progress_proxy.h"
#include "kis_layer_composition.h"
#include "kis_wrapped_rect.h"
#include "kis_crop_saved_extra_data.h"
#include "kis_layer_utils.h"
#include "kis_lod_transform.h"
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include "kis_sync_lod_cache_stroke_strategy.h"
#include "kis_projection_updates_filter.h"
#include "kis_layer_projection_plane.h"
#include "kis_update_time_monitor.h"
#include "kis_image_barrier_locker.h"
#include <QtCore>
#include <functional>
#include "kis_time_range.h"
// #define SANITY_CHECKS
#ifdef SANITY_CHECKS
#define SANITY_CHECK_LOCKED(name) \
if (!locked()) warnKrita() << "Locking policy failed:" << name \
<< "has been called without the image" \
"being locked";
#else
#define SANITY_CHECK_LOCKED(name)
#endif
struct KisImageSPStaticRegistrar {
KisImageSPStaticRegistrar() {
qRegisterMetaType<KisImageSP>("KisImageSP");
}
};
static KisImageSPStaticRegistrar __registrar;
class KisImage::KisImagePrivate
{
public:
KisImagePrivate(KisImage *_q, qint32 w, qint32 h,
const KoColorSpace *c,
KisUndoStore *undo,
KisImageAnimationInterface *_animationInterface)
: q(_q)
, lockedForReadOnly(false)
, width(w)
, height(h)
, colorSpace(c ? c : KoColorSpaceRegistry::instance()->rgb8())
, nserver(1)
, undoStore(undo ? undo : new KisDumbUndoStore())
, legacyUndoAdapter(undoStore.data(), _q)
, postExecutionUndoAdapter(undoStore.data(), _q)
, recorder(_q)
, signalRouter(_q)
, animationInterface(_animationInterface)
, scheduler(_q)
, axesCenter(QPointF(0.5, 0.5))
{
{
KisImageConfig cfg;
if (cfg.enableProgressReporting()) {
scheduler.setProgressProxy(&compositeProgressProxy);
}
// Each of these lambdas defines a new factory function.
scheduler.setLod0ToNStrokeStrategyFactory(
[=](bool forgettable) {
return KisLodSyncPair(
new KisSyncLodCacheStrokeStrategy(KisImageWSP(q), forgettable),
KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(q)));
});
scheduler.setSuspendUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), true),
KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(q)));
});
scheduler.setResumeUpdatesStrokeStrategyFactory(
[=]() {
return KisSuspendResumePair(
new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(q), false),
KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(q)));
});
}
connect(q, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged()));
}
~KisImagePrivate() {
/**
* Stop animation interface. It may use the rootLayer.
*/
delete animationInterface;
/**
* First delete the nodes, while strokes
* and undo are still alive
*/
rootLayer.clear();
}
KisImage *q;
quint32 lockCount = 0;
bool lockedForReadOnly;
qint32 width;
qint32 height;
double xres = 1.0;
double yres = 1.0;
const KoColorSpace * colorSpace;
KisProofingConfigurationSP proofingConfig;
KisSelectionSP deselectedGlobalSelection;
KisGroupLayerSP rootLayer; // The layers are contained in here
QList<KisLayerCompositionSP> compositions;
KisNodeSP isolatedRootNode;
bool wrapAroundModePermitted = false;
KisNameServer nserver;
QScopedPointer<KisUndoStore> undoStore;
KisLegacyUndoAdapter legacyUndoAdapter;
KisPostExecutionUndoAdapter postExecutionUndoAdapter;
KisActionRecorder recorder;
vKisAnnotationSP annotations;
QAtomicInt disableUIUpdateSignals;
KisProjectionUpdatesFilterSP projectionUpdatesFilter;
KisImageSignalRouter signalRouter;
KisImageAnimationInterface *animationInterface;
KisUpdateScheduler scheduler;
QAtomicInt disableDirtyRequests;
KisCompositeProgressProxy compositeProgressProxy;
bool blockLevelOfDetail = false;
QPointF axesCenter;
bool tryCancelCurrentStrokeAsync();
void notifyProjectionUpdatedInPatches(const QRect &rc);
};
KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, KisShared()
, m_d(new KisImagePrivate(this, width, height,
colorSpace, undoStore,
new KisImageAnimationInterface(this)))
{
setObjectName(name);
setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8));
}
KisImage::~KisImage()
{
dbgImage << "deleting kisimage" << objectName();
/**
* Request the tools to end currently running strokes
*/
waitForDone();
delete m_d;
disconnect(); // in case Qt gets confused
}
KisImage *KisImage::clone(bool exactCopy)
{
return new KisImage(*this, 0, exactCopy);
}
KisImage::KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy)
: KisNodeFacade(),
KisNodeGraphListener(),
KisShared(),
m_d(new KisImagePrivate(this,
rhs.width(), rhs.height(),
rhs.colorSpace(),
undoStore ? undoStore : new KisDumbUndoStore(),
new KisImageAnimationInterface(*rhs.animationInterface(), this)))
{
setObjectName(rhs.objectName());
m_d->xres = rhs.m_d->xres;
m_d->yres = rhs.m_d->yres;
if (rhs.m_d->proofingConfig) {
m_d->proofingConfig = toQShared(new KisProofingConfiguration(*rhs.m_d->proofingConfig));
}
KisNodeSP newRoot = rhs.root()->clone();
newRoot->setGraphListener(this);
newRoot->setImage(this);
m_d->rootLayer = dynamic_cast<KisGroupLayer*>(newRoot.data());
setRoot(newRoot);
if (exactCopy) {
QQueue<KisNodeSP> linearizedNodes;
KisLayerUtils::recursiveApplyNodes(rhs.root(),
[&linearizedNodes](KisNodeSP node) {
linearizedNodes.enqueue(node);
});
KisLayerUtils::recursiveApplyNodes(newRoot,
[&linearizedNodes](KisNodeSP node) {
KisNodeSP refNode = linearizedNodes.dequeue();
node->setUuid(refNode->uuid());
});
}
Q_FOREACH (KisLayerCompositionSP comp, rhs.m_d->compositions) {
m_d->compositions << toQShared(new KisLayerComposition(*comp, this));
}
rhs.m_d->nserver = KisNameServer(rhs.m_d->nserver);
vKisAnnotationSP newAnnotations;
Q_FOREACH (KisAnnotationSP annotation, rhs.m_d->annotations) {
newAnnotations << annotation->clone();
}
m_d->annotations = newAnnotations;
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->projectionUpdatesFilter);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableUIUpdateSignals);
KIS_ASSERT_RECOVER_NOOP(!rhs.m_d->disableDirtyRequests);
m_d->blockLevelOfDetail = rhs.m_d->blockLevelOfDetail;
}
void KisImage::aboutToAddANode(KisNode *parent, int index)
{
KisNodeGraphListener::aboutToAddANode(parent, index);
SANITY_CHECK_LOCKED("aboutToAddANode");
}
void KisImage::nodeHasBeenAdded(KisNode *parent, int index)
{
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
SANITY_CHECK_LOCKED("nodeHasBeenAdded");
m_d->signalRouter.emitNodeHasBeenAdded(parent, index);
KisNodeSP newNode = parent->at(index);
if (!dynamic_cast<KisSelectionMask*>(newNode.data())) {
stopIsolatedMode();
}
}
void KisImage::aboutToRemoveANode(KisNode *parent, int index)
{
KisNodeSP deletedNode = parent->at(index);
if (!dynamic_cast<KisSelectionMask*>(deletedNode.data())) {
stopIsolatedMode();
}
KisNodeGraphListener::aboutToRemoveANode(parent, index);
SANITY_CHECK_LOCKED("aboutToRemoveANode");
m_d->signalRouter.emitAboutToRemoveANode(parent, index);
}
void KisImage::nodeChanged(KisNode* node)
{
KisNodeGraphListener::nodeChanged(node);
requestStrokeEnd();
m_d->signalRouter.emitNodeChanged(node);
}
void KisImage::invalidateAllFrames()
{
invalidateFrames(KisTimeRange::infinite(0), QRect());
}
KisSelectionSP KisImage::globalSelection() const
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (selectionMask) {
return selectionMask->selection();
} else {
return 0;
}
}
void KisImage::setGlobalSelection(KisSelectionSP globalSelection)
{
KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask();
if (!globalSelection) {
if (selectionMask) {
removeNode(selectionMask);
}
}
else {
if (!selectionMask) {
selectionMask = new KisSelectionMask(this);
selectionMask->initSelection(m_d->rootLayer);
addNode(selectionMask);
// If we do not set the selection now, the setActive call coming next
// can be very, very expensive, depending on the size of the image.
selectionMask->setSelection(globalSelection);
selectionMask->setActive(true);
}
else {
selectionMask->setSelection(globalSelection);
}
Q_ASSERT(m_d->rootLayer->childCount() > 0);
Q_ASSERT(m_d->rootLayer->selectionMask());
}
m_d->deselectedGlobalSelection = 0;
m_d->legacyUndoAdapter.emitSelectionChanged();
}
void KisImage::deselectGlobalSelection()
{
KisSelectionSP savedSelection = globalSelection();
setGlobalSelection(0);
m_d->deselectedGlobalSelection = savedSelection;
}
bool KisImage::canReselectGlobalSelection()
{
return m_d->deselectedGlobalSelection;
}
void KisImage::reselectGlobalSelection()
{
if(m_d->deselectedGlobalSelection) {
setGlobalSelection(m_d->deselectedGlobalSelection);
}
}
QString KisImage::nextLayerName(const QString &_baseName) const
{
QString baseName = _baseName;
if (m_d->nserver.currentSeed() == 0) {
m_d->nserver.number();
return i18n("background");
}
if (baseName.isEmpty()) {
baseName = i18n("Layer");
}
return QString("%1 %2").arg(baseName).arg(m_d->nserver.number());
}
void KisImage::rollBackLayerName()
{
m_d->nserver.rollback();
}
KisCompositeProgressProxy* KisImage::compositeProgressProxy()
{
return &m_d->compositeProgressProxy;
}
bool KisImage::locked() const
{
return m_d->lockCount != 0;
}
void KisImage::barrierLock(bool readOnly)
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.barrierLock();
m_d->lockedForReadOnly = readOnly;
} else {
m_d->lockedForReadOnly &= readOnly;
}
m_d->lockCount++;
}
bool KisImage::tryBarrierLock(bool readOnly)
{
bool result = true;
if (!locked()) {
result = m_d->scheduler.tryBarrierLock();
m_d->lockedForReadOnly = readOnly;
}
if (result) {
m_d->lockCount++;
m_d->lockedForReadOnly &= readOnly;
}
return result;
}
bool KisImage::isIdle(bool allowLocked)
{
return (allowLocked || !locked()) && m_d->scheduler.isIdle();
}
void KisImage::lock()
{
if (!locked()) {
requestStrokeEnd();
m_d->scheduler.lock();
}
m_d->lockCount++;
m_d->lockedForReadOnly = false;
}
void KisImage::unlock()
{
Q_ASSERT(locked());
if (locked()) {
m_d->lockCount--;
if (m_d->lockCount == 0) {
m_d->scheduler.unlock(!m_d->lockedForReadOnly);
}
}
}
void KisImage::blockUpdates()
{
m_d->scheduler.blockUpdates();
}
void KisImage::unblockUpdates()
{
m_d->scheduler.unblockUpdates();
}
void KisImage::setSize(const QSize& size)
{
m_d->width = size.width();
m_d->height = size.height();
}
void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers)
{
if (newRect == bounds() && !cropLayers) return;
KUndo2MagicString actionName = cropLayers ?
kundo2_i18n("Crop Image") :
kundo2_i18n("Resize Image");
KisImageSignalVector emitSignals;
emitSignals << ComplexSizeChangedSignal(newRect, newRect.size());
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(cropLayers ?
KisCropSavedExtraData::CROP_IMAGE :
KisCropSavedExtraData::RESIZE_IMAGE,
newRect);
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE |
KisProcessingApplicator::NO_UI_UPDATES,
emitSignals, actionName, extraData);
if (cropLayers || !newRect.topLeft().isNull()) {
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, cropLayers, true);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
}
applicator.applyCommand(new KisImageResizeCommand(this, newRect.size()));
applicator.end();
}
void KisImage::resizeImage(const QRect& newRect)
{
resizeImageImpl(newRect, false);
}
void KisImage::cropImage(const QRect& newRect)
{
resizeImageImpl(newRect, true);
}
void KisImage::cropNode(KisNodeSP node, const QRect& newRect)
{
bool isLayer = qobject_cast<KisLayer*>(node.data());
KUndo2MagicString actionName = isLayer ?
kundo2_i18n("Crop Layer") :
kundo2_i18n("Crop Mask");
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisCropSavedExtraData *extraData =
new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER,
newRect, node);
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName, extraData);
KisProcessingVisitorSP visitor =
new KisCropProcessingVisitor(newRect, true, false);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy)
{
bool resolutionChanged = xres != xRes() && yres != yRes();
bool sizeChanged = size != this->size();
if (!resolutionChanged && !sizeChanged) return;
KisImageSignalVector emitSignals;
if (resolutionChanged) emitSignals << ResolutionChangedSignal;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size);
emitSignals << ModifiedSignal;
KUndo2MagicString actionName = sizeChanged ?
kundo2_i18n("Scale Image") :
kundo2_i18n("Change Image Resolution");
KisProcessingApplicator::ProcessingFlags signalFlags =
(resolutionChanged || sizeChanged) ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, m_d->rootLayer,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
qreal sx = qreal(size.width()) / this->size().width();
qreal sy = qreal(size.height()) / this->size().height();
QTransform shapesCorrection;
if (resolutionChanged) {
shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres);
}
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(sx, sy,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy,
shapesCorrection);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resolutionChanged) {
KUndo2Command *parent =
new KisResetShapesCommand(m_d->rootLayer);
new KisImageSetResolutionCommand(this, xres, yres, parent);
applicator.applyCommand(parent);
}
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, size));
}
applicator.end();
}
void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy)
{
KUndo2MagicString actionName(kundo2_i18n("Scale Layer"));
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(this, node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(scaleX, scaleY,
0, 0,
QPointF(),
0,
0, 0,
filterStrategy);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
void KisImage::rotateImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double radians)
{
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
0, 0, 0, 0,
radians,
0, 0, 0, 0);
QTransform transform = worker.transform();
if (resizeImage) {
QRect newRect = transform.mapRect(bounds());
newSize = newRect.size();
offset = -newRect.topLeft();
}
else {
QPointF origin = QRectF(rootNode->exactBounds()).center();
newSize = size();
offset = -(transform.map(origin) - origin);
}
}
bool sizeChanged = resizeImage &&
(newSize.width() != width() || newSize.height() != height());
// These signals will be emitted after processing is done
KisImageSignalVector emitSignals;
if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
emitSignals << ModifiedSignal;
// These flags determine whether updates are transferred to the UI during processing
KisProcessingApplicator::ProcessingFlags signalFlags =
sizeChanged ?
KisProcessingApplicator::NO_UI_UPDATES :
KisProcessingApplicator::NONE;
KisProcessingApplicator applicator(this, rootNode,
KisProcessingApplicator::RECURSIVE | signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic");
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0,
QPointF(),
radians,
offset.x(), offset.y(),
filter);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (sizeChanged) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::rotateImage(double radians)
{
rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians);
}
void KisImage::rotateNode(KisNodeSP node, double radians)
{
if (node->inherits("KisMask")) {
rotateImpl(kundo2_i18n("Rotate Mask"), node, false, radians);
} else {
rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians);
}
}
void KisImage::shearImpl(const KUndo2MagicString &actionName,
KisNodeSP rootNode,
bool resizeImage,
double angleX, double angleY,
const QPointF &origin)
{
//angleX, angleY are in degrees
const qreal pi = 3.1415926535897932385;
const qreal deg2rad = pi / 180.0;
qreal tanX = tan(angleX * deg2rad);
qreal tanY = tan(angleY * deg2rad);
QPointF offset;
QSize newSize;
{
KisTransformWorker worker(0,
1.0, 1.0,
tanX, tanY, origin.x(), origin.y(),
0,
0, 0, 0, 0);
QRect newRect = worker.transform().mapRect(bounds());
newSize = newRect.size();
if (resizeImage) offset = -newRect.topLeft();
}
if (newSize == size()) return;
KisImageSignalVector emitSignals;
if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize);
emitSignals << ModifiedSignal;
KisProcessingApplicator::ProcessingFlags signalFlags =
KisProcessingApplicator::RECURSIVE;
if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES;
KisProcessingApplicator applicator(this, rootNode,
signalFlags,
emitSignals, actionName);
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear");
KisProcessingVisitorSP visitor =
new KisTransformProcessingVisitor(1.0, 1.0,
tanX, tanY, origin,
0,
offset.x(), offset.y(),
filter);
applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT);
if (resizeImage) {
applicator.applyCommand(new KisImageResizeCommand(this, newSize));
}
applicator.end();
}
void KisImage::shearNode(KisNodeSP node, double angleX, double angleY)
{
QPointF shearOrigin = QRectF(bounds()).center();
if (node->inherits("KisMask")) {
shearImpl(kundo2_i18n("Shear Mask"), node, false,
angleX, angleY, shearOrigin);
} else {
shearImpl(kundo2_i18n("Shear Layer"), node, false,
angleX, angleY, shearOrigin);
}
}
void KisImage::shear(double angleX, double angleY)
{
shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true,
angleX, angleY, QPointF());
}
void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags)
{
if (!dstColorSpace) return;
const KoColorSpace *srcColorSpace = m_d->colorSpace;
undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags);
m_d->rootLayer->accept(visitor);
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
bool KisImage::assignImageProfile(const KoColorProfile *profile)
{
if (!profile) return false;
const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
const KoColorSpace *srcCs = colorSpace();
if (!dstCs) return false;
m_d->colorSpace = dstCs;
KisChangeProfileVisitor visitor(srcCs, dstCs);
- return m_d->rootLayer->accept(visitor);
+ bool retval = m_d->rootLayer->accept(visitor);
+ m_d->signalRouter.emitNotification(ProfileChangedSignal);
+ return retval;
}
void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace)
{
if (*m_d->colorSpace == *dstColorSpace) return;
undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space"));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true));
undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace));
undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false));
undoAdapter()->endMacro();
setModified();
}
void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace)
{
m_d->colorSpace = colorSpace;
m_d->rootLayer->resetCache();
m_d->signalRouter.emitNotification(ColorSpaceChangedSignal);
}
const KoColorSpace * KisImage::colorSpace() const
{
return m_d->colorSpace;
}
const KoColorProfile * KisImage::profile() const
{
return colorSpace()->profile();
}
double KisImage::xRes() const
{
return m_d->xres;
}
double KisImage::yRes() const
{
return m_d->yres;
}
void KisImage::setResolution(double xres, double yres)
{
m_d->xres = xres;
m_d->yres = yres;
m_d->signalRouter.emitNotification(ResolutionChangedSignal);
}
QPointF KisImage::documentToPixel(const QPointF &documentCoord) const
{
return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes());
}
QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const
{
QPointF pixelCoord = documentToPixel(documentCoord);
return QPoint((int)pixelCoord.x(), (int)pixelCoord.y());
}
QRectF KisImage::documentToPixel(const QRectF &documentRect) const
{
return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight()));
}
QRect KisImage::documentToIntPixel(const QRectF &documentRect) const
{
return documentToPixel(documentRect).toAlignedRect();
}
QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const
{
return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes());
}
QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const
{
return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes());
}
QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const
{
return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight()));
}
qint32 KisImage::width() const
{
return m_d->width;
}
qint32 KisImage::height() const
{
return m_d->height;
}
KisGroupLayerSP KisImage::rootLayer() const
{
Q_ASSERT(m_d->rootLayer);
return m_d->rootLayer;
}
KisPaintDeviceSP KisImage::projection() const
{
if (m_d->isolatedRootNode) {
return m_d->isolatedRootNode->projection();
}
Q_ASSERT(m_d->rootLayer);
KisPaintDeviceSP projection = m_d->rootLayer->projection();
Q_ASSERT(projection);
return projection;
}
qint32 KisImage::nlayers() const
{
QStringList list;
list << "KisLayer";
KisCountVisitor visitor(list, KoProperties());
m_d->rootLayer->accept(visitor);
return visitor.count();
}
qint32 KisImage::nHiddenLayers() const
{
QStringList list;
list << "KisLayer";
KoProperties properties;
properties.setProperty("visible", false);
KisCountVisitor visitor(list, properties);
m_d->rootLayer->accept(visitor);
return visitor.count();
}
void KisImage::flatten()
{
KisLayerUtils::flattenImage(this);
}
void KisImage::mergeMultipleLayers(QList<KisNodeSP> mergedNodes, KisNodeSP putAfter)
{
if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) {
KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter);
}
}
void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
KisLayerUtils::mergeDown(this, layer, strategy);
}
void KisImage::flattenLayer(KisLayerSP layer)
{
KisLayerUtils::flattenLayer(this, layer);
}
void KisImage::setModified()
{
m_d->signalRouter.emitNotification(ModifiedSignal);
}
QImage KisImage::convertToQImage(QRect imageRect,
const KoColorProfile * profile)
{
qint32 x;
qint32 y;
qint32 w;
qint32 h;
imageRect.getRect(&x, &y, &w, &h);
return convertToQImage(x, y, w, h, profile);
}
QImage KisImage::convertToQImage(qint32 x,
qint32 y,
qint32 w,
qint32 h,
const KoColorProfile * profile)
{
KisPaintDeviceSP dev = projection();
if (!dev) return QImage();
QImage image = dev->convertToQImage(const_cast<KoColorProfile*>(profile), x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return image;
}
QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile)
{
if (scaledImageSize.isEmpty()) {
return QImage();
}
KisPaintDeviceSP dev = new KisPaintDevice(colorSpace());
KisPainter gc;
gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds());
gc.end();
double scaleX = qreal(scaledImageSize.width()) / width();
double scaleY = qreal(scaledImageSize.height()) / height();
QPointer<KoUpdater> updater = new KoDummyUpdater();
KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic"));
worker.run();
delete updater;
return dev->convertToQImage(profile);
}
void KisImage::notifyLayersChanged()
{
m_d->signalRouter.emitNotification(LayersChangedSignal);
}
QRect KisImage::bounds() const
{
return QRect(0, 0, width(), height());
}
QRect KisImage::effectiveLodBounds() const
{
QRect boundRect = bounds();
const int lod = currentLevelOfDetail();
if (lod > 0) {
KisLodTransform t(lod);
boundRect = t.map(boundRect);
}
return boundRect;
}
KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const
{
const int lod = currentLevelOfDetail();
return lod > 0 ?
m_d->scheduler.lodNPostExecutionUndoAdapter() :
&m_d->postExecutionUndoAdapter;
}
void KisImage::setUndoStore(KisUndoStore *undoStore)
{
m_d->legacyUndoAdapter.setUndoStore(undoStore);
m_d->postExecutionUndoAdapter.setUndoStore(undoStore);
m_d->undoStore.reset(undoStore);
}
KisUndoStore* KisImage::undoStore()
{
return m_d->undoStore.data();
}
KisUndoAdapter* KisImage::undoAdapter() const
{
return &m_d->legacyUndoAdapter;
}
KisActionRecorder* KisImage::actionRecorder() const
{
return &m_d->recorder;
}
void KisImage::setDefaultProjectionColor(const KoColor &color)
{
KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer);
m_d->rootLayer->setDefaultProjectionColor(color);
}
KoColor KisImage::defaultProjectionColor() const
{
KIS_ASSERT_RECOVER(m_d->rootLayer) {
return KoColor(Qt::transparent, m_d->colorSpace);
}
return m_d->rootLayer->defaultProjectionColor();
}
void KisImage::setRootLayer(KisGroupLayerSP rootLayer)
{
stopIsolatedMode();
KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace);
if (m_d->rootLayer) {
m_d->rootLayer->setGraphListener(0);
m_d->rootLayer->disconnect();
KisPaintDeviceSP original = m_d->rootLayer->original();
defaultProjectionColor = original->defaultPixel();
}
m_d->rootLayer = rootLayer;
m_d->rootLayer->disconnect();
m_d->rootLayer->setGraphListener(this);
m_d->rootLayer->setImage(this);
KisPaintDeviceSP newOriginal = m_d->rootLayer->original();
newOriginal->setDefaultPixel(defaultProjectionColor);
setRoot(m_d->rootLayer.data());
}
void KisImage::addAnnotation(KisAnnotationSP annotation)
{
// Find the icc annotation, if there is one
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == annotation->type()) {
*it = annotation;
return;
}
++it;
}
m_d->annotations.push_back(annotation);
}
KisAnnotationSP KisImage::annotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
return *it;
}
++it;
}
return KisAnnotationSP(0);
}
void KisImage::removeAnnotation(const QString& type)
{
vKisAnnotationSP_it it = m_d->annotations.begin();
while (it != m_d->annotations.end()) {
if ((*it)->type() == type) {
m_d->annotations.erase(it);
return;
}
++it;
}
}
vKisAnnotationSP_it KisImage::beginAnnotations()
{
return m_d->annotations.begin();
}
vKisAnnotationSP_it KisImage::endAnnotations()
{
return m_d->annotations.end();
}
void KisImage::notifyAboutToBeDeleted()
{
emit sigAboutToBeDeleted();
}
KisImageSignalRouter* KisImage::signalRouter()
{
return &m_d->signalRouter;
}
void KisImage::waitForDone()
{
requestStrokeEnd();
m_d->scheduler.waitForDone();
}
KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy)
{
/**
* Ask open strokes to end gracefully. All the strokes clients
* (including the one calling this method right now) will get
* a notification that they should probably end their strokes.
* However this is purely their choice whether to end a stroke
* or not.
*/
if (strokeStrategy->requestsOtherStrokesToEnd()) {
requestStrokeEnd();
}
/**
* Some of the strokes can cancel their work with undoing all the
* changes they did to the paint devices. The problem is that undo
* stack will know nothing about it. Therefore, just notify it
* explicitly
*/
if (strokeStrategy->clearsRedoOnStart()) {
m_d->undoStore->purgeRedoState();
}
return m_d->scheduler.startStroke(strokeStrategy);
}
void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc)
{
KisImageConfig imageConfig;
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < rc.height(); y += patchHeight) {
for (int x = 0; x < rc.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
patchRect &= rc;
QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect));
}
}
}
bool KisImage::startIsolatedMode(KisNodeSP node)
{
if (!tryBarrierLock()) return false;
unlock();
m_d->isolatedRootNode = node;
emit sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_d->notifyProjectionUpdatedInPatches(bounds());
invalidateAllFrames();
return true;
}
void KisImage::stopIsolatedMode()
{
if (!m_d->isolatedRootNode) return;
KisNodeSP oldRootNode = m_d->isolatedRootNode;
m_d->isolatedRootNode = 0;
emit sigIsolatedModeChanged();
// the GUI uses our thread to do the color space conversion so we
// need to emit this signal in multiple threads
m_d->notifyProjectionUpdatedInPatches(bounds());
invalidateAllFrames();
// TODO: Substitute notifyProjectionUpdated() with this code
// when update optimization is implemented
//
// QRect updateRect = bounds() | oldRootNode->extent();
// oldRootNode->setDirty(updateRect);
}
KisNodeSP KisImage::isolatedModeRoot() const
{
return m_d->isolatedRootNode;
}
void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data)
{
KisUpdateTimeMonitor::instance()->reportJobStarted(data);
m_d->scheduler.addJob(id, data);
}
void KisImage::endStroke(KisStrokeId id)
{
m_d->scheduler.endStroke(id);
}
bool KisImage::cancelStroke(KisStrokeId id)
{
return m_d->scheduler.cancelStroke(id);
}
bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync()
{
return scheduler.tryCancelCurrentStrokeAsync();
}
void KisImage::requestUndoDuringStroke()
{
emit sigUndoDuringStrokeRequested();
}
void KisImage::requestStrokeCancellation()
{
if (!m_d->tryCancelCurrentStrokeAsync()) {
emit sigStrokeCancellationRequested();
}
}
UndoResult KisImage::tryUndoUnfinishedLod0Stroke()
{
return m_d->scheduler.tryUndoLastStrokeAsync();
}
void KisImage::requestStrokeEnd()
{
emit sigStrokeEndRequested();
emit sigStrokeEndRequestedActiveNodeFiltered();
}
void KisImage::requestStrokeEndActiveNode()
{
emit sigStrokeEndRequested();
}
void KisImage::refreshGraph(KisNodeSP root)
{
refreshGraph(root, bounds(), bounds());
}
void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefresh(root, rc, cropRect);
}
void KisImage::initialRefreshGraph()
{
/**
* NOTE: Tricky part. We set crop rect to null, so the clones
* will not rely on precalculated projections of their sources
*/
refreshGraphAsync(0, bounds(), QRect());
waitForDone();
}
void KisImage::refreshGraphAsync(KisNodeSP root)
{
refreshGraphAsync(root, bounds(), bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc)
{
refreshGraphAsync(root, rc, bounds());
}
void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect)
{
if (!root) root = m_d->rootLayer;
m_d->animationInterface->notifyNodeChanged(root.data(), rc, true);
m_d->scheduler.fullRefreshAsync(root, rc, cropRect);
}
void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect)
{
KIS_ASSERT_RECOVER_RETURN(pseudoFilthy);
m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false);
m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect);
}
void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->scheduler.addSpontaneousJob(spontaneousJob);
}
void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter)
{
// udpate filters are *not* recursive!
KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter);
m_d->projectionUpdatesFilter = filter;
}
KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const
{
return m_d->projectionUpdatesFilter;
}
void KisImage::disableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter()));
}
void KisImage::enableDirtyRequests()
{
setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
}
void KisImage::disableUIUpdates()
{
m_d->disableUIUpdateSignals.ref();
}
void KisImage::enableUIUpdates()
{
m_d->disableUIUpdateSignals.deref();
}
void KisImage::notifyProjectionUpdated(const QRect &rc)
{
KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc);
if (!m_d->disableUIUpdateSignals) {
int lod = currentLevelOfDetail();
QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod);
if (dirtyRect.isEmpty()) return;
emit sigImageUpdated(dirtyRect);
}
}
void KisImage::notifySelectionChanged()
{
/**
* The selection is calculated asynchromously, so it is not
* handled by disableUIUpdates() and other special signals of
* KisImageSignalRouter
*/
m_d->legacyUndoAdapter.emitSelectionChanged();
/**
* Editing of selection masks doesn't necessary produce a
* setDirty() call, so in the end of the stroke we need to request
* direct update of the UI's cache.
*/
if (m_d->isolatedRootNode &&
dynamic_cast<KisSelectionMask*>(m_d->isolatedRootNode.data())) {
notifyProjectionUpdated(bounds());
}
}
void KisImage::requestProjectionUpdateImpl(KisNode *node,
const QRect &rect,
const QRect &cropRect)
{
if (rect.isEmpty()) return;
- KisNodeGraphListener::requestProjectionUpdate(node, rect);
m_d->scheduler.updateProjection(node, rect, cropRect);
}
-void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect)
+void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache)
{
if (m_d->projectionUpdatesFilter
- && m_d->projectionUpdatesFilter->filter(this, node, rect)) {
+ && m_d->projectionUpdatesFilter->filter(this, node, rect, resetAnimationCache)) {
return;
}
- m_d->animationInterface->notifyNodeChanged(node, rect, false);
+ if (resetAnimationCache) {
+ m_d->animationInterface->notifyNodeChanged(node, rect, false);
+ }
/**
* Here we use 'permitted' instead of 'active' intentively,
* because the updates may come after the actual stroke has been
* finished. And having some more updates for the stroke not
* supporting the wrap-around mode will not make much harm.
*/
if (m_d->wrapAroundModePermitted) {
const QRect boundRect = effectiveLodBounds();
KisWrappedRect splitRect(rect, boundRect);
Q_FOREACH (const QRect &rc, splitRect) {
requestProjectionUpdateImpl(node, rc, boundRect);
}
} else {
requestProjectionUpdateImpl(node, rect, bounds());
}
+
+ KisNodeGraphListener::requestProjectionUpdate(node, rect, resetAnimationCache);
}
void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
m_d->animationInterface->invalidateFrames(range, rect);
}
void KisImage::requestTimeSwitch(int time)
{
m_d->animationInterface->requestTimeSwitchNonGUI(time);
}
QList<KisLayerCompositionSP> KisImage::compositions()
{
return m_d->compositions;
}
void KisImage::addComposition(KisLayerCompositionSP composition)
{
m_d->compositions.append(composition);
}
void KisImage::removeComposition(KisLayerCompositionSP composition)
{
m_d->compositions.removeAll(composition);
}
bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds)
{
KisSelectionMask *mask = dynamic_cast<KisSelectionMask*>(root.data());
if (mask &&
(!bounds.contains(mask->paintDevice()->exactBounds()) ||
mask->selection()->hasShapeSelection())) {
return true;
}
KisNodeSP node = root->firstChild();
while (node) {
if (checkMasksNeedConversion(node, bounds)) {
return true;
}
node = node->nextSibling();
}
return false;
}
void KisImage::setWrapAroundModePermitted(bool value)
{
m_d->wrapAroundModePermitted = value;
if (m_d->wrapAroundModePermitted &&
checkMasksNeedConversion(root(), bounds())) {
KisProcessingApplicator applicator(this, root(),
KisProcessingApplicator::RECURSIVE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Crop Selections"));
KisProcessingVisitorSP visitor =
new KisCropSelectionsProcessingVisitor(bounds());
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
}
}
bool KisImage::wrapAroundModePermitted() const
{
return m_d->wrapAroundModePermitted;
}
bool KisImage::wrapAroundModeActive() const
{
return m_d->wrapAroundModePermitted &&
m_d->scheduler.wrapAroundModeSupported();
}
void KisImage::setDesiredLevelOfDetail(int lod)
{
if (m_d->blockLevelOfDetail) {
qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()"
<< "was called while LoD functionality was being blocked!";
return;
}
m_d->scheduler.setDesiredLevelOfDetail(lod);
}
int KisImage::currentLevelOfDetail() const
{
if (m_d->blockLevelOfDetail) {
return 0;
}
return m_d->scheduler.currentLevelOfDetail();
}
void KisImage::setLevelOfDetailBlocked(bool value)
{
KisImageBarrierLockerRaw l(this);
if (value && !m_d->blockLevelOfDetail) {
m_d->scheduler.setDesiredLevelOfDetail(0);
}
m_d->blockLevelOfDetail = value;
}
void KisImage::explicitRegenerateLevelOfDetail()
{
if (!m_d->blockLevelOfDetail) {
m_d->scheduler.explicitRegenerateLevelOfDetail();
}
}
bool KisImage::levelOfDetailBlocked() const
{
return m_d->blockLevelOfDetail;
}
void KisImage::notifyNodeCollpasedChanged()
{
emit sigNodeCollapsedChanged();
}
KisImageAnimationInterface* KisImage::animationInterface() const
{
return m_d->animationInterface;
}
void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig)
{
m_d->proofingConfig = proofingConfig;
emit sigProofingConfigChanged();
}
KisProofingConfigurationSP KisImage::proofingConfiguration() const
{
if (!m_d->proofingConfig) {
KisImageConfig cfg;
m_d->proofingConfig = cfg.defaultProofingconfiguration();
}
return m_d->proofingConfig;
}
QPointF KisImage::mirrorAxesCenter() const
{
return m_d->axesCenter;
}
void KisImage::setMirrorAxesCenter(const QPointF &value) const
{
m_d->axesCenter = value;
}
diff --git a/libs/image/kis_image.h b/libs/image/kis_image.h
index 8704e5885a..e38964bc45 100644
--- a/libs/image/kis_image.h
+++ b/libs/image/kis_image.h
@@ -1,984 +1,985 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMAGE_H_
#define KIS_IMAGE_H_
#include <QObject>
#include <QString>
#include <QPainter>
#include <QRect>
#include <QRegion>
#include <QBitArray>
#include <KoColorConversionTransformation.h>
#include "kis_paint_device.h" // msvc cannot handle forward declarations, so include kis_paint_device here
#include "kis_types.h"
#include "kis_shared.h"
#include "kis_node_graph_listener.h"
#include "kis_node_facade.h"
#include "kis_image_interfaces.h"
#include "kis_strokes_queue_undo_result.h"
#include <kritaimage_export.h>
class KisDocument;
class KoColorSpace;
class KoColor;
class KisCompositeProgressProxy;
class KisActionRecorder;
class KisUndoStore;
class KisUndoAdapter;
class KisImageSignalRouter;
class KisPostExecutionUndoAdapter;
class KisFilterStrategy;
class KoColorProfile;
class KisLayerComposition;
class KisSpontaneousJob;
class KisImageAnimationInterface;
class KUndo2MagicString;
class KisProofingConfiguration;
namespace KisMetaData
{
class MergeStrategy;
}
/**
* This is the image class, it contains a tree of KisLayer stack and
* meta information about the image. And it also provides some
* functions to manipulate the whole image.
*/
class KRITAIMAGE_EXPORT KisImage : public QObject,
public KisStrokesFacade,
public KisStrokeUndoFacade,
public KisUpdatesFacade,
public KisProjectionUpdateListener,
public KisNodeFacade,
public KisNodeGraphListener,
public KisShared
{
Q_OBJECT
public:
/// @param colorSpace can be null. in that case it will be initialised to a default color space.
KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace *colorSpace, const QString& name);
virtual ~KisImage();
public: // KisNodeGraphListener implementation
void aboutToAddANode(KisNode *parent, int index);
void nodeHasBeenAdded(KisNode *parent, int index);
void aboutToRemoveANode(KisNode *parent, int index);
void nodeChanged(KisNode * node);
void invalidateAllFrames();
void notifySelectionChanged();
- void requestProjectionUpdate(KisNode *node, const QRect& rect);
+ void requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache);
void invalidateFrames(const KisTimeRange &range, const QRect &rect);
void requestTimeSwitch(int time);
public: // KisProjectionUpdateListener implementation
void notifyProjectionUpdated(const QRect &rc);
public:
/**
* Makes a copy of the image with all the layers. If possible, shallow
* copies of the layers are made.
*
* \p exactCopy shows if the copied image should look *exactly* the same as
* the other one (according to it's .kra xml representation). It means that
* the layers will have the same UUID keys and, therefore, you are not
* expected to use the copied image anywhere except for saving. Don't use
* this option if you plan to work with the copied image later.
*/
KisImage *clone(bool exactCopy = false);
/**
* Render the projection onto a QImage.
*/
QImage convertToQImage(qint32 x1,
qint32 y1,
qint32 width,
qint32 height,
const KoColorProfile * profile);
/**
* Render the projection onto a QImage.
* (this is an overloaded function)
*/
QImage convertToQImage(QRect imageRect,
const KoColorProfile * profile);
/**
* XXX: docs!
*/
QImage convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile);
/**
* Calls KisUpdateScheduler::lock (XXX: APIDOX -- what does that mean?)
*/
void lock();
/**
* Calls KisUpdateScheduler::unlock (XXX: APIDOX -- what does that mean?)
*/
void unlock();
/**
* Returns true if lock() has been called more often than unlock().
*/
bool locked() const;
/**
* @return the global selection object or 0 if there is none. The
* global selection is always read-write.
*/
KisSelectionSP globalSelection() const;
/**
* Retrieve the next automatic layername (XXX: fix to add option to return Mask X)
*/
QString nextLayerName(const QString &baseName = "") const;
/**
* Set the automatic layer name counter one back.
*/
void rollBackLayerName();
/**
* Resize the image to the specified rect. The resize
* method handles the creating on an undo step itself.
*
* @param newRect the rect describing the new width, height and offset
* of the image
*/
void resizeImage(const QRect& newRect);
/**
* Crop the image to the specified rect. The crop
* method handles the creating on an undo step itself.
*
* @param newRect the rect describing the new width, height and offset
* of the image
*/
void cropImage(const QRect& newRect);
/**
* Crop a node to @newRect. The node will *not* be moved anywhere,
* it just drops some content
*/
void cropNode(KisNodeSP node, const QRect& newRect);
/// XXX: ApiDox
void scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy);
/// XXX: ApiDox
void scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy);
/**
* Execute a rotate transform on all layers in this image.
* Image is resized to fit rotated image.
*/
void rotateImage(double radians);
/**
* Execute a rotate transform on on a subtree of this image.
* Image is not resized.
*/
void rotateNode(KisNodeSP node, double radians);
/**
* Execute a shear transform on all layers in this image.
*/
void shear(double angleX, double angleY);
/**
* Shear a node and all its children.
* @param angleX, @param angleY are given in degrees.
*/
void shearNode(KisNodeSP node, double angleX, double angleY);
/**
* Convert the image and all its layers to the dstColorSpace
*/
void convertImageColorSpace(const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
/**
* Set the color space of the projection (and the root layer)
* to dstColorSpace. No conversion is done for other layers,
* their colorspace can differ.
* NOTE: Note conversion is done, only regeneration, so no rendering
* intent needed
*/
void convertProjectionColorSpace(const KoColorSpace *dstColorSpace);
// Get the profile associated with this image
const KoColorProfile * profile() const;
/**
* Set the profile of the image to the new profile and do the same for
* all layers that have the same colorspace and profile of the image.
* It doesn't do any pixel conversion.
*
* This is essential if you have loaded an image that didn't
* have an embedded profile to which you want to attach the right profile.
*
* This does not create an undo action; only call it when creating or
* loading an image.
*
* @returns false if the profile could not be assigned
*/
bool assignImageProfile(const KoColorProfile *profile);
/**
* Returns the current undo adapter. You can add new commands to the
* undo stack using the adapter. This adapter is used for a backward
* compatibility for old commands created before strokes. It blocks
* all the porcessing at the scheduler, waits until it's finished
* adn executes commands exclusively.
*/
KisUndoAdapter* undoAdapter() const;
/**
* This adapter is used by the strokes system. The commands are added
* to it *after* redo() is done (in the scheduler context). They are
* wrapped into a special command and added to the undo stack. redo()
* in not called.
*/
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const;
/**
* Replace current undo store with the new one. The old store
* will be deleted.
* This method is used by KisDocument for dropping all the commands
* during file loading.
*/
void setUndoStore(KisUndoStore *undoStore);
/**
* Return current undo store of the image
*/
KisUndoStore* undoStore();
/**
* @return the action recorder associated with this image
*/
KisActionRecorder* actionRecorder() const;
/**
* Tell the image it's modified; this emits the sigImageModified
* signal. This happens when the image needs to be saved
*/
void setModified();
/**
* The default colorspace of this image: new layers will have this
* colorspace and the projection will have this colorspace.
*/
const KoColorSpace * colorSpace() const;
/**
* X resolution in pixels per pt
*/
double xRes() const;
/**
* Y resolution in pixels per pt
*/
double yRes() const;
/**
* Set the resolution in pixels per pt.
*/
void setResolution(double xres, double yres);
/**
* Convert a document coordinate to a pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPointF documentToPixel(const QPointF &documentCoord) const;
/**
* Convert a document coordinate to an integer pixel coordinate.
*
* @param documentCoord PostScript Pt coordinate to convert.
*/
QPoint documentToIntPixel(const QPointF &documentCoord) const;
/**
* Convert a document rectangle to a pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRectF documentToPixel(const QRectF &documentRect) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param documentRect PostScript Pt rectangle to convert.
*/
QRect documentToIntPixel(const QRectF &documentRect) const;
/**
* Convert a pixel coordinate to a document coordinate.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPointF &pixelCoord) const;
/**
* Convert an integer pixel coordinate to a document coordinate.
* The document coordinate is at the centre of the pixel.
*
* @param pixelCoord pixel coordinate to convert.
*/
QPointF pixelToDocument(const QPoint &pixelCoord) const;
/**
* Convert a document rectangle to an integer pixel rectangle.
*
* @param pixelCoord pixel coordinate to convert.
*/
QRectF pixelToDocument(const QRectF &pixelCoord) const;
/**
* Return the width of the image
*/
qint32 width() const;
/**
* Return the height of the image
*/
qint32 height() const;
/**
* Return the size of the image
*/
QSize size() const {
return QSize(width(), height());
}
/**
* @return the root node of the image node graph
*/
KisGroupLayerSP rootLayer() const;
/**
* Return the projection; that is, the complete, composited
* representation of this image.
*/
KisPaintDeviceSP projection() const;
/**
* Return the number of layers (not other nodes) that are in this
* image.
*/
qint32 nlayers() const;
/**
* Return the number of layers (not other node types) that are in
* this image and that are hidden.
*/
qint32 nHiddenLayers() const;
/**
* Merge all visible layers and discard hidden ones.
*/
void flatten();
/**
* Merge the specified layer with the layer
* below this layer, remove the specified layer.
*/
void mergeDown(KisLayerSP l, const KisMetaData::MergeStrategy* strategy);
/**
* flatten the layer: that is, the projection becomes the layer
* and all subnodes are removed. If this is not a paint layer, it will morph
* into a paint layer.
*/
void flattenLayer(KisLayerSP layer);
/**
* Merges layers in \p mergedLayers and creates a new layer above
* \p putAfter
*/
void mergeMultipleLayers(QList<KisNodeSP> mergedLayers, KisNodeSP putAfter);
/// @return the exact bounds of the image in pixel coordinates.
QRect bounds() const;
/**
* Returns the actual bounds of the image, taking LevelOfDetail
* into account. This value is used as a bounds() value of
* KisDefaultBounds object.
*/
QRect effectiveLodBounds() const;
/// use if the layers have changed _completely_ (eg. when flattening)
void notifyLayersChanged();
/**
* Sets the default color of the root layer projection. All the layers
* will be merged on top of this very color
*/
void setDefaultProjectionColor(const KoColor &color);
/**
* \see setDefaultProjectionColor()
*/
KoColor defaultProjectionColor() const;
void setRootLayer(KisGroupLayerSP rootLayer);
/**
* Add an annotation for this image. This can be anything: Gamma, EXIF, etc.
* Note that the "icc" annotation is reserved for the color strategies.
* If the annotation already exists, overwrite it with this one.
*/
void addAnnotation(KisAnnotationSP annotation);
/** get the annotation with the given type, can return 0 */
KisAnnotationSP annotation(const QString& type);
/** delete the annotation, if the image contains it */
void removeAnnotation(const QString& type);
/**
* Start of an iteration over the annotations of this image (including the ICC Profile)
*/
vKisAnnotationSP_it beginAnnotations();
/** end of an iteration over the annotations of this image */
vKisAnnotationSP_it endAnnotations();
/**
* Called before the image is delted and sends the sigAboutToBeDeleted signal
*/
void notifyAboutToBeDeleted();
KisImageSignalRouter* signalRouter();
/**
* Returns whether we can reselect current global selection
*
* \see reselectGlobalSelection()
*/
bool canReselectGlobalSelection();
/**
* Returns the layer compositions for the image
*/
QList<KisLayerCompositionSP> compositions();
/**
* Adds a new layer composition, will be saved with the image
*/
void addComposition(KisLayerCompositionSP composition);
/**
* Remove the layer compostion
*/
void removeComposition(KisLayerCompositionSP composition);
/**
* Permit or deny the wrap-around mode for all the paint devices
* of the image. Note that permitting the wraparound mode will not
* necessarily activate it right now. To be activated the wrap
* around mode should be 1) permitted; 2) supported by the
* currently running stroke.
*/
void setWrapAroundModePermitted(bool value);
/**
* \return whether the wrap-around mode is permitted for this
* image. If the wrap around mode is permitted and the
* currently running stroke supports it, the mode will be
* activated for all paint devices of the image.
*
* \see setWrapAroundMode
*/
bool wrapAroundModePermitted() const;
/**
* \return whether the wraparound mode is activated for all the
* devices of the image. The mode is activated when both
* factors are true: the user permitted it and the stroke
* supports it
*/
bool wrapAroundModeActive() const;
/**
* \return curent level of detail which is used when processing the image.
* Current working zoom = 2 ^ (- currentLevelOfDetail()). Default value is
* null, which means we work on the original image.
*/
int currentLevelOfDetail() const;
/**
* Notify KisImage which level of detail should be used in the
* lod-mode. Setting the mode does not guarantee the LOD to be
* used. It will be activated only when the stokes supports it.
*/
void setDesiredLevelOfDetail(int lod);
/**
* Relative position of the mirror axis center
* 0,0 - topleft corner of the image
* 1,1 - bottomright corner of the image
*/
QPointF mirrorAxesCenter() const;
/**
* Sets the relative position of the axes center
* \see mirrorAxesCenter() for details
*/
void setMirrorAxesCenter(const QPointF &value) const;
public Q_SLOTS:
/**
* Explicitly start regeneration of LoD planes of all the devices
* in the image. This call should be performed when the user is idle,
* just to make the quality of image updates better.
*/
void explicitRegenerateLevelOfDetail();
public:
/**
* Blocks usage of level of detail functionality. After this method
* has been called, no new strokes will use LoD.
*/
void setLevelOfDetailBlocked(bool value);
/**
* \see setLevelOfDetailBlocked()
*/
bool levelOfDetailBlocked() const;
/**
* Notifies that the node collapsed state has changed
*/
void notifyNodeCollpasedChanged();
KisImageAnimationInterface *animationInterface() const;
/**
* @brief setProofingConfiguration, this sets the image's proofing configuration, and signals
* the proofingConfiguration has changed.
* @param proofingConfig - the kis proofing config that will be used instead.
*/
void setProofingConfiguration(KisProofingConfigurationSP proofingConfig);
/**
* @brief proofingConfiguration
* @return the proofing configuration of the image.
*/
KisProofingConfigurationSP proofingConfiguration() const;
public:
bool startIsolatedMode(KisNodeSP node);
void stopIsolatedMode();
KisNodeSP isolatedModeRoot() const;
Q_SIGNALS:
/**
* Emitted whenever an action has caused the image to be
* recomposited.
*
* @param rc The rect that has been recomposited.
*/
void sigImageUpdated(const QRect &);
/**
Emitted whenever the image has been modified, so that it
doesn't match with the version saved on disk.
*/
void sigImageModified();
/**
* The signal is emitted when the size of the image is changed.
* \p oldStillPoint and \p newStillPoint give the receiver the
* hint about how the new and old rect of the image correspond to
* each other. They specify the point of the image around which
* the conversion was done. This point will stay still on the
* user's screen. That is the \p newStillPoint of the new image
* will be painted at the same screen position, where \p
* oldStillPoint of the old image was painted.
*
* \param oldStillPoint is a still point represented in *old*
* image coordinates
*
* \param newStillPoint is a still point represented in *new*
* image coordinates
*/
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void sigResolutionChanged(double xRes, double yRes);
void sigRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes);
/**
* Inform the model that a node was changed
*/
void sigNodeChanged(KisNodeSP node);
/**
* Inform that the image is going to be deleted
*/
void sigAboutToBeDeleted();
/**
* The signal is emitted right after a node has been connected
* to the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread. If you need
* information about the parent/siblings of the node connect
* with Qt::DirectConnection, get needed information and then
* emit another Qt::AutoConnection signal to pass this information
* to your thread. See details of the implementation
* in KisDummiesfacadeBase.
*/
void sigNodeAddedAsync(KisNodeSP node);
/**
* This signal is emitted right before a node is going to removed
* from the graph of the nodes.
*
* WARNING: you must not request any graph-related information
* about the node being run in a not-scheduler thread.
*
* \see comment in sigNodeAddedAsync()
*/
void sigRemoveNodeAsync(KisNodeSP node);
/**
* Emitted when the root node of the image has changed.
* It happens, e.g. when we flatten the image. When
* this happens the receiver should reload information
* about the image
*/
void sigLayersChangedAsync();
/**
* Emitted when the UI has requested the undo of the last stroke's
* operation. The point is, we cannot deal with the internals of
* the stroke without its creator knowing about it (which most
* probably cause a crash), so we just forward this request from
* the UI to the creator of the stroke.
*
* If your tool supports undoing part of its work, just listen to
* this signal and undo when it comes
*/
void sigUndoDuringStrokeRequested();
/**
* Emitted when the UI has requested the cancellation of
* the stroke. The point is, we cannot cancel the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports cancelling of its work in the middle
* of operation, just listen to this signal and cancel
* the stroke when it comes
*/
void sigStrokeCancellationRequested();
/**
* Emitted when the image decides that the stroke should better
* be ended. The point is, we cannot just end the stroke
* without its creator knowing about it (which most probably
* cause a crash), so we just forward this request from the UI
* to the creator of the stroke.
*
* If your tool supports long strokes that may involve multiple
* mouse actions in one stroke, just listen to this signal and
* end the stroke when it comes.
*/
void sigStrokeEndRequested();
/**
* Same as sigStrokeEndRequested() but is not emitted when the active node
* is changed.
*/
void sigStrokeEndRequestedActiveNodeFiltered();
/**
* Emitted when the isolated mode status has changed.
*
* Can be used by the receivers to catch a fact of forcefully
* stopping the isolated mode by the image when some complex
* action was requested
*/
void sigIsolatedModeChanged();
/**
* Emitted when one or more nodes changed the collapsed state
*
*/
void sigNodeCollapsedChanged();
/**
* Emitted when the proofing configuration of the image is being changed.
*
*/
void sigProofingConfigChanged();
public Q_SLOTS:
KisCompositeProgressProxy* compositeProgressProxy();
bool isIdle(bool allowLocked = false);
/**
* @brief barrierLock APIDOX
* @param readOnly
*/
void barrierLock(bool readOnly = false);
/**
* @brief barrierLock APIDOX
* @param readOnly
*/
bool tryBarrierLock(bool readOnly = false);
/**
* @brief barrierLock APIDOX
* @param readOnly
*/
void waitForDone();
KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy);
void addJob(KisStrokeId id, KisStrokeJobData *data);
void endStroke(KisStrokeId id);
bool cancelStroke(KisStrokeId id);
/**
* @brief blockUpdates block updating the image projection
*/
void blockUpdates();
/**
* @brief unblockUpdates unblock updating the image project. This
* only restarts the scheduler and does not schedule a full refresh.
*/
void unblockUpdates();
/**
* Disables notification of the UI about the changes in the image.
* This feature is used by KisProcessingApplicator. It is needed
* when we change the size of the image. In this case, the whole
* image will be reloaded into UI by sigSizeChanged(), so there is
* no need to inform the UI about individual dirty rects.
*/
void disableUIUpdates();
/**
* \see disableUIUpdates
*/
void enableUIUpdates();
/**
* Disables the processing of all the setDirty() requests that
* come to the image. The incoming requests are effectively
* *dropped*.
*
* This feature is used by KisProcessingApplicator. For many cases
* it provides its own updates interface, which recalculates the
* whole subtree of nodes. But while we change any particular
* node, it can ask for an update itself. This method is a way of
* blocking such intermediate (and excessive) requests.
*
* NOTE: this is a convenience function for setProjectionUpdatesFilter()
* that installs a predefined filter that eats everything. Please
* note that these calls are *not* recursive
*/
void disableDirtyRequests();
/**
* \see disableDirtyRequests()
*/
void enableDirtyRequests();
/**
* Installs a filter object that will filter all the incoming projection update
* requests. If the filter return true, the incoming update is dropped.
*
* NOTE: you cannot set filters recursively!
*/
void setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter);
/**
* \see setProjectionUpdatesFilter()
*/
KisProjectionUpdatesFilterSP projectionUpdatesFilter() const;
void refreshGraphAsync(KisNodeSP root = KisNodeSP());
void refreshGraphAsync(KisNodeSP root, const QRect &rc);
void refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect);
/**
* Triggers synchronous recomposition of the projection
*/
void refreshGraph(KisNodeSP root = KisNodeSP());
void refreshGraph(KisNodeSP root, const QRect& rc, const QRect &cropRect);
void initialRefreshGraph();
/**
* Initiate a stack regeneration skipping the recalculation of the
* filthy node's projection.
*
* Works exactly as pseudoFilthy->setDirty() with the only
* exception that pseudoFilthy::updateProjection() will not be
* called. That is used by KisRecalculateTransformMaskJob to avoid
* cyclic dependencies.
*/
void requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect);
/**
* Adds a spontaneous job to the updates queue.
*
* A spontaneous job may do some trivial tasks in the background,
* like updating the outline of selection or purging unused tiles
* from the existing paint devices.
*/
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob);
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks the current stroke should undo its last
* action, for example, when the user presses Ctrl+Z while some
* stroke is active.
*
* If the creator of the stroke supports undoing of intermediate
* actions, it will be notified about this request and can undo
* its last action.
*/
void requestUndoDuringStroke();
/**
* This method is called by the UI (*not* by the creator of the
* stroke) when it thinks current stroke should be cancelled. If
* there is a running stroke that has already been detached from
* its creator (ended or cancelled), it will be forcefully
* cancelled and reverted. If there is an open stroke present, and
* if its creator supports cancelling, it will be notified about
* the request and the stroke will be cancelled
*/
void requestStrokeCancellation();
/**
* This method requests the last stroke executed on the image to become undone.
* If the stroke is not ended, or if all the Lod0 strokes are completed, the method
* returns UNDO_FAIL. If the last Lod0 is going to be finished soon, then UNDO_WAIT
* is returned and the caller should just wait for its completion and call global undo
* instead. UNDO_OK means one unfinished stroke has been undone.
*/
UndoResult tryUndoUnfinishedLod0Stroke();
/**
* This method is called when image or some other part of Krita
* (*not* the creator of the stroke) decides that the stroke
* should be ended. If the creator of the stroke supports it, it
* will be notified and the stroke will be cancelled
*/
void requestStrokeEnd();
/**
* Same as requestStrokeEnd() but is called by view manager when
* the current node is changed. Use to dintinguish
* sigStrokeEndRequested() and
* sigStrokeEndRequestedActiveNodeFiltered() which are used by
* KisNodeJugglerCompressed
*/
void requestStrokeEndActiveNode();
private:
KisImage(const KisImage& rhs, KisUndoStore *undoStore, bool exactCopy);
KisImage& operator=(const KisImage& rhs);
void emitSizeChanged();
void resizeImageImpl(const QRect& newRect, bool cropLayers);
void rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double radians);
void shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode,
bool resizeImage, double angleX, double angleY,
const QPointF &origin);
void safeRemoveTwoNodes(KisNodeSP node1, KisNodeSP node2);
void refreshHiddenArea(KisNodeSP rootNode, const QRect &preparedArea);
void requestProjectionUpdateImpl(KisNode *node,
const QRect& rect,
const QRect &cropRect);
friend class KisImageResizeCommand;
void setSize(const QSize& size);
friend class KisImageSetProjectionColorSpaceCommand;
void setProjectionColorSpace(const KoColorSpace * colorSpace);
friend class KisDeselectGlobalSelectionCommand;
friend class KisReselectGlobalSelectionCommand;
friend class KisSetGlobalSelectionCommand;
friend class KisImageTest;
+ friend class Document; // For libkis
/**
* Replaces the current global selection with globalSelection. If
* \p globalSelection is empty, removes the selection object, so that
* \ref globalSelection() will return 0 after that.
*/
void setGlobalSelection(KisSelectionSP globalSelection);
/**
* Deselects current global selection.
* \ref globalSelection() will return 0 after that.
*/
void deselectGlobalSelection();
/**
* Reselects current deselected selection
*
* \see deselectGlobalSelection()
*/
void reselectGlobalSelection();
private:
class KisImagePrivate;
KisImagePrivate * m_d;
};
#endif // KIS_IMAGE_H_
diff --git a/libs/image/kis_iterator_complete_listener.h b/libs/image/kis_iterator_complete_listener.h
new file mode 100644
index 0000000000..f6bd3da18f
--- /dev/null
+++ b/libs/image/kis_iterator_complete_listener.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KIS_ITERATOR_COMPLETE_LISTENER_H
+#define KIS_ITERATOR_COMPLETE_LISTENER_H
+
+
+/**
+ * @brief The KisIteratorCompleteListener struct is a special interface for
+ * notifying the paint device that an iterator has completed its execution.
+ */
+struct KisIteratorCompleteListener {
+ virtual ~KisIteratorCompleteListener() {}
+ virtual void notifyWritableIteratorCompleted() = 0;
+};
+
+#endif // KIS_ITERATOR_COMPLETE_LISTENER_H
+
diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc
index e39b2d23c8..ba1f78c9c5 100644
--- a/libs/image/kis_layer.cc
+++ b/libs/image/kis_layer.cc
@@ -1,902 +1,904 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* 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.
*/
#include "kis_layer.h"
#include <klocalizedstring.h>
#include <QImage>
#include <QBitArray>
#include <QStack>
#include <QMutex>
#include <QMutexLocker>
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoProperties.h>
#include <KoCompositeOpRegistry.h>
#include <KoColorSpace.h>
#include "kis_debug.h"
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_mask.h"
#include "kis_effect_mask.h"
#include "kis_selection_mask.h"
#include "kis_meta_data_store.h"
#include "kis_selection.h"
#include "kis_paint_layer.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_clone_layer.h"
#include "kis_psd_layer_style.h"
#include "kis_layer_projection_plane.h"
#include "layerstyles/kis_layer_style_projection_plane.h"
#include "krita_utils.h"
#include "kis_layer_properties_icons.h"
#include "kis_layer_utils.h"
class KisSafeProjection {
public:
KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) {
QMutexLocker locker(&m_lock);
if (!m_reusablePaintDevice) {
m_reusablePaintDevice = new KisPaintDevice(*prototype);
}
if(!m_projection ||
*m_projection->colorSpace() != *prototype->colorSpace()) {
m_projection = m_reusablePaintDevice;
m_projection->makeCloneFromRough(prototype, prototype->extent());
m_projection->setProjectionDevice(true);
}
return m_projection;
}
void freeDevice() {
QMutexLocker locker(&m_lock);
m_projection = 0;
if(m_reusablePaintDevice) {
m_reusablePaintDevice->clear();
}
}
private:
QMutex m_lock;
KisPaintDeviceSP m_projection;
KisPaintDeviceSP m_reusablePaintDevice;
};
class KisCloneLayersList {
public:
void addClone(KisCloneLayerWSP cloneLayer) {
m_clonesList.append(cloneLayer);
}
void removeClone(KisCloneLayerWSP cloneLayer) {
m_clonesList.removeOne(cloneLayer);
}
void setDirty(const QRect &rect) {
Q_FOREACH (KisCloneLayerSP clone, m_clonesList) {
if (clone) {
clone->setDirtyOriginal(rect);
}
}
}
const QList<KisCloneLayerWSP> registeredClones() const {
return m_clonesList;
}
bool hasClones() const {
return !m_clonesList.isEmpty();
}
private:
QList<KisCloneLayerWSP> m_clonesList;
};
struct Q_DECL_HIDDEN KisLayer::Private
{
KisImageWSP image;
QBitArray channelFlags;
KisMetaData::Store* metaDataStore;
KisSafeProjection safeProjection;
KisCloneLayersList clonesList;
KisPSDLayerStyleSP layerStyle;
KisAbstractProjectionPlaneSP layerStyleProjectionPlane;
KisAbstractProjectionPlaneSP projectionPlane;
};
KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity)
: KisNode()
, m_d(new Private)
{
setName(name);
setOpacity(opacity);
m_d->image = image;
m_d->metaDataStore = new KisMetaData::Store();
m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this));
}
KisLayer::KisLayer(const KisLayer& rhs)
: KisNode(rhs)
, m_d(new Private())
{
if (this != &rhs) {
m_d->image = rhs.m_d->image;
m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore);
m_d->channelFlags = rhs.m_d->channelFlags;
setName(rhs.name());
m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this));
if (rhs.m_d->layerStyle) {
setLayerStyle(rhs.m_d->layerStyle->clone());
}
}
}
KisLayer::~KisLayer()
{
delete m_d->metaDataStore;
delete m_d;
}
const KoColorSpace * KisLayer::colorSpace() const
{
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return nullptr;
}
return image->colorSpace();
}
const KoCompositeOp * KisLayer::compositeOp() const
{
/**
* FIXME: This function duplicates the same function from
* KisMask. We can't move it to KisBaseNode as it doesn't
* know anything about parent() method of KisNode
* Please think it over...
*/
KisNodeSP parentNode = parent();
if (!parentNode) return 0;
if (!parentNode->colorSpace()) return 0;
const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId());
return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER);
}
KisPSDLayerStyleSP KisLayer::layerStyle() const
{
return m_d->layerStyle;
}
void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle)
{
if (layerStyle) {
m_d->layerStyle = layerStyle;
KisAbstractProjectionPlaneSP plane = !layerStyle->isEmpty() ?
KisAbstractProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) :
KisAbstractProjectionPlaneSP(0);
m_d->layerStyleProjectionPlane = plane;
} else {
m_d->layerStyleProjectionPlane.clear();
m_d->layerStyle.clear();
}
}
KisBaseNode::PropertyList KisLayer::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties();
l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity()));
const KoCompositeOp * compositeOp = this->compositeOp();
if (compositeOp) {
l << KisBaseNode::Property(KoID("compositeop", i18n("Composite Mode")), compositeOp->description());
}
if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) {
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled());
}
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled());
return l;
}
void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
KisBaseNode::setSectionModelProperties(properties);
Q_FOREACH (const KisBaseNode::Property &property, properties) {
if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) {
disableAlphaChannel(property.state.toBool());
}
if (property.id == KisLayerPropertiesIcons::layerStyle.id()) {
if (m_d->layerStyle &&
m_d->layerStyle->isEnabled() != property.state.toBool()) {
m_d->layerStyle->setEnabled(property.state.toBool());
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
}
}
}
void KisLayer::disableAlphaChannel(bool disable)
{
QBitArray newChannelFlags = m_d->channelFlags;
if(newChannelFlags.isEmpty())
newChannelFlags = colorSpace()->channelFlags(true, true);
if(disable)
newChannelFlags &= colorSpace()->channelFlags(true, false);
else
newChannelFlags |= colorSpace()->channelFlags(false, true);
setChannelFlags(newChannelFlags);
}
bool KisLayer::alphaChannelDisabled() const
{
QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags;
return flags.count(true) == 0 && !m_d->channelFlags.isEmpty();
}
void KisLayer::setChannelFlags(const QBitArray & channelFlags)
{
Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount()));
if (KritaUtils::compareChannelFlags(channelFlags,
this->channelFlags())) {
return;
}
if (!channelFlags.isEmpty() &&
channelFlags == QBitArray(channelFlags.size(), true)) {
m_d->channelFlags.clear();
} else {
m_d->channelFlags = channelFlags;
}
baseNodeChangedCallback();
baseNodeInvalidateAllFramesCallback();
}
QBitArray & KisLayer::channelFlags() const
{
return m_d->channelFlags;
}
bool KisLayer::temporary() const
{
return nodeProperties().boolProperty("temporary", false);
}
void KisLayer::setTemporary(bool t)
{
nodeProperties().setProperty("temporary", t);
}
KisImageWSP KisLayer::image() const
{
return m_d->image;
}
void KisLayer::setImage(KisImageWSP image)
{
m_d->image = image;
KisNodeSP node = firstChild();
while (node) {
KisLayerUtils::recursiveApplyNodes(node,
[image] (KisNodeSP node) {
node->setImage(image);
});
node = node->nextSibling();
}
}
bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer)
{
return
this->compositeOpId() == otherLayer->compositeOpId() &&
this->opacity() == otherLayer->opacity() &&
this->channelFlags() == otherLayer->channelFlags() &&
!this->layerStyle() && !otherLayer->layerStyle() &&
(this->colorSpace() == otherLayer->colorSpace() ||
*this->colorSpace() == *otherLayer->colorSpace());
}
KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer)
{
const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer);
KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8);
if (keepBlendingOptions) {
newLayer->setCompositeOpId(compositeOpId());
newLayer->setOpacity(opacity());
newLayer->setChannelFlags(channelFlags());
}
return newLayer;
}
void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer)
{
const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer);
QRect layerProjectionExtent = this->projection()->extent();
QRect prevLayerProjectionExtent = prevLayer->projection()->extent();
bool alphaDisabled = this->alphaChannelDisabled();
bool prevAlphaDisabled = prevLayer->alphaChannelDisabled();
KisPaintDeviceSP mergedDevice = dstLayer->paintDevice();
if (!keepBlendingOptions) {
KisPainter gc(mergedDevice);
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
//Copy the pixels of previous layer with their actual alpha value
prevLayer->disableAlphaChannel(false);
prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds());
//Restore the previous prevLayer disableAlpha status for correct undo/redo
prevLayer->disableAlphaChannel(prevAlphaDisabled);
//Paint the pixels of the current layer, using their actual alpha value
if (alphaDisabled == prevAlphaDisabled) {
this->disableAlphaChannel(false);
}
this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds());
//Restore the layer disableAlpha status for correct undo/redo
this->disableAlphaChannel(alphaDisabled);
}
else {
//Copy prevLayer
KisPaintDeviceSP srcDev = prevLayer->projection();
mergedDevice->makeCloneFrom(srcDev, srcDev->extent());
//Paint layer on the copy
KisPainter gc(mergedDevice);
gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent);
}
}
void KisLayer::registerClone(KisCloneLayerWSP clone)
{
m_d->clonesList.addClone(clone);
}
void KisLayer::unregisterClone(KisCloneLayerWSP clone)
{
m_d->clonesList.removeClone(clone);
}
const QList<KisCloneLayerWSP> KisLayer::registeredClones() const
{
return m_d->clonesList.registeredClones();
}
bool KisLayer::hasClones() const
{
return m_d->clonesList.hasClones();
}
void KisLayer::updateClones(const QRect &rect)
{
m_d->clonesList.setDirty(rect);
}
KisSelectionMaskSP KisLayer::selectionMask() const
{
KoProperties properties;
properties.setProperty("active", true);
QList<KisNodeSP> masks = childNodes(QStringList("KisSelectionMask"), properties);
// return the first visible mask
Q_FOREACH (KisNodeSP mask, masks) {
if (mask->visible()) {
return dynamic_cast<KisSelectionMask*>(mask.data());
}
}
return KisSelectionMaskSP();
}
KisSelectionSP KisLayer::selection() const
{
KisSelectionMaskSP mask = selectionMask();
if (mask) {
return mask->selection();
}
KisImageSP image = m_d->image.toStrongRef();
if (image) {
return image->globalSelection();
}
return KisSelectionSP();
}
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////
QList<KisEffectMaskSP> KisLayer::effectMasks(KisNodeSP lastNode) const
{
QList<KisEffectMaskSP> masks;
if (childCount() > 0) {
KoProperties properties;
properties.setProperty("visible", true);
QList<KisNodeSP> nodes = childNodes(QStringList("KisEffectMask"), properties);
Q_FOREACH (const KisNodeSP& node, nodes) {
if (node == lastNode) break;
KisEffectMaskSP mask = dynamic_cast<KisEffectMask*>(const_cast<KisNode*>(node.data()));
if (mask)
masks.append(mask);
}
}
return masks;
}
bool KisLayer::hasEffectMasks() const
{
if (childCount() == 0) return false;
KisNodeSP node = firstChild();
while (node) {
if (node->inherits("KisEffectMask") && node->visible()) {
return true;
}
node = node->nextSibling();
}
return false;
}
QRect KisLayer::masksChangeRect(const QList<KisEffectMaskSP> &masks,
const QRect &requestedRect,
bool &rectVariesFlag) const
{
rectVariesFlag = false;
QRect prevChangeRect = requestedRect;
/**
* We set default value of the change rect for the case
* when there is no mask at all
*/
QRect changeRect = requestedRect;
Q_FOREACH (const KisEffectMaskSP& mask, masks) {
changeRect = mask->changeRect(prevChangeRect);
if (changeRect != prevChangeRect)
rectVariesFlag = true;
prevChangeRect = changeRect;
}
return changeRect;
}
QRect KisLayer::masksNeedRect(const QList<KisEffectMaskSP> &masks,
const QRect &changeRect,
QStack<QRect> &applyRects,
bool &rectVariesFlag) const
{
rectVariesFlag = false;
QRect prevNeedRect = changeRect;
QRect needRect;
for (qint32 i = masks.size() - 1; i >= 0; i--) {
applyRects.push(prevNeedRect);
needRect = masks[i]->needRect(prevNeedRect);
if (prevNeedRect != needRect)
rectVariesFlag = true;
prevNeedRect = needRect;
}
return needRect;
}
KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion,
KisNodeSP filthy,
KisNodeSP parent)
{
if (parent == filthy || parent != filthy->parent()) {
return KisNode::N_ABOVE_FILTHY;
}
if (nodeInQuestion == filthy) {
return KisNode::N_FILTHY;
}
KisNodeSP node = nodeInQuestion->prevSibling();
while (node) {
if (node == filthy) {
return KisNode::N_ABOVE_FILTHY;
}
node = node->prevSibling();
}
return KisNode::N_BELOW_FILTHY;
}
QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
KisPaintDeviceSP destination,
const QRect &requestedRect,
KisNodeSP filthyNode,
KisNodeSP lastNode) const
{
Q_ASSERT(source);
Q_ASSERT(destination);
QList<KisEffectMaskSP> masks = effectMasks(lastNode);
QRect changeRect;
QRect needRect;
if (masks.isEmpty()) {
changeRect = requestedRect;
if (source != destination) {
copyOriginalToProjection(source, destination, requestedRect);
}
} else {
QStack<QRect> applyRects;
bool changeRectVaries;
bool needRectVaries;
/**
* FIXME: Assume that varying of the changeRect has already
* been taken into account while preparing walkers
*/
changeRectVaries = false;
changeRect = requestedRect;
//changeRect = masksChangeRect(masks, requestedRect,
// changeRectVaries);
needRect = masksNeedRect(masks, changeRect,
applyRects, needRectVaries);
if (!changeRectVaries && !needRectVaries) {
/**
* A bit of optimization:
* All filters will read/write exactly from/to the requested
* rect so we needn't create temporary paint device,
* just apply it onto destination
*/
Q_ASSERT(needRect == requestedRect);
if (source != destination) {
copyOriginalToProjection(source, destination, needRect);
}
Q_FOREACH (const KisEffectMaskSP& mask, masks) {
const QRect maskApplyRect = applyRects.pop();
const QRect maskNeedRect =
applyRects.isEmpty() ? needRect : applyRects.top();
PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast<KisLayer*>(this));
mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition);
}
Q_ASSERT(applyRects.isEmpty());
} else {
/**
* We can't eliminate additional copy-op
* as filters' behaviour may be quite insane here,
* so let them work on their own paintDevice =)
*/
KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace());
tempDevice->prepareClone(source);
copyOriginalToProjection(source, tempDevice, needRect);
QRect maskApplyRect = applyRects.pop();
QRect maskNeedRect = needRect;
Q_FOREACH (const KisEffectMaskSP& mask, masks) {
PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast<KisLayer*>(this));
mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition);
if (!applyRects.isEmpty()) {
maskNeedRect = maskApplyRect;
maskApplyRect = applyRects.pop();
}
}
Q_ASSERT(applyRects.isEmpty());
KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect);
}
}
return changeRect;
}
QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode)
{
QRect updatedRect = rect;
KisPaintDeviceSP originalDevice = original();
if (!rect.isValid() ||
!visible() ||
!originalDevice) return QRect();
if (!needProjection() && !hasEffectMasks()) {
m_d->safeProjection.freeDevice();
} else {
if (!updatedRect.isEmpty()) {
KisPaintDeviceSP projection =
m_d->safeProjection.getDeviceLazy(originalDevice);
updatedRect = applyMasks(originalDevice, projection,
updatedRect, filthyNode, 0);
}
}
return updatedRect;
}
QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect)
{
bool changeRectVaries = false;
QRect changeRect = outgoingChangeRect(rect);
changeRect = masksChangeRect(effectMasks(lastNode), changeRect,
changeRectVaries);
return changeRect;
}
/**
* \p rect is a dirty rect in layer's original() coordinates!
*/
void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect)
{
QRect changeRect = partialChangeRect(lastNode, rect);
KisPaintDeviceSP originalDevice = original();
KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks());
if (!changeRect.isEmpty()) {
applyMasks(originalDevice, projection,
changeRect, this, lastNode);
}
}
bool KisLayer::needProjection() const
{
return false;
}
void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const
{
KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect);
}
KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const
{
return m_d->layerStyleProjectionPlane ?
m_d->layerStyleProjectionPlane : m_d->projectionPlane;
}
KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const
{
return m_d->projectionPlane;
}
KisPaintDeviceSP KisLayer::projection() const
{
KisPaintDeviceSP originalDevice = original();
return needProjection() || hasEffectMasks() ?
m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice;
}
QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const
{
QRect changeRect = rect;
changeRect = incomingChangeRect(changeRect);
if(pos == KisNode::N_FILTHY) {
QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect;
bool changeRectVaries;
changeRect = outgoingChangeRect(changeRect);
changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries);
/**
* If the projection contains some dirty areas we should also
* add them to the change rect, because they might have
* changed. E.g. when a visibility of the mask has chnaged
* while the parent layer was invinisble.
*/
if (!projectionToBeUpdated.isEmpty() &&
!changeRect.contains(projectionToBeUpdated)) {
changeRect |= projectionToBeUpdated;
}
}
// TODO: string comparizon: optimize!
if (pos != KisNode::N_FILTHY &&
pos != KisNode::N_FILTHY_PROJECTION &&
compositeOpId() != COMPOSITE_COPY) {
changeRect |= rect;
}
return changeRect;
}
QRect KisLayer::incomingChangeRect(const QRect &rect) const
{
return rect;
}
QRect KisLayer::outgoingChangeRect(const QRect &rect) const
{
return rect;
}
QImage KisLayer::createThumbnail(qint32 w, qint32 h)
{
if (w == 0 || h == 0) {
return QImage();
}
KisPaintDeviceSP originalDevice = original();
return originalDevice ?
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) : QImage();
}
QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time)
{
if (w == 0 || h == 0) {
return QImage();
}
KisPaintDeviceSP originalDevice = original();
if (originalDevice) {
- KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace());
KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel();
- KisKeyframeSP keyframe = channel->activeKeyframeAt(time);
- channel->fetchFrame(keyframe, targetDevice);
- return targetDevice->createThumbnail(w, h, 1,
- KoColorConversionTransformation::internalRenderingIntent(),
- KoColorConversionTransformation::internalConversionFlags());
- }
- else {
- return QImage();
+
+ if (channel) {
+ KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace());
+ KisKeyframeSP keyframe = channel->activeKeyframeAt(time);
+ channel->fetchFrame(keyframe, targetDevice);
+ return targetDevice->createThumbnail(w, h, 1,
+ KoColorConversionTransformation::internalRenderingIntent(),
+ KoColorConversionTransformation::internalConversionFlags());
+ }
}
+
+ return createThumbnail(w, h);
}
qint32 KisLayer::x() const
{
KisPaintDeviceSP originalDevice = original();
return originalDevice ? originalDevice->x() : 0;
}
qint32 KisLayer::y() const
{
KisPaintDeviceSP originalDevice = original();
return originalDevice ? originalDevice->y() : 0;
}
void KisLayer::setX(qint32 x)
{
KisPaintDeviceSP originalDevice = original();
if (originalDevice)
originalDevice->setX(x);
}
void KisLayer::setY(qint32 y)
{
KisPaintDeviceSP originalDevice = original();
if (originalDevice)
originalDevice->setY(y);
}
QRect KisLayer::layerExtentImpl(bool needExactBounds) const
{
QRect additionalMaskExtent = QRect();
QList<KisEffectMaskSP> effectMasks = this->effectMasks();
Q_FOREACH(KisEffectMaskSP mask, effectMasks) {
additionalMaskExtent |= mask->nonDependentExtent();
}
KisPaintDeviceSP originalDevice = original();
QRect layerExtent;
if (originalDevice) {
layerExtent = needExactBounds ?
originalDevice->exactBounds() :
originalDevice->extent();
}
QRect additionalCompositeOpExtent;
if (compositeOpId() == COMPOSITE_DESTINATION_IN ||
compositeOpId() == COMPOSITE_DESTINATION_ATOP) {
additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds();
}
return layerExtent | additionalMaskExtent | additionalCompositeOpExtent;
}
QRect KisLayer::extent() const
{
return layerExtentImpl(false);
}
QRect KisLayer::exactBounds() const
{
return layerExtentImpl(true);
}
KisLayerSP KisLayer::parentLayer() const
{
return qobject_cast<KisLayer*>(parent().data());
}
KisMetaData::Store* KisLayer::metaData()
{
return m_d->metaDataStore;
}
diff --git a/libs/image/kis_layer_utils.cpp b/libs/image/kis_layer_utils.cpp
index 74926bb29b..da96b08674 100644
--- a/libs/image/kis_layer_utils.cpp
+++ b/libs/image/kis_layer_utils.cpp
@@ -1,1279 +1,1304 @@
/*
* 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_layer_utils.h"
#include <algorithm>
#include <QUuid>
#include <KoColorSpaceConstants.h>
#include "kis_painter.h"
#include "kis_image.h"
#include "kis_node.h"
#include "kis_layer.h"
#include "kis_paint_layer.h"
#include "kis_clone_layer.h"
#include "kis_group_layer.h"
#include "kis_selection.h"
#include "kis_selection_mask.h"
#include "kis_meta_data_merge_strategy.h"
#include <kundo2command.h>
#include "commands/kis_image_layer_add_command.h"
#include "commands/kis_image_layer_remove_command.h"
#include "commands/kis_image_layer_move_command.h"
#include "commands/kis_image_change_layers_command.h"
#include "commands_new/kis_activate_selection_mask_command.h"
#include "kis_abstract_projection_plane.h"
#include "kis_processing_applicator.h"
#include "kis_image_animation_interface.h"
#include "kis_keyframe_channel.h"
#include "kis_command_utils.h"
#include "kis_processing_applicator.h"
#include "commands_new/kis_change_projection_color_command.h"
#include "kis_layer_properties_icons.h"
#include "lazybrush/kis_colorize_mask.h"
#include "commands/kis_node_property_list_command.h"
+#include <KisDelayedUpdateNodeInterface.h>
namespace KisLayerUtils {
void fetchSelectionMasks(KisNodeList mergedNodes, QVector<KisSelectionMaskSP> &selectionMasks)
{
foreach (KisNodeSP node, mergedNodes) {
KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
KisSelectionMaskSP mask;
if (layer && (mask = layer->selectionMask())) {
selectionMasks.append(mask);
}
}
}
struct MergeDownInfoBase {
MergeDownInfoBase(KisImageSP _image)
: image(_image),
storage(new SwitchFrameCommand::SharedStorage())
{
}
virtual ~MergeDownInfoBase() {}
KisImageWSP image;
QVector<KisSelectionMaskSP> selectionMasks;
KisNodeSP dstNode;
SwitchFrameCommand::SharedStorageSP storage;
QSet<int> frames;
virtual KisNodeList allSrcNodes() = 0;
virtual KisLayerSP dstLayer() { return 0; }
};
struct MergeDownInfo : public MergeDownInfoBase {
MergeDownInfo(KisImageSP _image,
KisLayerSP _prevLayer,
KisLayerSP _currLayer)
: MergeDownInfoBase(_image),
prevLayer(_prevLayer),
currLayer(_currLayer)
{
frames =
fetchLayerFramesRecursive(prevLayer) |
fetchLayerFramesRecursive(currLayer);
}
KisLayerSP prevLayer;
KisLayerSP currLayer;
KisNodeList allSrcNodes() override {
KisNodeList mergedNodes;
mergedNodes << currLayer;
mergedNodes << prevLayer;
return mergedNodes;
}
KisLayerSP dstLayer() override {
return qobject_cast<KisLayer*>(dstNode.data());
}
};
struct MergeMultipleInfo : public MergeDownInfoBase {
MergeMultipleInfo(KisImageSP _image,
KisNodeList _mergedNodes)
: MergeDownInfoBase(_image),
mergedNodes(_mergedNodes)
{
foreach (KisNodeSP node, mergedNodes) {
frames |= fetchLayerFramesRecursive(node);
}
}
KisNodeList mergedNodes;
KisNodeList allSrcNodes() override {
return mergedNodes;
}
};
typedef QSharedPointer<MergeDownInfoBase> MergeDownInfoBaseSP;
typedef QSharedPointer<MergeDownInfo> MergeDownInfoSP;
typedef QSharedPointer<MergeMultipleInfo> MergeMultipleInfoSP;
struct FillSelectionMasks : public KUndo2Command {
FillSelectionMasks(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
fetchSelectionMasks(m_info->allSrcNodes(), m_info->selectionMasks);
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableColorizeKeyStrokes : public KisCommandUtils::AggregateCommand {
DisableColorizeKeyStrokes(MergeDownInfoBaseSP info) : m_info(info) {}
void populateChildCommands() override {
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
recursiveApplyNodes(node,
[this] (KisNodeSP node) {
if (dynamic_cast<KisColorizeMask*>(node.data()) &&
KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::colorizeEditKeyStrokes,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
});
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct DisableOnionSkins : public KisCommandUtils::AggregateCommand {
DisableOnionSkins(MergeDownInfoBaseSP info) : m_info(info) {}
void populateChildCommands() override {
Q_FOREACH (KisNodeSP node, m_info->allSrcNodes()) {
recursiveApplyNodes(node,
[this] (KisNodeSP node) {
if (KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::onionSkins, false).toBool()) {
KisBaseNode::PropertyList props = node->sectionModelProperties();
KisLayerPropertiesIcons::setNodeProperty(&props,
KisLayerPropertiesIcons::onionSkins,
false);
addCommand(new KisNodePropertyListCommand(node, props));
}
});
}
}
private:
MergeDownInfoBaseSP m_info;
};
struct RefreshHiddenAreas : public KUndo2Command {
RefreshHiddenAreas(MergeDownInfoBaseSP info) : m_info(info) {}
void redo() override {
KisImageAnimationInterface *interface = m_info->image->animationInterface();
const QRect preparedRect = !interface->externalFrameActive() ?
m_info->image->bounds() : QRect();
foreach (KisNodeSP node, m_info->allSrcNodes()) {
refreshHiddenAreaAsync(node, preparedRect);
}
}
private:
QRect realNodeExactBounds(KisNodeSP rootNode, QRect currentRect = QRect()) {
KisNodeSP node = rootNode->firstChild();
while(node) {
currentRect |= realNodeExactBounds(node, currentRect);
node = node->nextSibling();
}
// TODO: it would be better to count up changeRect inside
// node's extent() method
currentRect |= rootNode->projectionPlane()->changeRect(rootNode->exactBounds());
return currentRect;
}
void refreshHiddenAreaAsync(KisNodeSP rootNode, const QRect &preparedArea) {
QRect realNodeRect = realNodeExactBounds(rootNode);
if (!preparedArea.contains(realNodeRect)) {
QRegion dirtyRegion = realNodeRect;
dirtyRegion -= preparedArea;
foreach(const QRect &rc, dirtyRegion.rects()) {
m_info->image->refreshGraphAsync(rootNode, rc, realNodeRect);
}
}
}
private:
MergeDownInfoBaseSP m_info;
};
+ struct RefreshDelayedUpdateLayers : public KUndo2Command {
+ RefreshDelayedUpdateLayers(MergeDownInfoBaseSP info) : m_info(info) {}
+
+ void redo() override {
+ foreach (KisNodeSP node, m_info->allSrcNodes()) {
+ recursiveApplyNodes(node,
+ [] (KisNodeSP node) {
+ KisDelayedUpdateNodeInterface *delayedUpdate =
+ dynamic_cast<KisDelayedUpdateNodeInterface*>(node.data());
+ if (delayedUpdate) {
+ delayedUpdate->forceUpdateTimedNode();
+ }
+ });
+ }
+ }
+
+ private:
+ MergeDownInfoBaseSP m_info;
+ };
+
struct KeepMergedNodesSelected : public KisCommandUtils::AggregateCommand {
KeepMergedNodesSelected(MergeDownInfoSP info, bool finalizing)
: m_singleInfo(info),
m_finalizing(finalizing) {}
KeepMergedNodesSelected(MergeMultipleInfoSP info, KisNodeSP putAfter, bool finalizing)
: m_multipleInfo(info),
m_finalizing(finalizing),
m_putAfter(putAfter) {}
void populateChildCommands() override {
KisNodeSP prevNode;
KisNodeSP nextNode;
KisNodeList prevSelection;
KisNodeList nextSelection;
KisImageSP image;
if (m_singleInfo) {
prevNode = m_singleInfo->currLayer;
nextNode = m_singleInfo->dstNode;
image = m_singleInfo->image;
} else if (m_multipleInfo) {
prevNode = m_putAfter;
nextNode = m_multipleInfo->dstNode;
prevSelection = m_multipleInfo->allSrcNodes();
image = m_multipleInfo->image;
}
if (!m_finalizing) {
addCommand(new KeepNodesSelectedCommand(prevSelection, KisNodeList(),
prevNode, KisNodeSP(),
image, false));
} else {
addCommand(new KeepNodesSelectedCommand(KisNodeList(), nextSelection,
KisNodeSP(), nextNode,
image, true));
}
}
private:
MergeDownInfoSP m_singleInfo;
MergeMultipleInfoSP m_multipleInfo;
bool m_finalizing;
KisNodeSP m_putAfter;
};
struct CreateMergedLayer : public KisCommandUtils::AggregateCommand {
CreateMergedLayer(MergeDownInfoSP info) : m_info(info) {}
void populateChildCommands() override {
// actual merging done by KisLayer::createMergedLayer (or specialized decendant)
m_info->dstNode = m_info->currLayer->createMergedLayerTemplate(m_info->prevLayer);
if (m_info->frames.size() > 0) {
m_info->dstNode->enableAnimation();
m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
}
}
private:
MergeDownInfoSP m_info;
};
struct CreateMergedLayerMultiple : public KisCommandUtils::AggregateCommand {
CreateMergedLayerMultiple(MergeMultipleInfoSP info, const QString name = QString() )
: m_info(info),
m_name(name) {}
void populateChildCommands() override {
QString mergedLayerName;
if (m_name.isEmpty()){
const QString mergedLayerSuffix = i18n("Merged");
mergedLayerName = m_info->mergedNodes.first()->name();
if (!mergedLayerName.endsWith(mergedLayerSuffix)) {
mergedLayerName = QString("%1 %2")
.arg(mergedLayerName).arg(mergedLayerSuffix);
}
} else {
mergedLayerName = m_name;
}
m_info->dstNode = new KisPaintLayer(m_info->image, mergedLayerName, OPACITY_OPAQUE_U8);
if (m_info->frames.size() > 0) {
m_info->dstNode->enableAnimation();
m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
}
QString compositeOpId;
QBitArray channelFlags;
bool compositionVaries = false;
foreach (KisNodeSP node, m_info->allSrcNodes()) {
if (compositeOpId.isEmpty()) {
compositeOpId = node->compositeOpId();
} else if (compositeOpId != node->compositeOpId()) {
compositionVaries = true;
break;
}
KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
if (layer && layer->layerStyle()) {
compositionVaries = true;
break;
}
}
if (!compositionVaries) {
if (!compositeOpId.isEmpty()) {
m_info->dstNode->setCompositeOpId(compositeOpId);
}
if (m_info->dstLayer() && !channelFlags.isEmpty()) {
m_info->dstLayer()->setChannelFlags(channelFlags);
}
}
}
private:
MergeMultipleInfoSP m_info;
QString m_name;
};
struct MergeLayers : public KisCommandUtils::AggregateCommand {
MergeLayers(MergeDownInfoSP info) : m_info(info) {}
void populateChildCommands() override {
// actual merging done by KisLayer::createMergedLayer (or specialized decendant)
m_info->currLayer->fillMergedLayerTemplate(m_info->dstLayer(), m_info->prevLayer);
}
private:
MergeDownInfoSP m_info;
};
struct MergeLayersMultiple : public KisCommandUtils::AggregateCommand {
MergeLayersMultiple(MergeMultipleInfoSP info) : m_info(info) {}
void populateChildCommands() override {
KisPainter gc(m_info->dstNode->paintDevice());
foreach (KisNodeSP node, m_info->allSrcNodes()) {
QRect rc = node->exactBounds() | m_info->image->bounds();
node->projectionPlane()->apply(&gc, rc);
}
}
private:
MergeMultipleInfoSP m_info;
};
struct MergeMetaData : public KUndo2Command {
MergeMetaData(MergeDownInfoSP info, const KisMetaData::MergeStrategy* strategy)
: m_info(info),
m_strategy(strategy) {}
void redo() override {
QRect layerProjectionExtent = m_info->currLayer->projection()->extent();
QRect prevLayerProjectionExtent = m_info->prevLayer->projection()->extent();
int prevLayerArea = prevLayerProjectionExtent.width() * prevLayerProjectionExtent.height();
int layerArea = layerProjectionExtent.width() * layerProjectionExtent.height();
QList<double> scores;
double norm = qMax(prevLayerArea, layerArea);
scores.append(prevLayerArea / norm);
scores.append(layerArea / norm);
QList<const KisMetaData::Store*> srcs;
srcs.append(m_info->prevLayer->metaData());
srcs.append(m_info->currLayer->metaData());
m_strategy->merge(m_info->dstLayer()->metaData(), srcs, scores);
}
private:
MergeDownInfoSP m_info;
const KisMetaData::MergeStrategy *m_strategy;
};
KeepNodesSelectedCommand::KeepNodesSelectedCommand(const KisNodeList &selectedBefore,
const KisNodeList &selectedAfter,
KisNodeSP activeBefore,
KisNodeSP activeAfter,
KisImageSP image,
bool finalize, KUndo2Command *parent)
: FlipFlopCommand(finalize, parent),
m_selectedBefore(selectedBefore),
m_selectedAfter(selectedAfter),
m_activeBefore(activeBefore),
m_activeAfter(activeAfter),
m_image(image)
{
}
void KeepNodesSelectedCommand::end() {
KisImageSignalType type;
if (isFinalizing()) {
type = ComplexNodeReselectionSignal(m_activeAfter, m_selectedAfter);
} else {
type = ComplexNodeReselectionSignal(m_activeBefore, m_selectedBefore);
}
m_image->signalRouter()->emitNotification(type);
}
KisLayerSP constructDefaultLayer(KisImageSP image) {
return new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, image->colorSpace());
}
RemoveNodeHelper::~RemoveNodeHelper()
{
}
/**
* The removal of two nodes in one go may be a bit tricky, because one
* of them may be the clone of another. If we remove the source of a
* clone layer, it will reincarnate into a paint layer. In this case
* the pointer to the second layer will be lost.
*
* That's why we need to care about the order of the nodes removal:
* the clone --- first, the source --- last.
*/
void RemoveNodeHelper::safeRemoveMultipleNodes(KisNodeList nodes, KisImageSP image) {
const bool lastLayer = scanForLastLayer(image, nodes);
while (!nodes.isEmpty()) {
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if (!checkIsSourceForClone(*it, nodes)) {
KisNodeSP node = *it;
addCommandImpl(new KisImageLayerRemoveCommand(image, node, false, true));
it = nodes.erase(it);
} else {
++it;
}
}
}
if (lastLayer) {
KisLayerSP newLayer = constructDefaultLayer(image);
addCommandImpl(new KisImageLayerAddCommand(image, newLayer,
image->root(),
KisNodeSP(),
false, false));
}
}
bool RemoveNodeHelper::checkIsSourceForClone(KisNodeSP src, const KisNodeList &nodes) {
foreach (KisNodeSP node, nodes) {
if (node == src) continue;
KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node.data());
if (clone && KisNodeSP(clone->copyFrom()) == src) {
return true;
}
}
return false;
}
bool RemoveNodeHelper::scanForLastLayer(KisImageWSP image, KisNodeList nodesToRemove) {
bool removeLayers = false;
Q_FOREACH(KisNodeSP nodeToRemove, nodesToRemove) {
if (qobject_cast<KisLayer*>(nodeToRemove.data())) {
removeLayers = true;
break;
}
}
if (!removeLayers) return false;
bool lastLayer = true;
KisNodeSP node = image->root()->firstChild();
while (node) {
if (!nodesToRemove.contains(node) &&
qobject_cast<KisLayer*>(node.data())) {
lastLayer = false;
break;
}
node = node->nextSibling();
}
return lastLayer;
}
SimpleRemoveLayers::SimpleRemoveLayers(const KisNodeList &nodes,
KisImageSP image)
: m_nodes(nodes),
m_image(image)
{
}
void SimpleRemoveLayers::populateChildCommands() {
if (m_nodes.isEmpty()) return;
safeRemoveMultipleNodes(m_nodes, m_image);
}
void SimpleRemoveLayers::addCommandImpl(KUndo2Command *cmd) {
addCommand(cmd);
}
struct InsertNode : public KisCommandUtils::AggregateCommand {
InsertNode(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info), m_putAfter(putAfter) {}
void populateChildCommands() override {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
m_putAfter->parent(),
m_putAfter,
true, false));
}
private:
virtual void addCommandImpl(KUndo2Command *cmd) {
addCommand(cmd);
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
struct CleanUpNodes : private RemoveNodeHelper, public KisCommandUtils::AggregateCommand {
CleanUpNodes(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info), m_putAfter(putAfter) {}
static void findPerfectParent(KisNodeList nodesToDelete, KisNodeSP &putAfter, KisNodeSP &parent) {
if (!putAfter) {
putAfter = nodesToDelete.last();
}
// Add the new merged node on top of the active node -- checking
// whether the parent is going to be deleted
parent = putAfter->parent();
while (parent && nodesToDelete.contains(parent)) {
parent = parent->parent();
}
}
void populateChildCommands() override {
KisNodeList nodesToDelete = m_info->allSrcNodes();
KisNodeSP parent;
findPerfectParent(nodesToDelete, m_putAfter, parent);
if (!parent) {
KisNodeSP oldRoot = m_info->image->root();
KisNodeSP newRoot(new KisGroupLayer(m_info->image, "root", OPACITY_OPAQUE_U8));
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
newRoot,
KisNodeSP(),
true, false));
addCommand(new KisImageChangeLayersCommand(m_info->image, oldRoot, newRoot));
}
else {
if (parent == m_putAfter->parent()) {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
parent,
m_putAfter,
true, false));
}
else {
addCommand(new KisImageLayerAddCommand(m_info->image,
m_info->dstNode,
parent,
parent->lastChild(),
true, false));
}
reparentSelectionMasks(m_info->image,
m_info->dstLayer(),
m_info->selectionMasks);
safeRemoveMultipleNodes(m_info->allSrcNodes(), m_info->image);
}
}
private:
void addCommandImpl(KUndo2Command *cmd) override {
addCommand(cmd);
}
void reparentSelectionMasks(KisImageSP image,
KisLayerSP newLayer,
const QVector<KisSelectionMaskSP> &selectionMasks) {
foreach (KisSelectionMaskSP mask, selectionMasks) {
addCommand(new KisImageLayerMoveCommand(image, mask, newLayer, newLayer->lastChild()));
addCommand(new KisActivateSelectionMaskCommand(mask, false));
}
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
SwitchFrameCommand::SharedStorage::~SharedStorage() {
}
SwitchFrameCommand::SwitchFrameCommand(KisImageSP image, int time, bool finalize, SharedStorageSP storage)
: FlipFlopCommand(finalize),
m_image(image),
m_newTime(time),
m_storage(storage) {}
SwitchFrameCommand::~SwitchFrameCommand() {}
void SwitchFrameCommand::init() {
KisImageAnimationInterface *interface = m_image->animationInterface();
const int currentTime = interface->currentTime();
if (currentTime == m_newTime) {
m_storage->value = m_newTime;
return;
}
interface->image()->disableUIUpdates();
interface->saveAndResetCurrentTime(m_newTime, &m_storage->value);
}
void SwitchFrameCommand::end() {
KisImageAnimationInterface *interface = m_image->animationInterface();
const int currentTime = interface->currentTime();
if (currentTime == m_storage->value) {
return;
}
interface->restoreCurrentTime(&m_storage->value);
interface->image()->enableUIUpdates();
}
struct AddNewFrame : public KisCommandUtils::AggregateCommand {
AddNewFrame(MergeDownInfoBaseSP info, int frame) : m_info(info), m_frame(frame) {}
void populateChildCommands() override {
KUndo2Command *cmd = new KisCommandUtils::SkipFirstRedoWrapper();
KisKeyframeChannel *channel = m_info->dstNode->getKeyframeChannel(KisKeyframeChannel::Content.id());
channel->addKeyframe(m_frame, cmd);
addCommand(cmd);
}
private:
MergeDownInfoBaseSP m_info;
int m_frame;
};
QSet<int> fetchLayerFrames(KisNodeSP node) {
KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id());
if (!channel) return QSet<int>();
return channel->allKeyframeIds();
}
QSet<int> fetchLayerFramesRecursive(KisNodeSP rootNode) {
QSet<int> frames = fetchLayerFrames(rootNode);
KisNodeSP node = rootNode->firstChild();
while(node) {
frames |= fetchLayerFramesRecursive(node);
node = node->nextSibling();
}
return frames;
}
void updateFrameJobs(FrameJobs *jobs, KisNodeSP node) {
QSet<int> frames = fetchLayerFrames(node);
if (frames.isEmpty()) {
(*jobs)[0].insert(node);
} else {
foreach (int frame, frames) {
(*jobs)[frame].insert(node);
}
}
}
void updateFrameJobsRecursive(FrameJobs *jobs, KisNodeSP rootNode) {
updateFrameJobs(jobs, rootNode);
KisNodeSP node = rootNode->firstChild();
while(node) {
updateFrameJobsRecursive(jobs, node);
node = node->nextSibling();
}
}
void mergeDown(KisImageSP image, KisLayerSP layer, const KisMetaData::MergeStrategy* strategy)
{
if (!layer->prevSibling()) return;
// XXX: this breaks if we allow free mixing of masks and layers
KisLayerSP prevLayer = qobject_cast<KisLayer*>(layer->prevSibling().data());
if (!prevLayer) return;
if (!layer->visible() && !prevLayer->visible()) {
return;
}
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
kundo2_i18n("Merge Down"));
if (layer->visible() && prevLayer->visible()) {
MergeDownInfoSP info(new MergeDownInfo(image, prevLayer, layer));
// disable key strokes on all colorize masks, all onion skins on
// paint layers and wait until update is finished with a barrier
applicator.applyCommand(new DisableColorizeKeyStrokes(info));
applicator.applyCommand(new DisableOnionSkins(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KeepMergedNodesSelected(info, false));
applicator.applyCommand(new FillSelectionMasks(info));
applicator.applyCommand(new CreateMergedLayer(info), KisStrokeJobData::BARRIER);
if (info->frames.size() > 0) {
foreach (int frame, info->frames) {
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage));
applicator.applyCommand(new AddNewFrame(info, frame));
applicator.applyCommand(new RefreshHiddenAreas(info));
+ applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage));
}
} else {
applicator.applyCommand(new RefreshHiddenAreas(info));
+ applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayers(info), KisStrokeJobData::BARRIER);
}
applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER);
applicator.applyCommand(new CleanUpNodes(info, layer),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepMergedNodesSelected(info, true));
} else if (layer->visible()) {
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
layer, KisNodeSP(),
image, false));
applicator.applyCommand(
new SimpleRemoveLayers(KisNodeList() << prevLayer,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
KisNodeSP(), layer,
image, true));
} else if (prevLayer->visible()) {
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
layer, KisNodeSP(),
image, false));
applicator.applyCommand(
new SimpleRemoveLayers(KisNodeList() << layer,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new KeepNodesSelectedCommand(KisNodeList(), KisNodeList(),
KisNodeSP(), prevLayer,
image, true));
}
applicator.end();
}
bool checkIsChildOf(KisNodeSP node, const KisNodeList &parents)
{
KisNodeList nodeParents;
KisNodeSP parent = node->parent();
while (parent) {
nodeParents << parent;
parent = parent->parent();
}
foreach(KisNodeSP perspectiveParent, parents) {
if (nodeParents.contains(perspectiveParent)) {
return true;
}
}
return false;
}
bool checkIsCloneOf(KisNodeSP node, const KisNodeList &nodes)
{
bool result = false;
KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node.data());
if (clone) {
KisNodeSP cloneSource = KisNodeSP(clone->copyFrom());
Q_FOREACH(KisNodeSP subtree, nodes) {
result =
recursiveFindNode(subtree,
[cloneSource](KisNodeSP node) -> bool
{
return node == cloneSource;
});
if (!result) {
result = checkIsCloneOf(cloneSource, nodes);
}
if (result) {
break;
}
}
}
return result;
}
void filterMergableNodes(KisNodeList &nodes, bool allowMasks)
{
KisNodeList::iterator it = nodes.begin();
while (it != nodes.end()) {
if ((!allowMasks && !qobject_cast<KisLayer*>(it->data())) ||
checkIsChildOf(*it, nodes)) {
qDebug() << "Skipping node" << ppVar((*it)->name());
it = nodes.erase(it);
} else {
++it;
}
}
}
void sortMergableNodes(KisNodeSP root, KisNodeList &inputNodes, KisNodeList &outputNodes)
{
KisNodeList::iterator it = std::find(inputNodes.begin(), inputNodes.end(), root);
if (it != inputNodes.end()) {
outputNodes << *it;
inputNodes.erase(it);
}
if (inputNodes.isEmpty()) {
return;
}
KisNodeSP child = root->firstChild();
while (child) {
sortMergableNodes(child, inputNodes, outputNodes);
child = child->nextSibling();
}
/**
* By the end of recursion \p inputNodes must be empty
*/
KIS_ASSERT_RECOVER_NOOP(root->parent() || inputNodes.isEmpty());
}
KisNodeList sortMergableNodes(KisNodeSP root, KisNodeList nodes)
{
KisNodeList result;
sortMergableNodes(root, nodes, result);
return result;
}
KisNodeList sortAndFilterMergableInternalNodes(KisNodeList nodes, bool allowMasks)
{
KIS_ASSERT_RECOVER(!nodes.isEmpty()) { return nodes; }
KisNodeSP root;
Q_FOREACH(KisNodeSP node, nodes) {
KisNodeSP localRoot = node;
while (localRoot->parent()) {
localRoot = localRoot->parent();
}
if (!root) {
root = localRoot;
}
KIS_ASSERT_RECOVER(root == localRoot) { return nodes; }
}
KisNodeList result;
sortMergableNodes(root, nodes, result);
filterMergableNodes(result, allowMasks);
return result;
}
void addCopyOfNameTag(KisNodeSP node)
{
const QString prefix = i18n("Copy of");
QString newName = node->name();
if (!newName.startsWith(prefix)) {
newName = QString("%1 %2").arg(prefix).arg(newName);
node->setName(newName);
}
}
KisNodeList findNodesWithProps(KisNodeSP root, const KoProperties &props, bool excludeRoot)
{
KisNodeList nodes;
if ((!excludeRoot || root->parent()) && root->check(props)) {
nodes << root;
}
KisNodeSP node = root->firstChild();
while (node) {
nodes += findNodesWithProps(node, props, excludeRoot);
node = node->nextSibling();
}
return nodes;
}
KisNodeList filterInvisibleNodes(const KisNodeList &nodes, KisNodeList *invisibleNodes, KisNodeSP *putAfter)
{
KIS_ASSERT_RECOVER(invisibleNodes) { return nodes; }
KIS_ASSERT_RECOVER(putAfter) { return nodes; }
KisNodeList visibleNodes;
int putAfterIndex = -1;
Q_FOREACH(KisNodeSP node, nodes) {
if (node->visible()) {
visibleNodes << node;
} else {
*invisibleNodes << node;
if (node == *putAfter) {
putAfterIndex = visibleNodes.size() - 1;
}
}
}
if (!visibleNodes.isEmpty() && putAfterIndex >= 0) {
putAfterIndex = qBound(0, putAfterIndex, visibleNodes.size() - 1);
*putAfter = visibleNodes[putAfterIndex];
}
return visibleNodes;
}
void changeImageDefaultProjectionColor(KisImageSP image, const KoColor &color)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image,
image->root(),
KisProcessingApplicator::RECURSIVE,
emitSignals,
kundo2_i18n("Change projection color"),
0,
142857 + 1);
applicator.applyCommand(new KisChangeProjectionColorCommand(image, color), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE);
applicator.end();
}
void mergeMultipleLayersImpl(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter,
bool flattenSingleLayer, const KUndo2MagicString &actionName,
bool cleanupNodes = true, const QString layerName = QString())
{
if (!putAfter) {
putAfter = mergedNodes.first();
}
filterMergableNodes(mergedNodes);
{
KisNodeList tempNodes;
qSwap(mergedNodes, tempNodes);
sortMergableNodes(image->root(), tempNodes, mergedNodes);
}
if (mergedNodes.size() <= 1 &&
(!flattenSingleLayer && mergedNodes.size() == 1)) return;
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
emitSignals << ComplexNodeReselectionSignal(KisNodeSP(), KisNodeList(), KisNodeSP(), mergedNodes);
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
actionName);
KisNodeList originalNodes = mergedNodes;
KisNodeList invisibleNodes;
mergedNodes = filterInvisibleNodes(originalNodes, &invisibleNodes, &putAfter);
if (!invisibleNodes.isEmpty()) {
applicator.applyCommand(
new SimpleRemoveLayers(invisibleNodes,
image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
if (mergedNodes.size() > 1 || invisibleNodes.isEmpty()) {
MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes));
// disable key strokes on all colorize masks, all onion skins on
// paint layers and wait until update is finished with a barrier
applicator.applyCommand(new DisableColorizeKeyStrokes(info));
applicator.applyCommand(new DisableOnionSkins(info));
applicator.applyCommand(new KUndo2Command(), KisStrokeJobData::BARRIER);
applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, false));
applicator.applyCommand(new FillSelectionMasks(info));
applicator.applyCommand(new CreateMergedLayerMultiple(info, layerName), KisStrokeJobData::BARRIER);
if (info->frames.size() > 0) {
foreach (int frame, info->frames) {
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, false, info->storage));
applicator.applyCommand(new AddNewFrame(info, frame));
applicator.applyCommand(new RefreshHiddenAreas(info));
+ applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new SwitchFrameCommand(info->image, frame, true, info->storage));
}
} else {
applicator.applyCommand(new RefreshHiddenAreas(info));
+ applicator.applyCommand(new RefreshDelayedUpdateLayers(info), KisStrokeJobData::BARRIER);
applicator.applyCommand(new MergeLayersMultiple(info), KisStrokeJobData::BARRIER);
}
//applicator.applyCommand(new MergeMetaData(info, strategy), KisStrokeJobData::BARRIER);
if (cleanupNodes){
applicator.applyCommand(new CleanUpNodes(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
} else {
applicator.applyCommand(new InsertNode(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
applicator.applyCommand(new KeepMergedNodesSelected(info, putAfter, true));
}
applicator.end();
}
void mergeMultipleLayers(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter)
{
mergeMultipleLayersImpl(image, mergedNodes, putAfter, false, kundo2_i18n("Merge Selected Nodes"));
}
void newLayerFromVisible(KisImageSP image, KisNodeSP putAfter)
{
KisNodeList mergedNodes;
mergedNodes << image->root();
mergeMultipleLayersImpl(image, mergedNodes, putAfter, true, kundo2_i18n("New From Visible"), false, i18nc("New layer created from all the visible layers", "Visible"));
}
struct MergeSelectionMasks : public KisCommandUtils::AggregateCommand {
MergeSelectionMasks(MergeDownInfoBaseSP info, KisNodeSP putAfter)
: m_info(info),
m_putAfter(putAfter){}
void populateChildCommands() override {
KisNodeSP parent;
CleanUpNodes::findPerfectParent(m_info->allSrcNodes(), m_putAfter, parent);
KisLayerSP parentLayer;
do {
parentLayer = qobject_cast<KisLayer*>(parent.data());
parent = parent->parent();
} while(!parentLayer && parent);
KisSelectionSP selection = new KisSelection();
foreach (KisNodeSP node, m_info->allSrcNodes()) {
KisMaskSP mask = dynamic_cast<KisMask*>(node.data());
if (!mask) continue;
selection->pixelSelection()->applySelection(
mask->selection()->pixelSelection(), SELECTION_ADD);
}
KisSelectionMaskSP mergedMask = new KisSelectionMask(m_info->image);
mergedMask->initSelection(parentLayer);
mergedMask->setSelection(selection);
m_info->dstNode = mergedMask;
}
private:
MergeDownInfoBaseSP m_info;
KisNodeSP m_putAfter;
};
struct ActivateSelectionMask : public KisCommandUtils::AggregateCommand {
ActivateSelectionMask(MergeDownInfoBaseSP info)
: m_info(info) {}
void populateChildCommands() override {
KisSelectionMaskSP mergedMask = dynamic_cast<KisSelectionMask*>(m_info->dstNode.data());
addCommand(new KisActivateSelectionMaskCommand(mergedMask, true));
}
private:
MergeDownInfoBaseSP m_info;
};
bool tryMergeSelectionMasks(KisImageSP image, KisNodeList mergedNodes, KisNodeSP putAfter)
{
QList<KisSelectionMaskSP> selectionMasks;
for (auto it = mergedNodes.begin(); it != mergedNodes.end(); /*noop*/) {
KisSelectionMaskSP mask = dynamic_cast<KisSelectionMask*>(it->data());
if (!mask) {
it = mergedNodes.erase(it);
} else {
selectionMasks.append(mask);
++it;
}
}
if (mergedNodes.isEmpty()) return false;
KisLayerSP parentLayer = qobject_cast<KisLayer*>(selectionMasks.first()->parent().data());
KIS_ASSERT_RECOVER(parentLayer) { return 0; }
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(image, 0,
KisProcessingApplicator::NONE,
emitSignals,
kundo2_i18n("Merge Selection Masks"));
MergeMultipleInfoSP info(new MergeMultipleInfo(image, mergedNodes));
applicator.applyCommand(new MergeSelectionMasks(info, putAfter));
applicator.applyCommand(new CleanUpNodes(info, putAfter),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.applyCommand(new ActivateSelectionMask(info));
applicator.end();
return true;
}
void flattenLayer(KisImageSP image, KisLayerSP layer)
{
if (!layer->childCount() && !layer->layerStyle())
return;
KisNodeList mergedNodes;
mergedNodes << layer;
mergeMultipleLayersImpl(image, mergedNodes, layer, true, kundo2_i18n("Flatten Layer"));
}
void flattenImage(KisImageSP image)
{
KisNodeList mergedNodes;
mergedNodes << image->root();
mergeMultipleLayersImpl(image, mergedNodes, 0, true, kundo2_i18n("Flatten Image"));
}
KisSimpleUpdateCommand::KisSimpleUpdateCommand(KisNodeList nodes, bool finalize, KUndo2Command *parent)
: FlipFlopCommand(finalize, parent),
m_nodes(nodes)
{
}
void KisSimpleUpdateCommand::end()
{
updateNodes(m_nodes);
}
void KisSimpleUpdateCommand::updateNodes(const KisNodeList &nodes)
{
Q_FOREACH(KisNodeSP node, nodes) {
node->setDirty(node->extent());
}
}
void recursiveApplyNodes(KisNodeSP node, std::function<void(KisNodeSP)> func)
{
func(node);
node = node->firstChild();
while (node) {
recursiveApplyNodes(node, func);
node = node->nextSibling();
}
}
KisNodeSP recursiveFindNode(KisNodeSP node, std::function<bool(KisNodeSP)> func)
{
if (func(node)) {
return node;
}
node = node->firstChild();
while (node) {
KisNodeSP resultNode = recursiveFindNode(node, func);
if (resultNode) {
return resultNode;
}
node = node->nextSibling();
}
return 0;
}
KisNodeSP findNodeByUuid(KisNodeSP root, const QUuid &uuid)
{
return recursiveFindNode(root,
[uuid] (KisNodeSP node) {
return node->uuid() == uuid;
});
}
}
diff --git a/libs/image/kis_liquify_transform_worker.cpp b/libs/image/kis_liquify_transform_worker.cpp
index f50d7fe29b..3b21d936ae 100644
--- a/libs/image/kis_liquify_transform_worker.cpp
+++ b/libs/image/kis_liquify_transform_worker.cpp
@@ -1,592 +1,592 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_liquify_transform_worker.h"
#include "kis_grid_interpolation_tools.h"
#include "kis_dom_utils.h"
#include "krita_utils.h"
struct Q_DECL_HIDDEN KisLiquifyTransformWorker::Private
{
Private(const QRect &_srcBounds,
KoUpdater *_progress,
int _pixelPrecision)
: srcBounds(_srcBounds),
progress(_progress),
pixelPrecision(_pixelPrecision)
{
}
const QRect srcBounds;
QVector<QPointF> originalPoints;
QVector<QPointF> transformedPoints;
KoUpdater *progress;
int pixelPrecision;
QSize gridSize;
void preparePoints();
struct MapIndexesOp;
template <class ProcessOp>
void processTransformedPixelsBuildUp(ProcessOp op,
const QPointF &base,
qreal sigma);
template <class ProcessOp>
void processTransformedPixelsWash(ProcessOp op,
const QPointF &base,
qreal sigma,
qreal flow);
template <class ProcessOp>
void processTransformedPixels(ProcessOp op,
const QPointF &base,
qreal sigma,
bool useWashMode,
qreal flow);
};
KisLiquifyTransformWorker::KisLiquifyTransformWorker(const QRect &srcBounds,
KoUpdater *progress,
int pixelPrecision)
: m_d(new Private(srcBounds, progress, pixelPrecision))
{
KIS_ASSERT_RECOVER_RETURN(!srcBounds.isEmpty());
// TODO: implement 'progress' stuff
m_d->preparePoints();
}
KisLiquifyTransformWorker::KisLiquifyTransformWorker(const KisLiquifyTransformWorker &rhs)
: m_d(new Private(*rhs.m_d.data()))
{
}
KisLiquifyTransformWorker::~KisLiquifyTransformWorker()
{
}
bool KisLiquifyTransformWorker::operator==(const KisLiquifyTransformWorker &other) const
{
return
m_d->srcBounds == other.m_d->srcBounds &&
m_d->originalPoints == other.m_d->originalPoints &&
m_d->transformedPoints == other.m_d->transformedPoints &&
m_d->pixelPrecision == other.m_d->pixelPrecision &&
m_d->gridSize == other.m_d->gridSize;
}
int KisLiquifyTransformWorker::pointToIndex(const QPoint &cellPt)
{
return GridIterationTools::pointToIndex(cellPt, m_d->gridSize);
}
QSize KisLiquifyTransformWorker::gridSize() const
{
return m_d->gridSize;
}
const QVector<QPointF>& KisLiquifyTransformWorker::originalPoints() const
{
return m_d->originalPoints;
}
QVector<QPointF>& KisLiquifyTransformWorker::transformedPoints()
{
return m_d->transformedPoints;
}
struct AllPointsFetcherOp
{
AllPointsFetcherOp(QRectF srcRect) : m_srcRect(srcRect) {}
inline void processPoint(int col, int row,
int prevCol, int prevRow,
int colIndex, int rowIndex) {
Q_UNUSED(prevCol);
Q_UNUSED(prevRow);
Q_UNUSED(colIndex);
Q_UNUSED(rowIndex);
QPointF pt(col, row);
m_points << pt;
}
inline void nextLine() {
}
QVector<QPointF> m_points;
QRectF m_srcRect;
};
void KisLiquifyTransformWorker::Private::preparePoints()
{
gridSize =
GridIterationTools::calcGridSize(srcBounds, pixelPrecision);
AllPointsFetcherOp pointsOp(srcBounds);
GridIterationTools::processGrid(pointsOp, srcBounds, pixelPrecision);
const int numPoints = pointsOp.m_points.size();
KIS_ASSERT_RECOVER_RETURN(numPoints == gridSize.width() * gridSize.height());
originalPoints = pointsOp.m_points;
transformedPoints = pointsOp.m_points;
}
void KisLiquifyTransformWorker::translate(const QPointF &offset)
{
QVector<QPointF>::iterator it = m_d->transformedPoints.begin();
QVector<QPointF>::iterator end = m_d->transformedPoints.end();
QVector<QPointF>::iterator refIt = m_d->originalPoints.begin();
KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() ==
m_d->transformedPoints.size());
for (; it != end; ++it, ++refIt) {
*it += offset;
*refIt += offset;
}
}
void KisLiquifyTransformWorker::undoPoints(const QPointF &base,
qreal amount,
qreal sigma)
{
const qreal maxDistCoeff = 3.0;
const qreal maxDist = maxDistCoeff * sigma;
QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
2 * maxDist, 2 * maxDist);
QVector<QPointF>::iterator it = m_d->transformedPoints.begin();
QVector<QPointF>::iterator end = m_d->transformedPoints.end();
QVector<QPointF>::iterator refIt = m_d->originalPoints.begin();
KIS_ASSERT_RECOVER_RETURN(m_d->originalPoints.size() ==
m_d->transformedPoints.size());
for (; it != end; ++it, ++refIt) {
if (!clipRect.contains(*it)) continue;
QPointF diff = *it - base;
qreal dist = KisAlgebra2D::norm(diff);
if (dist > maxDist) continue;
qreal lambda = exp(-0.5 * pow2(dist / sigma));
lambda *= amount;
*it = *refIt * lambda + *it * (1.0 - lambda);
}
}
template <class ProcessOp>
void KisLiquifyTransformWorker::Private::
processTransformedPixelsBuildUp(ProcessOp op,
const QPointF &base,
qreal sigma)
{
const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
2 * maxDist, 2 * maxDist);
QVector<QPointF>::iterator it = transformedPoints.begin();
QVector<QPointF>::iterator end = transformedPoints.end();
for (; it != end; ++it) {
if (!clipRect.contains(*it)) continue;
QPointF diff = *it - base;
qreal dist = KisAlgebra2D::norm(diff);
if (dist > maxDist) continue;
const qreal lambda = exp(-0.5 * pow2(dist / sigma));
*it = op(*it, base, diff, lambda);
}
}
template <class ProcessOp>
void KisLiquifyTransformWorker::Private::
processTransformedPixelsWash(ProcessOp op,
const QPointF &base,
qreal sigma,
qreal flow)
{
const qreal maxDist = ProcessOp::maxDistCoeff * sigma;
QRectF clipRect(base.x() - maxDist, base.y() - maxDist,
2 * maxDist, 2 * maxDist);
QVector<QPointF>::iterator it = transformedPoints.begin();
QVector<QPointF>::iterator end = transformedPoints.end();
QVector<QPointF>::iterator refIt = originalPoints.begin();
KIS_ASSERT_RECOVER_RETURN(originalPoints.size() ==
transformedPoints.size());
for (; it != end; ++it, ++refIt) {
if (!clipRect.contains(*it)) continue;
QPointF diff = *refIt - base;
qreal dist = KisAlgebra2D::norm(diff);
if (dist > maxDist) continue;
const qreal lambda = exp(-0.5 * pow2(dist / sigma));
QPointF dstPt = op(*refIt, base, diff, lambda);
if (kisDistance(dstPt, *refIt) > kisDistance(*it, *refIt)) {
*it = (1.0 - flow) * (*it) + flow * dstPt;
}
}
}
template <class ProcessOp>
void KisLiquifyTransformWorker::Private::
processTransformedPixels(ProcessOp op,
const QPointF &base,
qreal sigma,
bool useWashMode,
qreal flow)
{
if (useWashMode) {
processTransformedPixelsWash(op, base, sigma, flow);
} else {
processTransformedPixelsBuildUp(op, base, sigma);
}
}
struct TranslateOp
{
TranslateOp(const QPointF &offset) : m_offset(offset) {}
QPointF operator() (const QPointF &pt,
const QPointF &base,
const QPointF &diff,
qreal lambda)
{
Q_UNUSED(base);
Q_UNUSED(diff);
return pt + lambda * m_offset;
}
static const qreal maxDistCoeff;
QPointF m_offset;
};
const qreal TranslateOp::maxDistCoeff = 3.0;
struct ScaleOp
{
ScaleOp(qreal scale) : m_scale(scale) {}
QPointF operator() (const QPointF &pt,
const QPointF &base,
const QPointF &diff,
qreal lambda)
{
Q_UNUSED(pt);
Q_UNUSED(diff);
return base + (1.0 + m_scale * lambda) * diff;
}
static const qreal maxDistCoeff;
qreal m_scale;
};
const qreal ScaleOp::maxDistCoeff = 3.0;
struct RotateOp
{
RotateOp(qreal angle) : m_angle(angle) {}
QPointF operator() (const QPointF &pt,
const QPointF &base,
const QPointF &diff,
qreal lambda)
{
Q_UNUSED(pt);
const qreal angle = m_angle * lambda;
const qreal sinA = std::sin(angle);
const qreal cosA = std::cos(angle);
qreal x = cosA * diff.x() + sinA * diff.y();
qreal y = -sinA * diff.x() + cosA * diff.y();
return base + QPointF(x, y);
}
static const qreal maxDistCoeff;
qreal m_angle;
};
const qreal RotateOp::maxDistCoeff = 3.0;
void KisLiquifyTransformWorker::translatePoints(const QPointF &base,
const QPointF &offset,
qreal sigma,
bool useWashMode,
qreal flow)
{
TranslateOp op(offset);
m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
}
void KisLiquifyTransformWorker::scalePoints(const QPointF &base,
qreal scale,
qreal sigma,
bool useWashMode,
qreal flow)
{
ScaleOp op(scale);
m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
}
void KisLiquifyTransformWorker::rotatePoints(const QPointF &base,
qreal angle,
qreal sigma,
bool useWashMode,
qreal flow)
{
RotateOp op(angle);
m_d->processTransformedPixels(op, base, sigma, useWashMode, flow);
}
struct KisLiquifyTransformWorker::Private::MapIndexesOp {
MapIndexesOp(KisLiquifyTransformWorker::Private *d)
: m_d(d)
{
}
inline QVector<int> calculateMappedIndexes(int col, int row,
int *numExistingPoints) const {
*numExistingPoints = 4;
QVector<int> cellIndexes =
GridIterationTools::calculateCellIndexes(col, row, m_d->gridSize);
return cellIndexes;
}
inline int tryGetValidIndex(const QPoint &cellPt) const {
Q_UNUSED(cellPt);
KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
return -1;
}
inline QPointF getSrcPointForce(const QPoint &cellPt) const {
Q_UNUSED(cellPt);
KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
return QPointF();
}
inline const QPolygonF srcCropPolygon() const {
KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
return QPolygonF();
}
KisLiquifyTransformWorker::Private *m_d;
};
void KisLiquifyTransformWorker::run(KisPaintDeviceSP device)
{
KisPaintDeviceSP srcDev = new KisPaintDevice(*device.data());
device->clear();
using namespace GridIterationTools;
PaintDevicePolygonOp polygonOp(srcDev, device);
Private::MapIndexesOp indexesOp(m_d.data());
iterateThroughGrid<AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
m_d->gridSize,
m_d->originalPoints,
m_d->transformedPoints);
}
QRect KisLiquifyTransformWorker::approxChangeRect(const QRect &rc)
{
const qreal margin = 0.05;
/**
* Here we just return the full area occupied by the transformed grid.
* We sample grid points for not doing too much work.
*/
const int maxSamplePoints = 200;
const int minStep = 3;
const int step = qMax(minStep, m_d->transformedPoints.size() / maxSamplePoints);
QVector<QPoint> samplePoints;
for (int i = 0; i < m_d->transformedPoints.size(); i += step) {
samplePoints << m_d->transformedPoints[i].toPoint();
}
- QRect resultRect = KritaUtils::approximateRectFromPoints(samplePoints);
+ QRect resultRect = KisAlgebra2D::approximateRectFromPoints(samplePoints);
return KisAlgebra2D::blowRect(resultRect | rc, margin);
}
QRect KisLiquifyTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
{
Q_UNUSED(rc);
return fullBounds;
}
#include <functional>
#include <QTransform>
using PointMapFunction = std::function<QPointF (const QPointF&)>;
PointMapFunction bindPointMapTransform(const QTransform &transform) {
using namespace std::placeholders;
typedef QPointF (QTransform::*MapFuncType)(const QPointF&) const;
return std::bind(static_cast<MapFuncType>(&QTransform::map), &transform, _1);
}
QImage KisLiquifyTransformWorker::runOnQImage(const QImage &srcImage,
const QPointF &srcImageOffset,
const QTransform &imageToThumbTransform,
QPointF *newOffset)
{
KIS_ASSERT_RECOVER(m_d->originalPoints.size() == m_d->transformedPoints.size()) {
return QImage();
}
KIS_ASSERT_RECOVER(!srcImage.isNull()) {
return QImage();
}
KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
return QImage();
}
QVector<QPointF> originalPointsLocal(m_d->originalPoints);
QVector<QPointF> transformedPointsLocal(m_d->transformedPoints);
PointMapFunction mapFunc = bindPointMapTransform(imageToThumbTransform);
std::transform(originalPointsLocal.begin(), originalPointsLocal.end(),
originalPointsLocal.begin(), mapFunc);
std::transform(transformedPointsLocal.begin(), transformedPointsLocal.end(),
transformedPointsLocal.begin(), mapFunc);
QRectF dstBounds;
Q_FOREACH (const QPointF &pt, transformedPointsLocal) {
KisAlgebra2D::accumulateBounds(pt, &dstBounds);
}
const QRectF srcBounds(srcImageOffset, srcImage.size());
dstBounds |= srcBounds;
QPointF dstQImageOffset = dstBounds.topLeft();
*newOffset = dstQImageOffset;
QRect dstBoundsI = dstBounds.toAlignedRect();
QImage dstImage(dstBoundsI.size(), srcImage.format());
dstImage.fill(0);
GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcImageOffset, dstQImageOffset);
Private::MapIndexesOp indexesOp(m_d.data());
GridIterationTools::iterateThroughGrid
<GridIterationTools::AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
m_d->gridSize,
originalPointsLocal,
transformedPointsLocal);
return dstImage;
}
void KisLiquifyTransformWorker::toXML(QDomElement *e) const
{
QDomDocument doc = e->ownerDocument();
QDomElement liqEl = doc.createElement("liquify_points");
e->appendChild(liqEl);
KisDomUtils::saveValue(&liqEl, "srcBounds", m_d->srcBounds);
KisDomUtils::saveValue(&liqEl, "originalPoints", m_d->originalPoints);
KisDomUtils::saveValue(&liqEl, "transformedPoints", m_d->transformedPoints);
KisDomUtils::saveValue(&liqEl, "pixelPrecision", m_d->pixelPrecision);
KisDomUtils::saveValue(&liqEl, "gridSize", m_d->gridSize);
}
KisLiquifyTransformWorker* KisLiquifyTransformWorker::fromXML(const QDomElement &e)
{
QDomElement liquifyEl;
QRect srcBounds;
QVector<QPointF> originalPoints;
QVector<QPointF> transformedPoints;
int pixelPrecision;
QSize gridSize;
bool result = false;
result =
KisDomUtils::findOnlyElement(e, "liquify_points", &liquifyEl) &&
KisDomUtils::loadValue(liquifyEl, "srcBounds", &srcBounds) &&
KisDomUtils::loadValue(liquifyEl, "originalPoints", &originalPoints) &&
KisDomUtils::loadValue(liquifyEl, "transformedPoints", &transformedPoints) &&
KisDomUtils::loadValue(liquifyEl, "pixelPrecision", &pixelPrecision) &&
KisDomUtils::loadValue(liquifyEl, "gridSize", &gridSize);
if (!result) {
warnKrita << "WARNING: Failed to load liquify worker from XML";
return new KisLiquifyTransformWorker(QRect(0,0,1024, 1024), 0, 8);
}
KisLiquifyTransformWorker *worker =
new KisLiquifyTransformWorker(srcBounds, 0, pixelPrecision);
const int numPoints = originalPoints.size();
if (numPoints != transformedPoints.size() ||
numPoints != worker->m_d->originalPoints.size() ||
gridSize != worker->m_d->gridSize) {
warnKrita << "WARNING: Inconsistent number of points!";
warnKrita << ppVar(originalPoints.size());
warnKrita << ppVar(transformedPoints.size());
warnKrita << ppVar(gridSize);
warnKrita << ppVar(worker->m_d->originalPoints.size());
warnKrita << ppVar(worker->m_d->transformedPoints.size());
warnKrita << ppVar(worker->m_d->gridSize);
return worker;
}
for (int i = 0; i < numPoints; i++) {
worker->m_d->originalPoints[i] = originalPoints[i];
worker->m_d->transformedPoints[i] = transformedPoints[i];
}
return worker;
}
diff --git a/libs/image/kis_node.cpp b/libs/image/kis_node.cpp
index 14a87655bd..5fedb82dd9 100644
--- a/libs/image/kis_node.cpp
+++ b/libs/image/kis_node.cpp
@@ -1,629 +1,636 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node.h"
#include <QList>
#include <QReadWriteLock>
#include <QReadLocker>
#include <QWriteLocker>
#include <QPainterPath>
#include <QRect>
#include <QCoreApplication>
#include <KoProperties.h>
#include "kis_global.h"
#include "kis_node_graph_listener.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_node_progress_proxy.h"
#include "kis_busy_progress_indicator.h"
#include "kis_clone_layer.h"
#include "kis_safe_read_list.h"
typedef KisSafeReadList<KisNodeSP> KisSafeReadNodeList;
#include "kis_abstract_projection_plane.h"
#include "kis_projection_leaf.h"
#include "kis_undo_adapter.h"
#include "kis_keyframe_channel.h"
/**
*The link between KisProjection ans KisImageUpdater
*uses queued signals with an argument of KisNodeSP type,
*so we should register it beforehand
*/
struct KisNodeSPStaticRegistrar {
KisNodeSPStaticRegistrar() {
qRegisterMetaType<KisNodeSP>("KisNodeSP");
}
};
static KisNodeSPStaticRegistrar __registrar1;
struct KisNodeListStaticRegistrar {
KisNodeListStaticRegistrar() {
qRegisterMetaType<KisNodeList>("KisNodeList");
}
};
static KisNodeListStaticRegistrar __registrar2;
/**
* Note about "thread safety" of KisNode
*
* 1) One can *read* any information about node and node graph in any
* number of threads concurrently. This operation is safe because
* of the usage of KisSafeReadNodeList and will run concurrently
* (lock-free).
*
* 2) One can *write* any information into the node or node graph in a
* single thread only! Changing the graph concurrently is *not*
* sane and therefore not supported.
*
* 3) One can *read and write* information about the node graph
* concurrently, given that there is only *one* writer thread and
* any number of reader threads. Please note that in this case the
* node's code is just guaranteed *not to crash*, which is ensured
* by nodeSubgraphLock. You need to ensure the sanity of the data
* read by the reader threads yourself!
*/
struct Q_DECL_HIDDEN KisNode::Private
{
public:
Private(KisNode *node)
: graphListener(0)
, nodeProgressProxy(0)
, busyProgressIndicator(0)
, projectionLeaf(new KisProjectionLeaf(node))
{
}
KisNodeWSP parent;
KisNodeGraphListener *graphListener;
KisSafeReadNodeList nodes;
KisNodeProgressProxy *nodeProgressProxy;
KisBusyProgressIndicator *busyProgressIndicator;
QReadWriteLock nodeSubgraphLock;
KisProjectionLeafSP projectionLeaf;
const KisNode* findSymmetricClone(const KisNode *srcRoot,
const KisNode *dstRoot,
const KisNode *srcTarget);
void processDuplicatedClones(const KisNode *srcDuplicationRoot,
const KisNode *dstDuplicationRoot,
KisNode *node);
};
/**
* Finds the layer in \p dstRoot subtree, which has the same path as
* \p srcTarget has in \p srcRoot
*/
const KisNode* KisNode::Private::findSymmetricClone(const KisNode *srcRoot,
const KisNode *dstRoot,
const KisNode *srcTarget)
{
if (srcRoot == srcTarget) return dstRoot;
KisSafeReadNodeList::const_iterator srcIter = srcRoot->m_d->nodes.constBegin();
KisSafeReadNodeList::const_iterator dstIter = dstRoot->m_d->nodes.constBegin();
for (; srcIter != srcRoot->m_d->nodes.constEnd(); srcIter++, dstIter++) {
KIS_ASSERT_RECOVER_RETURN_VALUE((srcIter != srcRoot->m_d->nodes.constEnd()) ==
(dstIter != dstRoot->m_d->nodes.constEnd()), 0);
const KisNode *node = findSymmetricClone(srcIter->data(), dstIter->data(), srcTarget);
if (node) return node;
}
return 0;
}
/**
* This function walks through a subtrees of old and new layers and
* searches for clone layers. For each clone layer it checks whether
* its copyFrom() lays inside the old subtree, and if it is so resets
* it to the corresponding layer in the new subtree.
*
* That is needed when the user duplicates a group layer with all its
* layer subtree. In such a case all the "internal" clones must stay
* "internal" and not point to the layers of the older group.
*/
void KisNode::Private::processDuplicatedClones(const KisNode *srcDuplicationRoot,
const KisNode *dstDuplicationRoot,
KisNode *node)
{
if (KisCloneLayer *clone = dynamic_cast<KisCloneLayer*>(node)) {
KIS_ASSERT_RECOVER_RETURN(clone->copyFrom());
const KisNode *newCopyFrom = findSymmetricClone(srcDuplicationRoot,
dstDuplicationRoot,
clone->copyFrom());
if (newCopyFrom) {
KisLayer *newCopyFromLayer = qobject_cast<KisLayer*>(const_cast<KisNode*>(newCopyFrom));
KIS_ASSERT_RECOVER_RETURN(newCopyFromLayer);
clone->setCopyFrom(newCopyFromLayer);
}
}
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, node->m_d->nodes) {
KisNode *child = const_cast<KisNode*>((*iter).data());
processDuplicatedClones(srcDuplicationRoot, dstDuplicationRoot, child);
}
}
KisNode::KisNode()
: m_d(new Private(this))
{
m_d->parent = 0;
m_d->graphListener = 0;
moveToThread(qApp->thread());
}
KisNode::KisNode(const KisNode & rhs)
: KisBaseNode(rhs)
, m_d(new Private(this))
{
m_d->parent = 0;
m_d->graphListener = 0;
moveToThread(qApp->thread());
// NOTE: the nodes are not supposed to be added/removed while
// creation of another node, so we do *no* locking here!
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, rhs.m_d->nodes) {
KisNodeSP child = (*iter)->clone();
child->createNodeProgressProxy();
m_d->nodes.append(child);
child->setParent(this);
}
m_d->processDuplicatedClones(&rhs, this, this);
}
KisNode::~KisNode()
{
if (m_d->busyProgressIndicator) {
m_d->busyProgressIndicator->prepareDestroying();
m_d->busyProgressIndicator->deleteLater();
}
if (m_d->nodeProgressProxy) {
m_d->nodeProgressProxy->prepareDestroying();
m_d->nodeProgressProxy->deleteLater();
}
{
QWriteLocker l(&m_d->nodeSubgraphLock);
m_d->nodes.clear();
}
delete m_d;
}
QRect KisNode::needRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
QRect KisNode::changeRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
QRect KisNode::accessRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
KisAbstractProjectionPlaneSP KisNode::projectionPlane() const
{
KIS_ASSERT_RECOVER_NOOP(0 && "KisNode::projectionPlane() is not defined!");
static KisAbstractProjectionPlaneSP plane =
toQShared(new KisDumbProjectionPlane());
return plane;
}
KisProjectionLeafSP KisNode::projectionLeaf() const
{
return m_d->projectionLeaf;
}
bool KisNode::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisNode::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
visitor.visit(this, undoAdapter);
}
int KisNode::graphSequenceNumber() const
{
return m_d->graphListener ? m_d->graphListener->graphSequenceNumber() : -1;
}
KisNodeGraphListener *KisNode::graphListener() const
{
return m_d->graphListener;
}
void KisNode::setGraphListener(KisNodeGraphListener *graphListener)
{
m_d->graphListener = graphListener;
QReadLocker l(&m_d->nodeSubgraphLock);
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, m_d->nodes) {
KisNodeSP child = (*iter);
child->setGraphListener(graphListener);
}
}
void KisNode::setParent(KisNodeWSP parent)
{
QWriteLocker l(&m_d->nodeSubgraphLock);
m_d->parent = parent;
}
KisNodeSP KisNode::parent() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return m_d->parent.isValid() ? KisNodeSP(m_d->parent) : KisNodeSP();
}
KisBaseNodeSP KisNode::parentCallback() const
{
return parent();
}
void KisNode::notifyParentVisibilityChanged(bool value)
{
QReadLocker l(&m_d->nodeSubgraphLock);
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, m_d->nodes) {
KisNodeSP child = (*iter);
child->notifyParentVisibilityChanged(value);
}
}
void KisNode::baseNodeChangedCallback()
{
if(m_d->graphListener) {
m_d->graphListener->nodeChanged(this);
}
}
void KisNode::baseNodeInvalidateAllFramesCallback()
{
if(m_d->graphListener) {
m_d->graphListener->invalidateAllFrames();
}
}
void KisNode::addKeyframeChannel(KisKeyframeChannel *channel)
{
channel->setNode(this);
KisBaseNode::addKeyframeChannel(channel);
}
KisNodeSP KisNode::firstChild() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return !m_d->nodes.isEmpty() ? m_d->nodes.first() : 0;
}
KisNodeSP KisNode::lastChild() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return !m_d->nodes.isEmpty() ? m_d->nodes.last() : 0;
}
KisNodeSP KisNode::prevChildImpl(KisNodeSP child)
{
/**
* Warning: mind locking policy!
*
* The graph locks must be *always* taken in descending
* order. That is if you want to (or it implicitly happens that
* you) take a lock of a parent and a chil, you must first take
* the lock of a parent, and only after that ask a child to do the
* same. Otherwise you'll get a deadlock.
*/
QReadLocker l(&m_d->nodeSubgraphLock);
int i = m_d->nodes.indexOf(child) - 1;
return i >= 0 ? m_d->nodes.at(i) : 0;
}
KisNodeSP KisNode::nextChildImpl(KisNodeSP child)
{
/**
* See a comment in KisNode::prevChildImpl()
*/
QReadLocker l(&m_d->nodeSubgraphLock);
int i = m_d->nodes.indexOf(child) + 1;
return i > 0 && i < m_d->nodes.size() ? m_d->nodes.at(i) : 0;
}
KisNodeSP KisNode::prevSibling() const
{
KisNodeSP parentNode = parent();
return parentNode ? parentNode->prevChildImpl(const_cast<KisNode*>(this)) : 0;
}
KisNodeSP KisNode::nextSibling() const
{
KisNodeSP parentNode = parent();
return parentNode ? parentNode->nextChildImpl(const_cast<KisNode*>(this)) : 0;
}
quint32 KisNode::childCount() const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return m_d->nodes.size();
}
KisNodeSP KisNode::at(quint32 index) const
{
QReadLocker l(&m_d->nodeSubgraphLock);
if (!m_d->nodes.isEmpty() && index < (quint32)m_d->nodes.size()) {
return m_d->nodes.at(index);
}
return 0;
}
int KisNode::index(const KisNodeSP node) const
{
QReadLocker l(&m_d->nodeSubgraphLock);
return m_d->nodes.indexOf(node);
}
QList<KisNodeSP> KisNode::childNodes(const QStringList & nodeTypes, const KoProperties & properties) const
{
QReadLocker l(&m_d->nodeSubgraphLock);
QList<KisNodeSP> nodes;
KisSafeReadNodeList::const_iterator iter;
FOREACH_SAFE(iter, m_d->nodes) {
if (*iter) {
if (properties.isEmpty() || (*iter)->check(properties)) {
bool rightType = true;
if(!nodeTypes.isEmpty()) {
rightType = false;
Q_FOREACH (const QString &nodeType, nodeTypes) {
if ((*iter)->inherits(nodeType.toLatin1())) {
rightType = true;
break;
}
}
}
if (rightType) {
nodes.append(*iter);
}
}
}
}
return nodes;
}
KisNodeSP KisNode::findChildByName(const QString &name)
{
KisNodeSP child = firstChild();
while (child) {
if (child->name() == name) {
return child;
}
if (child->childCount() > 0) {
KisNodeSP grandChild = child->findChildByName(name);
if (grandChild) {
return grandChild;
}
}
child = child->nextSibling();
}
return 0;
}
bool KisNode::add(KisNodeSP newNode, KisNodeSP aboveThis)
{
Q_ASSERT(newNode);
if (!newNode) return false;
if (aboveThis && aboveThis->parent().data() != this) return false;
if (!allowAsChild(newNode)) return false;
if (newNode->parent()) return false;
if (index(newNode) >= 0) return false;
int idx = aboveThis ? this->index(aboveThis) + 1 : 0;
// threoretical race condition may happen here ('idx' may become
// deprecated until the write lock will be held). But we ignore
// it, because it is not supported to add/remove nodes from two
// concurrent threads simultaneously
if (m_d->graphListener) {
m_d->graphListener->aboutToAddANode(this, idx);
}
{
QWriteLocker l(&m_d->nodeSubgraphLock);
newNode->createNodeProgressProxy();
m_d->nodes.insert(idx, newNode);
newNode->setParent(this);
newNode->setGraphListener(m_d->graphListener);
}
if (m_d->graphListener) {
m_d->graphListener->nodeHasBeenAdded(this, idx);
}
return true;
}
bool KisNode::remove(quint32 index)
{
if (index < childCount()) {
KisNodeSP removedNode = at(index);
if (m_d->graphListener) {
m_d->graphListener->aboutToRemoveANode(this, index);
}
{
QWriteLocker l(&m_d->nodeSubgraphLock);
removedNode->setGraphListener(0);
removedNode->setParent(0); // after calling aboutToRemoveANode or then the model get broken according to TT's modeltest
m_d->nodes.removeAt(index);
}
if (m_d->graphListener) {
m_d->graphListener->nodeHasBeenRemoved(this, index);
}
return true;
}
return false;
}
bool KisNode::remove(KisNodeSP node)
{
return node->parent().data() == this ? remove(index(node)) : false;
}
KisNodeProgressProxy* KisNode::nodeProgressProxy() const
{
if (m_d->nodeProgressProxy) {
return m_d->nodeProgressProxy;
} else if (parent()) {
return parent()->nodeProgressProxy();
}
return 0;
}
KisBusyProgressIndicator* KisNode::busyProgressIndicator() const
{
if (m_d->busyProgressIndicator) {
return m_d->busyProgressIndicator;
} else if (parent()) {
return parent()->busyProgressIndicator();
}
return 0;
}
void KisNode::createNodeProgressProxy()
{
if (!m_d->nodeProgressProxy) {
m_d->nodeProgressProxy = new KisNodeProgressProxy(this);
m_d->busyProgressIndicator = new KisBusyProgressIndicator(m_d->nodeProgressProxy);
m_d->nodeProgressProxy->moveToThread(this->thread());
m_d->busyProgressIndicator->moveToThread(this->thread());
}
}
void KisNode::setDirty()
{
setDirty(extent());
}
void KisNode::setDirty(const QVector<QRect> &rects)
{
Q_FOREACH (const QRect &rc, rects) {
setDirty(rc);
}
}
void KisNode::setDirty(const QRegion &region)
{
setDirty(region.rects());
}
+void KisNode::setDirtyDontResetAnimationCache()
+{
+ if(m_d->graphListener) {
+ m_d->graphListener->requestProjectionUpdate(this, extent(), false);
+ }
+}
+
void KisNode::setDirty(const QRect & rect)
{
if(m_d->graphListener) {
- m_d->graphListener->requestProjectionUpdate(this, rect);
+ m_d->graphListener->requestProjectionUpdate(this, rect, true);
}
}
void KisNode::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
if(m_d->graphListener) {
m_d->graphListener->invalidateFrames(range, rect);
}
}
void KisNode::requestTimeSwitch(int time)
{
if(m_d->graphListener) {
m_d->graphListener->requestTimeSwitch(time);
}
}
void KisNode::syncLodCache()
{
// noop. everything is done by getLodCapableDevices()
}
KisPaintDeviceList KisNode::getLodCapableDevices() const
{
KisPaintDeviceList list;
KisPaintDeviceSP device = paintDevice();
if (device) {
list << device;
}
KisPaintDeviceSP originalDevice = original();
if (originalDevice && originalDevice != device) {
list << originalDevice;
}
list << projectionPlane()->getLodCapableDevices();
return list;
}
diff --git a/libs/image/kis_node.h b/libs/image/kis_node.h
index 2bae6a31a2..cceda7f22d 100644
--- a/libs/image/kis_node.h
+++ b/libs/image/kis_node.h
@@ -1,396 +1,403 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_NODE_H
#define _KIS_NODE_H
#include "kis_types.h"
#include "kis_base_node.h"
#include "kritaimage_export.h"
#include <QVector>
class QRect;
class QStringList;
class KoProperties;
class KisNodeVisitor;
class KisNodeGraphListener;
class KisNodeProgressProxy;
class KisBusyProgressIndicator;
class KisAbstractProjectionPlane;
class KisProjectionLeaf;
class KisKeyframeChannel;
class KisTimeRange;
class KisUndoAdapter;
/**
* A KisNode is a KisBaseNode that knows about its direct peers, parent
* and children and whether it can have children.
*
* THREAD-SAFETY: All const methods of this class and setDirty calls
* are considered to be thread-safe(!). All the others
* especially add(), remove() and setParent() must be
* protected externally.
*
* NOTE: your subclasses must have the Q_OBJECT declaration, even if
* you do not define new signals or slots.
*/
class KRITAIMAGE_EXPORT KisNode : public KisBaseNode
{
friend class KisFilterMaskTest;
Q_OBJECT
public:
/**
* The struct describing the position of the node
* against the filthy node.
* NOTE: please change KisBaseRectsWalker::getPositionToFilthy
* when changing this struct
*/
enum PositionToFilthy {
N_ABOVE_FILTHY = 0x08,
N_FILTHY_PROJECTION = 0x20,
N_FILTHY = 0x40,
N_BELOW_FILTHY = 0x80
};
/**
* Create an empty node without a parent.
*/
KisNode();
/**
* Create a copy of this node. The copy will not have a parent
* node.
*/
KisNode(const KisNode & rhs);
/**
* Delete this node
*/
virtual ~KisNode();
virtual KisNodeSP clone() const = 0;
virtual bool accept(KisNodeVisitor &v);
virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
/**
* Re-implement this method to add constraints for the
* subclasses that can be added as children to this node
*
* @return false if the given node is not allowed as a child to this node
*/
virtual bool allowAsChild(KisNodeSP) const = 0;
/**
* Set the entire node extent dirty; this percolates up to parent
* nodes all the way to the root node. By default this is the
* empty rect (through KisBaseNode::extent())
*/
virtual void setDirty();
/**
* Add the given rect to the set of dirty rects for this node;
* this percolates up to parent nodes all the way to the root
* node.
*/
virtual void setDirty(const QRect & rect);
/**
* Add the given rects to the set of dirty rects for this node;
* this percolates up to parent nodes all the way to the root
* node.
*/
virtual void setDirty(const QVector<QRect> &rects);
/**
* Add the given region to the set of dirty rects for this node;
* this percolates up to parent nodes all the way to the root
* node, if propagate is true;
*/
virtual void setDirty(const QRegion &region);
+ /**
+ * @brief setDirtyDontResetAnimationCache does almost the same thing as usual
+ * setDirty() call, but doesn't reset the animation cache (since onlion skins are
+ * not used when rendering animation.
+ */
+ void setDirtyDontResetAnimationCache();
+
/**
* Informs that the frames in the given range are no longer valid
* and need to be recached.
* @param range frames to invalidate
*/
void invalidateFrames(const KisTimeRange &range, const QRect &rect);
/**
* Informs that the current world time should be changed.
* Might be caused by e.g. undo operation
*/
void requestTimeSwitch(int time);
/**
* \return a pointer to a KisAbstractProjectionPlane interface of
* the node. This interface is used by the image merging
* framework to get information and to blending for the
* layer.
*
* Please note the difference between need/change/accessRect and
* the projectionPlane() interface. The former one gives
* information about internal composition of the layer, and the
* latter one about the total composition, including layer styles,
* pass-through blending and etc.
*/
virtual KisAbstractProjectionPlaneSP projectionPlane() const;
/**
* Synchronizes LoD caches of the node with the current state of it.
* The current level of detail is fetched from the image pointed by
* default bounds object
*/
virtual void syncLodCache();
virtual KisPaintDeviceList getLodCapableDevices() const;
/**
* The rendering of the image may not always happen in the order
* of the main graph. Pass-through nodes ake some subgraphs
* linear, so it the order of rendering change. projectionLeaf()
* is a special interface of KisNode that represents "a graph for
* projection rendering". Therefore the nodes in projectionLeaf()
* graph may have a different order the main one.
*/
virtual KisProjectionLeafSP projectionLeaf() const;
protected:
/**
* \return internal changeRect() of the node. Do not mix with \see
* projectionPlane()
*
* Some filters will cause a change of pixels those are outside
* a requested rect. E.g. we change a rect of 2x2, then we want to
* apply a convolution filter with kernel 4x4 (changeRect is
* (2+2*3)x(2+2*3)=8x8) to that area. The rect that should be updated
* on the layer will be exaclty 8x8. More than that the needRect for
* that update will be 14x14. See \ref needeRect.
*/
virtual QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
/**
* \return internal needRect() of the node. Do not mix with \see
* projectionPlane()
*
* Some filters need pixels outside the current processing rect to
* compute the new value (for instance, convolution filters)
* See \ref changeRect
* See \ref accessRect
*/
virtual QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
/**
* \return internal accessRect() of the node. Do not mix with \see
* projectionPlane()
*
* Shows the area of image, that may be accessed during accessing
* the node.
*
* Example. You have a layer that needs to prepare some rect on a
* projection, say expectedRect. To perform this, the projection
* of all the layers below of the size needRect(expectedRect)
* should be calculeated by the merger beforehand and the layer
* will access some other area of image inside the rect
* accessRect(expectedRect) during updateProjection call.
*
* This knowledge about real access rect of a node is used by the
* scheduler to avoid collisions between two multithreaded updaters
* and so avoid flickering of the image.
*
* Currently, this method has nondefault value for shifted clone
* layers only.
*/
virtual QRect accessRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
public: // Graph methods
/**
* @return the graph sequence number calculated by the associated
* graph listener. You can use it for checking for changes in the
* graph.
*/
int graphSequenceNumber() const;
/**
* @return the graph listener this node belongs to. 0 if the node
* does not belong to a grap listener.
*/
KisNodeGraphListener * graphListener() const;
/**
* Set the graph listener for this node. The graphlistener will be
* informed before and after the list of child nodes has changed.
*/
void setGraphListener(KisNodeGraphListener * graphListener);
/**
* Returns the parent node of this node. This is 0 only for a root
* node; otherwise this will be an actual Node
*/
KisNodeSP parent() const;
/**
* Returns the first child node of this node, or 0 if there are no
* child nodes.
*/
KisNodeSP firstChild() const;
/**
* Returns the last child node of this node, or 0 if there are no
* child nodes.
*/
KisNodeSP lastChild() const;
/**
* Returns the previous sibling of this node in the parent's list.
* This is the node *above* this node in the composition stack. 0
* is returned if this child has no more previous siblings (==
* firstChild())
*/
KisNodeSP prevSibling() const;
/**
* Returns the next sibling of this node in the parent's list.
* This is the node *below* this node in the composition stack. 0
* is returned if this child has no more next siblings (==
* lastChild())
*/
KisNodeSP nextSibling() const;
/**
* Returns how many direct child nodes this node has (not
* recursive).
*/
quint32 childCount() const;
/**
* Retrieve the child node at the specified index.
*
* @return 0 if there is no node at this index.
*/
KisNodeSP at(quint32 index) const;
/**
* Retrieve the index of the specified child node.
*
* @return -1 if the specified node is not a child node of this
* node.
*/
int index(const KisNodeSP node) const;
/**
* Return a list of child nodes of the current node that conform
* to the specified constraints. There are no guarantees about the
* order of the nodes in the list. The function is not recursive.
*
* @param nodeTypes. if not empty, only nodes that inherit the
* classnames in this stringlist will be returned.
* @param properties. if not empty, only nodes for which
* KisNodeBase::check(properties) returns true will be returned.
*/
QList<KisNodeSP> childNodes(const QStringList & nodeTypes, const KoProperties & properties) const;
/**
* @brief findChildByName finds the first child that has the given name
* @param name the name to look for
* @return the first child with the given name
*/
KisNodeSP findChildByName(const QString &name);
public:
/**
* @return the node progress proxy used by this node, if this node has no progress
* proxy, it will return the proxy of its parent, if the parent has no progress proxy
* it will return 0
*/
KisNodeProgressProxy* nodeProgressProxy() const;
KisBusyProgressIndicator* busyProgressIndicator() const;
private:
/**
* Create a node progress proxy for this node. You need to create a progress proxy only
* if the node is going to appear in the layerbox, and it needs to be created before
* the layer box is made aware of the proxy.
*/
void createNodeProgressProxy();
protected:
KisBaseNodeSP parentCallback() const;
void notifyParentVisibilityChanged(bool value);
void baseNodeChangedCallback();
void baseNodeInvalidateAllFramesCallback();
protected:
virtual void addKeyframeChannel(KisKeyframeChannel* channel);
private:
friend class KisNodeFacade;
friend class KisNodeTest;
friend class KisLayer; // Note: only for setting the preview mask!
/**
* Set the parent of this node.
*/
void setParent(KisNodeWSP parent);
/**
* Add the specified node above the specified node. If aboveThis
* is 0, the node is added at the bottom.
*/
bool add(KisNodeSP newNode, KisNodeSP aboveThis);
/**
* Removes the node at the specified index from the child nodes.
*
* @return false if there is no node at this index
*/
bool remove(quint32 index);
/**
* Removes the node from the child nodes.
*
* @return false if there's no such node in this node.
*/
bool remove(KisNodeSP node);
KisNodeSP prevChildImpl(KisNodeSP child);
KisNodeSP nextChildImpl(KisNodeSP child);
private:
struct Private;
Private * const m_d;
};
Q_DECLARE_METATYPE(KisNodeSP)
Q_DECLARE_METATYPE(KisNodeWSP)
#endif
diff --git a/libs/image/kis_node_graph_listener.cpp b/libs/image/kis_node_graph_listener.cpp
index 1fd38c778a..0d04e22df3 100644
--- a/libs/image/kis_node_graph_listener.cpp
+++ b/libs/image/kis_node_graph_listener.cpp
@@ -1,102 +1,102 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_graph_listener.h"
#include "kis_time_range.h"
#include <QRect>
#include <QtGlobal>
struct Q_DECL_HIDDEN KisNodeGraphListener::Private
{
Private() : sequenceNumber(0) {}
int sequenceNumber;
};
KisNodeGraphListener::KisNodeGraphListener()
: m_d(new Private())
{
}
KisNodeGraphListener::~KisNodeGraphListener()
{
}
void KisNodeGraphListener::aboutToAddANode(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::nodeHasBeenAdded(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::aboutToRemoveANode(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::nodeHasBeenRemoved(KisNode */*parent*/, int /*index*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::aboutToMoveNode(KisNode * /*node*/, int /*oldIndex*/, int /*newIndex*/)
{
m_d->sequenceNumber++;
}
void KisNodeGraphListener::nodeHasBeenMoved(KisNode * /*node*/, int /*oldIndex*/, int /*newIndex*/)
{
m_d->sequenceNumber++;
}
int KisNodeGraphListener::graphSequenceNumber() const
{
return m_d->sequenceNumber;
}
void KisNodeGraphListener::nodeChanged(KisNode * /*node*/)
{
}
void KisNodeGraphListener::invalidateAllFrames()
{
}
void KisNodeGraphListener::notifySelectionChanged()
{
}
-void KisNodeGraphListener::requestProjectionUpdate(KisNode * /*node*/, const QRect& /*rect*/)
+void KisNodeGraphListener::requestProjectionUpdate(KisNode * /*node*/, const QRect& /*rect*/, bool /*resetAnimationCache*/)
{
}
void KisNodeGraphListener::invalidateFrames(const KisTimeRange &range, const QRect &rect)
{
Q_UNUSED(range);
Q_UNUSED(rect);
}
void KisNodeGraphListener::requestTimeSwitch(int time)
{
Q_UNUSED(time);
}
diff --git a/libs/image/kis_node_graph_listener.h b/libs/image/kis_node_graph_listener.h
index d8b1ae410d..692359afcb 100644
--- a/libs/image/kis_node_graph_listener.h
+++ b/libs/image/kis_node_graph_listener.h
@@ -1,123 +1,123 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_NODE_GRAPH_LISTENER_H_
#define KIS_NODE_GRAPH_LISTENER_H_
#include "kritaimage_export.h"
#include <QScopedPointer>
class KisTimeRange;
class KisNode;
class QRect;
/**
* Implementations of this class are called by nodes whenever the node
* graph changes. These implementations can then emit the right
* signals so Qt interview models can be updated before and after
* changes.
*
* The reason for this go-between is that we don't want our nodes to
* be QObjects, nor to have sig-slot connections between every node
* and every mode.
*
* It also manages the sequence number of the graph. This is a number
* which can be used as a checksum for whether the graph has chenged
* from some period of time or not. \see graphSequenceNumber()
*/
class KRITAIMAGE_EXPORT KisNodeGraphListener
{
public:
KisNodeGraphListener();
virtual ~KisNodeGraphListener();
/**
* Inform the model that we're going to add a node.
*/
virtual void aboutToAddANode(KisNode *parent, int index);
/**
* Inform the model we're done adding a node.
*/
virtual void nodeHasBeenAdded(KisNode *parent, int index);
/**
* Inform the model we're going to remove a node.
*/
virtual void aboutToRemoveANode(KisNode *parent, int index);
/**
* Inform the model we're done removing a node.
*/
virtual void nodeHasBeenRemoved(KisNode *parent, int index);
/**
* Inform the model we're about to start moving a node (which
* includes removing and adding the same node)
*/
virtual void aboutToMoveNode(KisNode * node, int oldIndex, int newIndex);
/**
* Inform the model we're done moving the node: it has been
* removed and added successfully
*/
virtual void nodeHasBeenMoved(KisNode * node, int oldIndex, int newIndex);
virtual void nodeChanged(KisNode * node);
virtual void invalidateAllFrames();
/**
* Inform the model that one of the selections in the graph is
* changed. The sender is not passed to the function (at least for
* now) because the UI should decide itself whether it needs to
* fetch new selection of not.
*/
virtual void notifySelectionChanged();
/**
* Inform the model that a node has been changed (setDirty)
*/
- virtual void requestProjectionUpdate(KisNode * node, const QRect& rect);
+ virtual void requestProjectionUpdate(KisNode * node, const QRect& rect, bool resetAnimationCache);
virtual void invalidateFrames(const KisTimeRange &range, const QRect &rect);
virtual void requestTimeSwitch(int time);
/**
* Returns the sequence of the graph.
*
* Every time some operation performed, which might change the
* hierarchy of the nodes, the sequence number grows by one. So
* if you have any information about the graph which was acquired
* when the sequence number was X and now it has become Y, it
* means your information is outdated.
*
* It is used in the scheduler for checking whether queued walkers
* should be regenerated.
*/
int graphSequenceNumber() const;
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc
index b119cbb2cc..daf269dc39 100644
--- a/libs/image/kis_paint_device.cc
+++ b/libs/image/kis_paint_device.cc
@@ -1,2113 +1,2117 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_paint_device.h"
#include <QRect>
#include <QTransform>
#include <QImage>
#include <QList>
#include <QHash>
#include <QIODevice>
#include <qmath.h>
#include <klocalizedstring.h>
#include <KoChannelInfo.h>
#include <KoColorProfile.h>
#include <KoColor.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoIntegerMaths.h>
#include <KoMixColorsOp.h>
#include <KoUpdater.h>
#include "kis_image.h"
#include "kis_random_sub_accessor.h"
#include "kis_selection.h"
#include "kis_node.h"
#include "kis_datamanager.h"
#include "kis_paint_device_writer.h"
#include "kis_selection_component.h"
#include "kis_pixel_selection.h"
#include "kis_repeat_iterators_pixel.h"
#include "kis_fixed_paint_device.h"
#include "tiles3/kis_hline_iterator.h"
#include "tiles3/kis_vline_iterator.h"
#include "tiles3/kis_random_accessor.h"
#include "kis_default_bounds.h"
#include "kis_lod_transform.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_paint_device_cache.h"
#include "kis_paint_device_data.h"
#include "kis_paint_device_frames_interface.h"
#include "kis_transform_worker.h"
#include "kis_filter_strategy.h"
#include "krita_utils.h"
struct KisPaintDeviceSPStaticRegistrar {
KisPaintDeviceSPStaticRegistrar() {
qRegisterMetaType<KisPaintDeviceSP>("KisPaintDeviceSP");
}
};
static KisPaintDeviceSPStaticRegistrar __registrar;
struct KisPaintDevice::Private
{
/**
* Used when the paint device is loading to ensure no lod/animation
* interferes the process.
*/
static const KisDefaultBoundsSP transitionalDefaultBounds;
public:
class KisPaintDeviceStrategy;
class KisPaintDeviceWrappedStrategy;
Private(KisPaintDevice *paintDevice);
~Private();
KisPaintDevice *q;
KisNodeWSP parent;
QScopedPointer<KisRasterKeyframeChannel> contentChannel;
KisDefaultBoundsBaseSP defaultBounds;
QScopedPointer<KisPaintDeviceStrategy> basicStrategy;
QScopedPointer<KisPaintDeviceWrappedStrategy> wrappedStrategy;
QScopedPointer<KisPaintDeviceFramesInterface> framesInterface;
bool isProjectionDevice;
KisPaintDeviceStrategy* currentStrategy();
void init(const KoColorSpace *cs, const quint8 *defaultPixel);
KUndo2Command* convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags);
bool assignProfile(const KoColorProfile * profile);
inline const KoColorSpace* colorSpace() const
{
return currentData()->colorSpace();
}
inline KisDataManagerSP dataManager() const
{
return currentData()->dataManager();
}
inline qint32 x() const
{
return currentData()->x();
}
inline qint32 y() const
{
return currentData()->y();
}
inline void setX(qint32 x)
{
currentData()->setX(x);
}
inline void setY(qint32 y)
{
currentData()->setY(y);
}
inline KisPaintDeviceCache* cache()
{
return currentData()->cache();
}
+ inline KisIteratorCompleteListener* cacheInvalidator() {
+ return currentData()->cacheInvalidator();
+ }
+
void cloneAllDataObjects(Private *rhs, bool copyFrames)
{
m_lodData.reset();
m_externalFrameData.reset();
if (!m_frames.isEmpty()) {
m_frames.clear();
}
if (!copyFrames) {
if (m_data) {
m_data->prepareClone(rhs->currentNonLodData(), true);
} else {
m_data = toQShared(new KisPaintDeviceData(rhs->currentNonLodData(), true));
}
} else {
if (m_data && !rhs->m_data) {
m_data.clear();
} else if (!m_data && rhs->m_data) {
m_data = toQShared(new KisPaintDeviceData(rhs->m_data.data(), true));
} else if (m_data && rhs->m_data) {
m_data->prepareClone(rhs->m_data.data(), true);
}
if (!rhs->m_frames.isEmpty()) {
FramesHash::const_iterator it = rhs->m_frames.constBegin();
FramesHash::const_iterator end = rhs->m_frames.constEnd();
for (; it != end; ++it) {
DataSP data = toQShared(new KisPaintDeviceData(it.value().data(), true));
m_frames.insert(it.key(), data);
}
}
m_nextFreeFrameId = rhs->m_nextFreeFrameId;
}
if (rhs->m_lodData) {
m_lodData.reset(new KisPaintDeviceData(rhs->m_lodData.data(), true));
}
}
void prepareClone(KisPaintDeviceSP src)
{
prepareCloneImpl(src, src->m_d->currentData());
Q_ASSERT(fastBitBltPossible(src));
}
bool fastBitBltPossible(KisPaintDeviceSP src)
{
return fastBitBltPossibleImpl(src->m_d->currentData());
}
int currentFrameId() const
{
KIS_ASSERT_RECOVER(contentChannel) {
return -1;
}
return !defaultBounds->currentLevelOfDetail() ?
contentChannel->frameIdAt(defaultBounds->currentTime()) :
-1;
}
KisDataManagerSP frameDataManager(int frameId) const
{
DataSP data = m_frames[frameId];
return data->dataManager();
}
void invalidateFrameCache(int frameId)
{
DataSP data = m_frames[frameId];
return data->cache()->invalidate();
}
private:
typedef KisPaintDeviceData Data;
typedef QSharedPointer<Data> DataSP;
typedef QHash<int, DataSP> FramesHash;
class FrameInsertionCommand : public KUndo2Command
{
public:
FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand)
: KUndo2Command(parentCommand),
m_hash(hash),
m_data(data),
m_frameId(frameId),
m_insert(insert)
{
}
void redo() override
{
doSwap(m_insert);
}
void undo() override
{
doSwap(!m_insert);
}
private:
void doSwap(bool insert)
{
if (insert) {
m_hash->insert(m_frameId, m_data);
} else {
DataSP deletedData = m_hash->take(m_frameId);
}
}
private:
FramesHash *m_hash;
DataSP m_data;
int m_frameId;
bool m_insert;
};
public:
int getNextFrameId() {
int frameId = 0;
while (m_frames.contains(frameId = m_nextFreeFrameId++));
KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId));
return frameId;
}
int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand)
{
KIS_ASSERT_RECOVER(parentCommand) {
return -1;
}
DataSP data;
bool initialFrame = false;
if (m_frames.isEmpty()) {
/**
* Here we move the contents of the paint device to the
* new frame and clear m_data to make the "background" for
* the areas where there is no frame at all.
*/
data = toQShared(new Data(m_data.data(), true));
m_data->dataManager()->clear();
m_data->cache()->invalidate();
initialFrame = true;
} else if (copy) {
DataSP srcData = m_frames[copySrc];
data = toQShared(new Data(srcData.data(), true));
} else {
DataSP srcData = m_frames.begin().value();
data = toQShared(new Data(srcData.data(), false));
}
if (!initialFrame && !copy) {
data->setX(offset.x());
data->setY(offset.y());
}
int frameId = getNextFrameId();
KUndo2Command *cmd =
new FrameInsertionCommand(&m_frames,
data,
frameId, true,
parentCommand);
cmd->redo();
return frameId;
}
void deleteFrame(int frame, KUndo2Command *parentCommand)
{
KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame));
KIS_ASSERT_RECOVER_RETURN(parentCommand);
DataSP deletedData = m_frames[frame];
KUndo2Command *cmd =
new FrameInsertionCommand(&m_frames,
deletedData,
frame, false,
parentCommand);
cmd->redo();
}
QRect frameBounds(int frameId)
{
DataSP data = m_frames[frameId];
QRect extent = data->dataManager()->extent();
extent.translate(data->x(), data->y());
return extent;
}
QPoint frameOffset(int frameId) const
{
DataSP data = m_frames[frameId];
return QPoint(data->x(), data->y());
}
void setFrameOffset(int frameId, const QPoint &offset)
{
DataSP data = m_frames[frameId];
data->setX(offset.x());
data->setY(offset.y());
}
const QList<int> frameIds() const
{
return m_frames.keys();
}
bool readFrame(QIODevice *stream, int frameId)
{
bool retval = false;
DataSP data = m_frames[frameId];
retval = data->dataManager()->read(stream);
data->cache()->invalidate();
return retval;
}
bool writeFrame(KisPaintDeviceWriter &store, int frameId)
{
DataSP data = m_frames[frameId];
return data->dataManager()->write(store);
}
void setFrameDefaultPixel(const KoColor &defPixel, int frameId)
{
DataSP data = m_frames[frameId];
KoColor color(defPixel);
color.convertTo(data->colorSpace());
data->dataManager()->setDefaultPixel(color.data());
}
KoColor frameDefaultPixel(int frameId) const
{
DataSP data = m_frames[frameId];
return KoColor(data->dataManager()->defaultPixel(),
data->colorSpace());
}
void fetchFrame(int frameId, KisPaintDeviceSP targetDevice);
void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice);
void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice);
void uploadFrameData(DataSP srcData, DataSP dstData);
struct LodDataStructImpl;
LodDataStruct* createLodDataStruct(int lod);
void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect);
void uploadLodDataStruct(LodDataStruct *dst);
QRegion regionForLodSyncing() const;
void tesingFetchLodDevice(KisPaintDeviceSP targetDevice);
private:
QRegion syncWholeDevice(Data *srcData);
inline DataSP currentFrameData() const
{
DataSP data;
const int numberOfFrames = contentChannel->keyframeCount();
if (numberOfFrames > 1) {
int frameId = contentChannel->frameIdAt(defaultBounds->currentTime());
if (frameId == -1) {
data = m_data;
} else {
KIS_ASSERT_RECOVER(m_frames.contains(frameId)) {
return m_frames.begin().value();
}
data = m_frames[frameId];
}
} else if (numberOfFrames == 1) {
data = m_frames.begin().value();
} else {
data = m_data;
}
return data;
}
inline Data* currentNonLodData() const
{
Data *data = m_data.data();
if (contentChannel) {
data = currentFrameData().data();
} else if (isProjectionDevice && defaultBounds->externalFrameActive()) {
if (!m_externalFrameData) {
QMutexLocker l(&m_dataSwitchLock);
if (!m_externalFrameData) {
m_externalFrameData.reset(new Data(m_data.data(), false));
}
}
data = m_externalFrameData.data();
}
return data;
}
inline void ensureLodDataPresent() const
{
if (!m_lodData) {
Data *srcData = currentNonLodData();
QMutexLocker l(&m_dataSwitchLock);
if (!m_lodData) {
m_lodData.reset(new Data(srcData, false));
}
}
}
inline Data* currentData() const
{
Data *data;
if (defaultBounds->currentLevelOfDetail()) {
ensureLodDataPresent();
data = m_lodData.data();
} else {
data = currentNonLodData();
}
return data;
}
void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData)
{
currentData()->prepareClone(srcData);
q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace()));
q->setDefaultBounds(src->defaultBounds());
}
bool fastBitBltPossibleImpl(Data *srcData)
{
return x() == srcData->x() && y() == srcData->y() &&
*colorSpace() == *srcData->colorSpace();
}
QList<Data*> allDataObjects() const
{
QList<Data*> dataObjects;
if (m_frames.isEmpty()) {
dataObjects << m_data.data();
}
dataObjects << m_lodData.data();
dataObjects << m_externalFrameData.data();
Q_FOREACH (DataSP value, m_frames.values()) {
dataObjects << value.data();
}
return dataObjects;
}
void transferFromData(Data *data, KisPaintDeviceSP targetDevice);
struct Q_DECL_HIDDEN StrategyPolicy;
typedef KisSequentialIteratorBase<ReadOnlyIteratorPolicy<StrategyPolicy>, StrategyPolicy> InternalSequentialConstIterator;
typedef KisSequentialIteratorBase<WritableIteratorPolicy<StrategyPolicy>, StrategyPolicy> InternalSequentialIterator;
private:
friend class KisPaintDeviceFramesInterface;
private:
DataSP m_data;
mutable QScopedPointer<Data> m_lodData;
mutable QScopedPointer<Data> m_externalFrameData;
mutable QMutex m_dataSwitchLock;
FramesHash m_frames;
int m_nextFreeFrameId;
};
const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds();
#include "kis_paint_device_strategies.h"
KisPaintDevice::Private::Private(KisPaintDevice *paintDevice)
: q(paintDevice),
basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)),
isProjectionDevice(false),
m_data(new Data(paintDevice)),
m_nextFreeFrameId(0)
{
}
KisPaintDevice::Private::~Private()
{
m_frames.clear();
}
KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy()
{
if (!defaultBounds->wrapAroundMode()) {
return basicStrategy.data();
}
QRect wrapRect = defaultBounds->bounds();
if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) {
wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this));
}
return wrappedStrategy.data();
}
struct KisPaintDevice::Private::StrategyPolicy {
StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy,
KisDataManager *dataManager, qint32 offsetX, qint32 offsetY)
: m_strategy(strategy),
m_dataManager(dataManager),
m_offsetX(offsetX),
m_offsetY(offsetY)
{
}
KisHLineConstIteratorSP createConstIterator(const QRect &rect)
{
return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY);
}
KisHLineIteratorSP createIterator(const QRect &rect)
{
return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY);
}
int pixelSize() const
{
return m_dataManager->pixelSize();
}
KisPaintDeviceStrategy *m_strategy;
KisDataManager *m_dataManager;
int m_offsetX;
int m_offsetY;
};
struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct {
LodDataStructImpl(Data *_lodData) : lodData(_lodData) {}
QScopedPointer<Data> lodData;
};
QRegion KisPaintDevice::Private::regionForLodSyncing() const
{
Data *srcData = currentNonLodData();
return srcData->dataManager()->region().translated(srcData->x(), srcData->y());
}
KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod)
{
Data *srcData = currentNonLodData();
Data *lodData = new Data(srcData, false);
LodDataStruct *lodStruct = new LodDataStructImpl(lodData);
int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod);
int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod);
/**
* We compare color spaces as pure pointers, because they must be
* exactly the same, since they come from the common source.
*/
if (lodData->levelOfDetail() != newLod ||
lodData->colorSpace() != srcData->colorSpace() ||
lodData->x() != expectedX ||
lodData->y() != expectedY) {
lodData->prepareClone(srcData);
lodData->setLevelOfDetail(newLod);
lodData->setX(expectedX);
lodData->setY(expectedY);
// FIXME: different kind of synchronization
}
//QRegion dirtyRegion = syncWholeDevice(srcData);
lodData->cache()->invalidate();
return lodStruct;
}
void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect)
{
LodDataStructImpl *dst = dynamic_cast<LodDataStructImpl*>(_dst);
KIS_SAFE_ASSERT_RECOVER_RETURN(dst);
Data *lodData = dst->lodData.data();
Data *srcData = currentNonLodData();
const int lod = lodData->levelOfDetail();
const int srcStepSize = 1 << lod;
KIS_ASSERT_RECOVER_RETURN(lod > 0);
const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod);
const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod);
if (!srcRect.isValid() || !dstRect.isValid()) return;
KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width());
const int pixelSize = srcData->dataManager()->pixelSize();
int rowsAccumulated = 0;
int columnsAccumulated = 0;
KoMixColorsOp *mixOp = colorSpace()->mixColorsOp();
QScopedArrayPointer<quint8> blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]);
quint8 *blendDataPtr = blendData.data();
int blendDataOffset = 0;
const int srcCellSize = srcStepSize * srcStepSize;
const int srcCellStride = srcCellSize * pixelSize;
const int srcStepStride = srcStepSize * pixelSize;
const int srcColumnStride = (srcStepSize - 1) * srcStepStride;
QScopedArrayPointer<qint16> weights(new qint16[srcCellSize]);
{
const qint16 averageWeight = qCeil(255.0 / srcCellSize);
const qint16 extraWeight = averageWeight * srcCellSize - 255;
KIS_ASSERT_RECOVER_NOOP(extraWeight == 1);
for (int i = 0; i < srcCellSize - 1; i++) {
weights[i] = averageWeight;
}
weights[srcCellSize - 1] = averageWeight - extraWeight;
}
InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcData->dataManager().data(), srcData->x(), srcData->y()), srcRect);
InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), lodData->dataManager().data(), lodData->x(), lodData->y()), dstRect);
int rowsRemaining = srcRect.height();
while (rowsRemaining > 0) {
int colsRemaining = srcRect.width();
while (colsRemaining > 0) {
memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize);
blendDataPtr += pixelSize;
columnsAccumulated++;
if (columnsAccumulated >= srcStepSize) {
blendDataPtr += srcColumnStride;
columnsAccumulated = 0;
}
srcIntIt.nextPixel();
colsRemaining--;
}
rowsAccumulated++;
if (rowsAccumulated >= srcStepSize) {
// blend and write the final data
blendDataPtr = blendData.data();
for (int i = 0; i < dstRect.width(); i++) {
mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData());
blendDataPtr += srcCellStride;
dstIntIt.nextPixel();
}
// reset counters
rowsAccumulated = 0;
blendDataPtr = blendData.data();
blendDataOffset = 0;
} else {
blendDataOffset += srcStepStride;
blendDataPtr = blendData.data() + blendDataOffset;
}
rowsRemaining--;
}
}
void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst)
{
LodDataStructImpl *dst = dynamic_cast<LodDataStructImpl*>(_dst);
KIS_SAFE_ASSERT_RECOVER_RETURN(dst);
KIS_SAFE_ASSERT_RECOVER_RETURN(
dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail());
ensureLodDataPresent();
m_lodData->prepareClone(dst->lodData.data());
m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent());
}
void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice)
{
QRect extent = data->dataManager()->extent();
extent.translate(data->x(), data->y());
targetDevice->m_d->prepareCloneImpl(q, data);
targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent);
}
void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice)
{
DataSP data = m_frames[frameId];
transferFromData(data.data(), targetDevice);
}
void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice)
{
DataSP dstData = m_frames[dstFrameId];
KIS_ASSERT_RECOVER_RETURN(dstData);
DataSP srcData = srcDevice->m_d->m_frames[srcFrameId];
KIS_ASSERT_RECOVER_RETURN(srcData);
uploadFrameData(srcData, dstData);
}
void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice)
{
DataSP dstData = m_frames[dstFrameId];
KIS_ASSERT_RECOVER_RETURN(dstData);
DataSP srcData = srcDevice->m_d->m_data;
KIS_ASSERT_RECOVER_RETURN(srcData);
uploadFrameData(srcData, dstData);
}
void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData)
{
if (srcData->colorSpace() != dstData->colorSpace() &&
*srcData->colorSpace() != *dstData->colorSpace()) {
KUndo2Command tempCommand;
srcData = toQShared(new Data(srcData.data(), true));
srcData->convertDataColorSpace(dstData->colorSpace(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags(),
&tempCommand);
}
dstData->dataManager()->clear();
dstData->cache()->invalidate();
const QRect rect = srcData->dataManager()->extent();
dstData->dataManager()->bitBltRough(srcData->dataManager(), rect);
}
void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice)
{
Data *data = m_lodData.data();
Q_ASSERT(data);
transferFromData(data, targetDevice);
}
KUndo2Command* KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
class DeviceChangeColorSpaceCommand : public KUndo2Command
{
public:
DeviceChangeColorSpaceCommand(KisPaintDeviceSP device)
: m_firstRun(true),
m_device(device)
{
}
void emitNotifications()
{
m_device->emitColorSpaceChanged();
m_device->setDirty();
}
void redo() override
{
KUndo2Command::redo();
if (!m_firstRun) {
m_firstRun = false;
return;
}
emitNotifications();
}
void undo() override
{
KUndo2Command::undo();
emitNotifications();
}
private:
bool m_firstRun;
KisPaintDeviceSP m_device;
};
KUndo2Command *parentCommand = new DeviceChangeColorSpaceCommand(q);
QList<Data*> dataObjects = allDataObjects();;
Q_FOREACH (Data *data, dataObjects) {
if (!data) continue;
data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand);
}
if (!parentCommand->childCount()) {
delete parentCommand;
parentCommand = 0;
} else {
q->emitColorSpaceChanged();
}
return parentCommand;
}
bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile)
{
if (!profile) return false;
const KoColorSpace *dstColorSpace =
KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
if (!dstColorSpace) return false;
QList<Data*> dataObjects = allDataObjects();;
Q_FOREACH (Data *data, dataObjects) {
if (!data) continue;
data->assignColorSpace(dstColorSpace);
}
q->emitProfileChanged();
// no undo information is provided here
return true;
}
void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel)
{
QList<Data*> dataObjects = allDataObjects();;
Q_FOREACH (Data *data, dataObjects) {
if (!data) continue;
KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel);
data->init(cs, dataManager);
}
}
KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name)
: QObject(0)
, m_d(new Private(this))
{
init(colorSpace, new KisDefaultBounds(), 0, name);
}
KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name)
: QObject(0)
, m_d(new Private(this))
{
init(colorSpace, defaultBounds, parent, name);
}
void KisPaintDevice::init(const KoColorSpace *colorSpace,
KisDefaultBoundsBaseSP defaultBounds,
KisNodeWSP parent, const QString& name)
{
Q_ASSERT(colorSpace);
setObjectName(name);
// temporary def. bounds object for the initialization phase only
m_d->defaultBounds = m_d->transitionalDefaultBounds;
if (!defaultBounds) {
// Reuse transitionalDefaultBounds here. Change if you change
// semantics of transitionalDefaultBounds
defaultBounds = m_d->transitionalDefaultBounds;
}
QScopedArrayPointer<quint8> defaultPixel(new quint8[colorSpace->pixelSize()]);
colorSpace->fromQColor(Qt::transparent, defaultPixel.data());
m_d->init(colorSpace, defaultPixel.data());
Q_ASSERT(m_d->colorSpace());
setDefaultBounds(defaultBounds);
setParentNode(parent);
}
KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, bool copyFrames, KisNode *newParentNode)
: QObject()
, KisShared()
, m_d(new Private(this))
{
if (this != &rhs) {
// temporary def. bounds object for the initialization phase only
m_d->defaultBounds = m_d->transitionalDefaultBounds;
// copy data objects with or without frames
m_d->cloneAllDataObjects(rhs.m_d, copyFrames);
if (copyFrames) {
KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface);
KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel);
m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this));
m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this));
}
setDefaultBounds(rhs.m_d->defaultBounds);
setParentNode(0);
}
}
KisPaintDevice::~KisPaintDevice()
{
delete m_d;
}
void KisPaintDevice::setProjectionDevice(bool value)
{
m_d->isProjectionDevice = value;
}
void KisPaintDevice::prepareClone(KisPaintDeviceSP src)
{
m_d->prepareClone(src);
Q_ASSERT(fastBitBltPossible(src));
}
void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect)
{
prepareClone(src);
// we guarantee that *this is totally empty, so copy pixels that
// are areally present on the source image only
const QRect optimizedRect = rect & src->extent();
fastBitBlt(src, optimizedRect);
}
void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect)
{
prepareClone(src);
// we guarantee that *this is totally empty, so copy pixels that
// are areally present on the source image only
const QRect optimizedRect = minimalRect & src->extent();
fastBitBltRough(src, optimizedRect);
}
void KisPaintDevice::setDirty()
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty();
}
void KisPaintDevice::setDirty(const QRect & rc)
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty(rc);
}
void KisPaintDevice::setDirty(const QRegion & region)
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty(region);
}
void KisPaintDevice::setDirty(const QVector<QRect> rects)
{
m_d->cache()->invalidate();
if (m_d->parent.isValid())
m_d->parent->setDirty(rects);
}
void KisPaintDevice::requestTimeSwitch(int time)
{
if (m_d->parent.isValid()) {
m_d->parent->requestTimeSwitch(time);
}
}
int KisPaintDevice::sequenceNumber() const
{
return m_d->cache()->sequenceNumber();
}
void KisPaintDevice::setParentNode(KisNodeWSP parent)
{
m_d->parent = parent;
}
// for testing purposes only
KisNodeWSP KisPaintDevice::parentNode() const
{
return m_d->parent;
}
void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds)
{
m_d->defaultBounds = defaultBounds;
m_d->cache()->invalidate();
}
KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const
{
return m_d->defaultBounds;
}
void KisPaintDevice::moveTo(const QPoint &pt)
{
m_d->currentStrategy()->move(pt);
m_d->cache()->invalidate();
}
QPoint KisPaintDevice::offset() const
{
return QPoint(x(), y());
}
void KisPaintDevice::moveTo(qint32 x, qint32 y)
{
moveTo(QPoint(x, y));
}
void KisPaintDevice::setX(qint32 x)
{
moveTo(QPoint(x, m_d->y()));
}
void KisPaintDevice::setY(qint32 y)
{
moveTo(QPoint(m_d->x(), y));
}
qint32 KisPaintDevice::x() const
{
return m_d->x();
}
qint32 KisPaintDevice::y() const
{
return m_d->y();
}
void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const
{
QRect rc = extent();
x = rc.x();
y = rc.y();
w = rc.width();
h = rc.height();
}
QRect KisPaintDevice::extent() const
{
return m_d->currentStrategy()->extent();
}
QRegion KisPaintDevice::region() const
{
return m_d->currentStrategy()->region();
}
QRect KisPaintDevice::nonDefaultPixelArea() const
{
return m_d->cache()->nonDefaultPixelArea();
}
QRect KisPaintDevice::exactBounds() const
{
return m_d->cache()->exactBounds();
}
QRect KisPaintDevice::exactBoundsAmortized() const
{
return m_d->cache()->exactBoundsAmortized();
}
namespace Impl
{
struct CheckFullyTransparent {
CheckFullyTransparent(const KoColorSpace *colorSpace)
: m_colorSpace(colorSpace)
{
}
bool isPixelEmpty(const quint8 *pixelData)
{
return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8;
}
private:
const KoColorSpace *m_colorSpace;
};
struct CheckNonDefault {
CheckNonDefault(int pixelSize, const quint8 *defaultPixel)
: m_pixelSize(pixelSize),
m_defaultPixel(defaultPixel)
{
}
bool isPixelEmpty(const quint8 *pixelData)
{
return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0;
}
private:
int m_pixelSize;
const quint8 *m_defaultPixel;
};
template <class ComparePixelOp>
QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp)
{
if (startRect == endRect) return startRect;
// Solution n°2
int x, y, w, h;
int boundLeft, boundTop, boundRight, boundBottom;
int endDirN, endDirE, endDirS, endDirW;
startRect.getRect(&x, &y, &w, &h);
if (endRect.isEmpty()) {
endDirS = startRect.bottom();
endDirN = startRect.top();
endDirE = startRect.right();
endDirW = startRect.left();
startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom);
} else {
endDirS = endRect.top() - 1;
endDirN = endRect.bottom() + 1;
endDirE = endRect.left() - 1;
endDirW = endRect.right() + 1;
endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom);
}
// XXX: a small optimization is possible by using H/V line iterators in the first
// and third cases, at the cost of making the code a bit more complex
KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(x, y);
bool found = false;
{
for (qint32 y2 = y; y2 <= endDirS; ++y2) {
for (qint32 x2 = x; x2 < x + w || found; ++ x2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundTop = y2;
found = true;
break;
}
}
if (found) break;
}
}
/**
* If the first pass hasn't found any opaque pixel, there is no
* reason to check that 3 more times. They will not appear in the
* meantime. Just return an empty bounding rect.
*/
if (!found && endRect.isEmpty()) {
return QRect();
}
found = false;
for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) {
for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundBottom = y2;
found = true;
break;
}
}
if (found) break;
}
found = false;
{
for (qint32 x2 = x; x2 <= endDirE ; ++x2) {
for (qint32 y2 = y; y2 < y + h || found; ++y2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundLeft = x2;
found = true;
break;
}
}
if (found) break;
}
}
found = false;
// Look for right edge )
{
for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) {
for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) {
accessor->moveTo(x2, y2);
if (!compareOp.isPixelEmpty(accessor->rawDataConst())) {
boundRight = x2;
found = true;
break;
}
}
if (found) break;
}
}
return QRect(boundLeft, boundTop,
boundRight - boundLeft + 1,
boundBottom - boundTop + 1);
}
}
QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const
{
QRect startRect = extent();
QRect endRect;
quint8 defaultOpacity = defaultPixel().opacityU8();
if (defaultOpacity != OPACITY_TRANSPARENT_U8) {
if (!nonDefaultOnly) {
/**
* We will calculate exact bounds only outside of the
* image bounds, and that'll be nondefault area only.
*/
endRect = defaultBounds()->bounds();
nonDefaultOnly = true;
} else {
startRect = region().boundingRect();
}
}
if (nonDefaultOnly) {
const KoColor defaultPixel = this->defaultPixel();
Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data());
endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp);
} else {
Impl::CheckFullyTransparent compareOp(m_d->colorSpace());
endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp);
}
return endRect;
}
QRegion KisPaintDevice::regionExact() const
{
QRegion resultRegion;
QVector<QRect> rects = region().rects();
const KoColor defaultPixel = this->defaultPixel();
Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data());
Q_FOREACH (const QRect &rc1, rects) {
const int patchSize = 64;
QVector<QRect> smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize));
Q_FOREACH (const QRect &rc2, smallerRects) {
const QRect result =
Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp);
if (!result.isEmpty()) {
resultRegion += result;
}
}
}
return resultRegion;
}
void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h)
{
crop(QRect(x, y, w, h));
}
void KisPaintDevice::crop(const QRect &rect)
{
m_d->currentStrategy()->crop(rect);
}
void KisPaintDevice::purgeDefaultPixels()
{
KisDataManagerSP dm = m_d->dataManager();
dm->purge(dm->extent());
}
void KisPaintDevice::setDefaultPixel(const KoColor &defPixel)
{
KoColor color(defPixel);
color.convertTo(colorSpace());
m_d->dataManager()->setDefaultPixel(color.data());
m_d->cache()->invalidate();
}
KoColor KisPaintDevice::defaultPixel() const
{
return KoColor(m_d->dataManager()->defaultPixel(), colorSpace());
}
void KisPaintDevice::clear()
{
m_d->dataManager()->clear();
m_d->cache()->invalidate();
}
void KisPaintDevice::clear(const QRect & rc)
{
m_d->currentStrategy()->clear(rc);
}
void KisPaintDevice::fill(const QRect & rc, const KoColor &color)
{
Q_ASSERT(*color.colorSpace() == *colorSpace());
m_d->currentStrategy()->fill(rc, color.data());
}
void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel)
{
m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel);
}
bool KisPaintDevice::write(KisPaintDeviceWriter &store)
{
return m_d->dataManager()->write(store);
}
bool KisPaintDevice::read(QIODevice *stream)
{
bool retval;
retval = m_d->dataManager()->read(stream);
m_d->cache()->invalidate();
return retval;
}
void KisPaintDevice::emitColorSpaceChanged()
{
emit colorSpaceChanged(m_d->colorSpace());
}
void KisPaintDevice::emitProfileChanged()
{
emit profileChanged(m_d->colorSpace()->profile());
}
KUndo2Command* KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
KUndo2Command *command = m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags);
return command;
}
bool KisPaintDevice::setProfile(const KoColorProfile * profile)
{
return m_d->assignProfile(profile);
}
KisDataManagerSP KisPaintDevice::dataManager() const
{
return m_d->dataManager();
}
void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile,
qint32 offsetX, qint32 offsetY)
{
QImage image = _image;
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
// Don't convert if not no profile is given and both paint dev and qimage are rgba.
if (!profile && colorSpace()->id() == "RGBA") {
writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height());
} else {
try {
quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()];
KoColorSpaceRegistry::instance()
->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile)
->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
writeBytes(dstData, offsetX, offsetY, image.width(), image.height());
delete[] dstData;
} catch (std::bad_alloc) {
warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes";
return;
}
}
m_d->cache()->invalidate();
}
QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
qint32 x1;
qint32 y1;
qint32 w;
qint32 h;
QRect rc = exactBounds();
x1 = rc.x();
y1 = rc.y();
w = rc.width();
h = rc.height();
return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags);
}
QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile,
const QRect &rc,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
return convertToQImage(dstProfile,
rc.x(), rc.y(), rc.width(), rc.height(),
renderingIntent, conversionFlags);
}
QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
if (w < 0)
return QImage();
if (h < 0)
return QImage();
quint8 *data = 0;
try {
data = new quint8 [w * h * pixelSize()];
} catch (std::bad_alloc) {
warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize();
//delete[] data; // data is not allocated, so don't free it
return QImage();
}
Q_CHECK_PTR(data);
// XXX: Is this really faster than converting line by line and building the QImage directly?
// This copies potentially a lot of data.
readBytes(data, x1, y1, w, h);
QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags);
delete[] data;
return image;
}
inline bool moveBy(KisSequentialConstIterator& iter, int numPixels)
{
int pos = 0;
while (pos < numPixels) {
int step = std::min(iter.nConseqPixels(), numPixels - pos);
if (!iter.nextPixels(step))
return false;
pos += step;
}
return true;
}
static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect)
{
KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace());
qint32 pixelSize = srcDev->pixelSize();
KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(0, 0);
KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(0, 0);
for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) {
qint32 iY = srcY0 + (y * srcHeight) / h;
for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) {
qint32 iX = srcX0 + (x * srcWidth) / w;
srcIter->moveTo(iX, iY);
dstIter->moveTo(x, y);
memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize);
}
}
return thumbnail;
}
QSize fixThumbnailSize(QSize size)
{
if (!size.width() && size.height()) {
size.setWidth(1);
}
if (size.width() && !size.height()) {
size.setHeight(1);
}
return size;
}
KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const
{
QSize thumbnailSize(w, h);
QRect imageRect = rect.isValid() ? rect : extent();
if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) {
thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio);
}
thumbnailSize = fixThumbnailSize(thumbnailSize);
//can't create thumbnail for an empty device, e.g. layer thumbnail for empty image
if (imageRect.isEmpty() || thumbnailSize.isEmpty()) {
return new KisPaintDevice(colorSpace());
}
int srcWidth, srcHeight;
int srcX0, srcY0;
imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight);
if (!outputRect.isValid()) {
outputRect = QRect(0, 0, w, h);
}
KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(),
thumbnailSize.width(), thumbnailSize.height(), outputRect);
return thumbnail;
}
KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const
{
QSize thumbnailSize(w, h);
qreal oversampleAdjusted = qMax(oversample, 1.);
QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize;
QRect outputRect;
QRect imageRect = rect.isValid() ? rect : extent();
qint32 hstart = thumbnailOversampledSize.height();
if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) {
thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio);
}
thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize);
//can't create thumbnail for an empty device, e.g. layer thumbnail for empty image
if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) {
return new KisPaintDevice(colorSpace());
}
oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size
outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height());
if (outputTileRect.isValid()) {
//compensating output rectangle for oversampling
outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight());
outputRect = outputRect.intersected(outputTileRect);
}
KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(),
thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect);
if (oversample != 1. && oversampleAdjusted != 1.) {
KoDummyUpdater updater;
KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
&updater, KisFilterStrategyRegistry::instance()->value("Bilinear"));
worker.run();
}
return thumbnail;
}
QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
QSize size = fixThumbnailSize(QSize(w, h));
KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect);
QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags);
return thumbnail;
}
QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
{
QSize size = fixThumbnailSize(QSize(w, h));
return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags);
}
KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
{
m_d->cache()->invalidate();
return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y());
}
KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
{
return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y());
}
KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w)
{
m_d->cache()->invalidate();
return m_d->currentStrategy()->createVLineIteratorNG(x, y, w);
}
KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
{
return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w);
}
KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const
{
- return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth);
+ return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator());
}
KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const
{
- return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth);
+ return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator());
}
KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG(qint32 x, qint32 y)
{
m_d->cache()->invalidate();
return m_d->currentStrategy()->createRandomAccessorNG(x, y);
}
KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG(qint32 x, qint32 y) const
{
return m_d->currentStrategy()->createRandomConstAccessorNG(x, y);
}
KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const
{
KisPaintDevice* pd = const_cast<KisPaintDevice*>(this);
return new KisRandomSubAccessor(pd);
}
void KisPaintDevice::clearSelection(KisSelectionSP selection)
{
const KoColorSpace *colorSpace = m_d->colorSpace();
QRect r = selection->selectedExactRect() & m_d->defaultBounds->bounds();
if (r.isValid()) {
KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width());
KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width());
const KoColor defaultPixel = this->defaultPixel();
bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8);
for (qint32 y = 0; y < r.height(); y++) {
do {
// XXX: Optimize by using stretches
colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1);
if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) {
memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize());
}
} while (devIt->nextPixel() && selectionIt->nextPixel());
devIt->nextRow();
selectionIt->nextRow();
}
m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y()));
setDirty(r);
}
}
bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const
{
KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1);
const quint8 *pix = iter->rawDataConst();
if (!pix) return false;
colorSpace()->toQColor(pix, c);
return true;
}
bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const
{
KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1);
const quint8 *pix = iter->rawDataConst();
if (!pix) return false;
kc->setColor(pix, m_d->colorSpace());
return true;
}
bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c)
{
KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1);
colorSpace()->fromQColor(c, iter->rawData());
m_d->cache()->invalidate();
return true;
}
bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc)
{
const quint8 * pix;
KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1);
if (kc.colorSpace() != m_d->colorSpace()) {
KoColor kc2(kc, m_d->colorSpace());
pix = kc2.data();
memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize());
} else {
pix = kc.data();
memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize());
}
m_d->cache()->invalidate();
return true;
}
bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src)
{
return m_d->fastBitBltPossible(src);
}
void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBlt(src, rect);
}
void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBltOldData(src, rect);
}
void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBltRough(src, rect);
}
void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect)
{
m_d->currentStrategy()->fastBitBltRoughOldData(src, rect);
}
void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const
{
readBytes(data, QRect(x, y, w, h));
}
void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const
{
m_d->currentStrategy()->readBytes(data, rect);
}
void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
{
writeBytes(data, QRect(x, y, w, h));
}
void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect)
{
m_d->currentStrategy()->writeBytes(data, rect);
}
QVector<quint8*> KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const
{
return m_d->currentStrategy()->readPlanarBytes(x, y, w, h);
}
void KisPaintDevice::writePlanarBytes(QVector<quint8*> planes, qint32 x, qint32 y, qint32 w, qint32 h)
{
m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h);
}
quint32 KisPaintDevice::pixelSize() const
{
quint32 _pixelSize = m_d->colorSpace()->pixelSize();
Q_ASSERT(_pixelSize > 0);
return _pixelSize;
}
quint32 KisPaintDevice::channelCount() const
{
quint32 _channelCount = m_d->colorSpace()->channelCount();
Q_ASSERT(_channelCount > 0);
return _channelCount;
}
KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id)
{
Q_ASSERT(!m_d->framesInterface);
m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this));
Q_ASSERT(!m_d->contentChannel);
m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds));
// Raster channels always have at least one frame (representing a static image)
KUndo2Command tempParentCommand;
m_d->contentChannel->addKeyframe(0, &tempParentCommand);
return m_d->contentChannel.data();
}
KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const
{
Q_ASSERT(m_d->contentChannel);
return m_d->contentChannel.data();
}
const KoColorSpace* KisPaintDevice::colorSpace() const
{
Q_ASSERT(m_d->colorSpace() != 0);
return m_d->colorSpace();
}
KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const
{
KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace());
device->setDefaultBounds(defaultBounds());
return device;
}
KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const
{
KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource);
clone->setDefaultBounds(defaultBounds());
clone->convertTo(compositionSourceColorSpace(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return clone;
}
KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const
{
KisPaintDeviceSP clone = new KisPaintDevice(colorSpace());
clone->setDefaultBounds(defaultBounds());
clone->makeCloneFromRough(cloneSource, roughRect);
clone->convertTo(compositionSourceColorSpace(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
return clone;
}
KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const
{
return new KisFixedPaintDevice(compositionSourceColorSpace());
}
const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const
{
return colorSpace();
}
QVector<qint32> KisPaintDevice::channelSizes() const
{
QVector<qint32> sizes;
QList<KoChannelInfo*> channels = colorSpace()->channels();
qSort(channels);
Q_FOREACH (KoChannelInfo * channelInfo, channels) {
sizes.append(channelInfo->size());
}
return sizes;
}
KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject()
{
KisDataManager::releaseInternalPools();
}
KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject()
{
return new MemoryReleaseObject();
}
KisPaintDevice::LodDataStruct::~LodDataStruct()
{
}
QRegion KisPaintDevice::regionForLodSyncing() const
{
return m_d->regionForLodSyncing();
}
KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod)
{
return m_d->createLodDataStruct(lod);
}
void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect)
{
m_d->updateLodDataStruct(dst, srcRect);
}
void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst)
{
m_d->uploadLodDataStruct(dst);
}
KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface()
{
return m_d->framesInterface.data();
}
/******************************************************************/
/* KisPaintDeviceFramesInterface */
/******************************************************************/
KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice)
: q(parentDevice)
{
}
QList<int> KisPaintDeviceFramesInterface::frames()
{
return q->m_d->frameIds();
}
int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand)
{
return q->m_d->createFrame(copy, copySrc, offset, parentCommand);
}
void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand)
{
return q->m_d->deleteFrame(frame, parentCommand);
}
void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice)
{
q->m_d->fetchFrame(frameId, targetDevice);
}
void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice)
{
q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice);
}
void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice)
{
q->m_d->uploadFrame(dstFrameId, srcDevice);
}
QRect KisPaintDeviceFramesInterface::frameBounds(int frameId)
{
return q->m_d->frameBounds(frameId);
}
QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const
{
return q->m_d->frameOffset(frameId);
}
void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId)
{
KIS_ASSERT_RECOVER_RETURN(frameId >= 0);
q->m_d->setFrameDefaultPixel(defPixel, frameId);
}
KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return KoColor(Qt::red, q->m_d->colorSpace());
}
return q->m_d->frameDefaultPixel(frameId);
}
bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId)
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return false;
}
return q->m_d->writeFrame(store, frameId);
}
bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId)
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return false;
}
return q->m_d->readFrame(stream, frameId);
}
int KisPaintDeviceFramesInterface::currentFrameId() const
{
return q->m_d->currentFrameId();
}
KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const
{
KIS_ASSERT_RECOVER(frameId >= 0) {
return q->m_d->dataManager();
}
return q->m_d->frameDataManager(frameId);
}
void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId)
{
KIS_ASSERT_RECOVER_RETURN(frameId >= 0);
return q->m_d->invalidateFrameCache(frameId);
}
void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset)
{
KIS_ASSERT_RECOVER_RETURN(frameId >= 0);
return q->m_d->setFrameOffset(frameId, offset);
}
KisPaintDeviceFramesInterface::TestingDataObjects
KisPaintDeviceFramesInterface::testingGetDataObjects() const
{
TestingDataObjects objects;
objects.m_data = q->m_d->m_data.data();
objects.m_lodData = q->m_d->m_lodData.data();
objects.m_externalFrameData = q->m_d->m_externalFrameData.data();
typedef KisPaintDevice::Private::FramesHash FramesHash;
FramesHash::const_iterator it = q->m_d->m_frames.constBegin();
FramesHash::const_iterator end = q->m_d->m_frames.constEnd();
for (; it != end; ++it) {
objects.m_frames.insert(it.key(), it.value().data());
}
objects.m_currentData = q->m_d->currentData();
return objects;
}
QList<KisPaintDeviceData*> KisPaintDeviceFramesInterface::testingGetDataObjectsList() const
{
return q->m_d->allDataObjects();
}
void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice)
{
m_d->tesingFetchLodDevice(targetDevice);
}
diff --git a/libs/image/kis_paint_device_data.h b/libs/image/kis_paint_device_data.h
index c5873ae1cb..8e9d2555f7 100644
--- a/libs/image/kis_paint_device_data.h
+++ b/libs/image/kis_paint_device_data.h
@@ -1,261 +1,284 @@
/*
* Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PAINT_DEVICE_DATA_H
#define __KIS_PAINT_DEVICE_DATA_H
#include "KoAlwaysInline.h"
#include "kundo2command.h"
struct DirectDataAccessPolicy {
- DirectDataAccessPolicy(KisDataManager *dataManager)
- : m_dataManager(dataManager) {}
+ DirectDataAccessPolicy(KisDataManager *dataManager, KisIteratorCompleteListener *completionListener)
+ : m_dataManager(dataManager),
+ m_completionListener(completionListener){}
+
KisHLineConstIteratorSP createConstIterator(const QRect &rect) {
const int xOffset = 0;
const int yOffset = 0;
- return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, false);
+ return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, false, m_completionListener);
}
KisHLineIteratorSP createIterator(const QRect &rect) {
const int xOffset = 0;
const int yOffset = 0;
- return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, true);
+ return new KisHLineIterator2(m_dataManager, rect.x(), rect.y(), rect.width(), xOffset, yOffset, true, m_completionListener);
}
int pixelSize() const {
return m_dataManager->pixelSize();
}
KisDataManager *m_dataManager;
+ KisIteratorCompleteListener *m_completionListener;
};
class KisPaintDeviceData
{
public:
KisPaintDeviceData(KisPaintDevice *paintDevice)
: m_cache(paintDevice),
m_x(0), m_y(0),
m_colorSpace(0),
- m_levelOfDetail(0)
+ m_levelOfDetail(0),
+ m_cacheInvalidator(this)
{
}
KisPaintDeviceData(const KisPaintDeviceData *rhs, bool cloneContent)
: m_dataManager(cloneContent ?
new KisDataManager(*rhs->m_dataManager) :
new KisDataManager(rhs->m_dataManager->pixelSize(), rhs->m_dataManager->defaultPixel())),
m_cache(rhs->m_cache),
m_x(rhs->m_x),
m_y(rhs->m_y),
m_colorSpace(rhs->m_colorSpace),
- m_levelOfDetail(rhs->m_levelOfDetail)
+ m_levelOfDetail(rhs->m_levelOfDetail),
+ m_cacheInvalidator(this)
{
m_cache.setupCache();
}
void init(const KoColorSpace *cs, KisDataManagerSP dataManager) {
m_colorSpace = cs;
m_dataManager = dataManager;
m_cache.setupCache();
}
class ChangeColorSpaceCommand : public KUndo2Command {
public:
ChangeColorSpaceCommand(KisPaintDeviceData *data,
KisDataManagerSP oldDm, KisDataManagerSP newDm,
const KoColorSpace *oldCs, const KoColorSpace *newCs,
KUndo2Command *parent)
: KUndo2Command(parent),
m_firstRun(true),
m_data(data),
m_oldDm(oldDm),
m_newDm(newDm),
m_oldCs(oldCs),
m_newCs(newCs)
{
}
void forcedRedo() {
m_data->m_dataManager = m_newDm;
m_data->m_colorSpace = m_newCs;
m_data->m_cache.setupCache();
}
void redo() {
KUndo2Command::redo();
if (!m_firstRun) {
m_firstRun = false;
return;
}
forcedRedo();
}
void undo() {
m_data->m_dataManager = m_oldDm;
m_data->m_colorSpace = m_oldCs;
m_data->m_cache.setupCache();
KUndo2Command::undo();
}
private:
bool m_firstRun;
KisPaintDeviceData *m_data;
KisDataManagerSP m_oldDm;
KisDataManagerSP m_newDm;
const KoColorSpace *m_oldCs;
const KoColorSpace *m_newCs;
};
void assignColorSpace(const KoColorSpace *dstColorSpace) {
KIS_ASSERT_RECOVER_RETURN(m_colorSpace->pixelSize() == dstColorSpace->pixelSize());
m_colorSpace = dstColorSpace;
m_cache.invalidate();
}
void convertDataColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) {
typedef KisSequentialIteratorBase<ReadOnlyIteratorPolicy<DirectDataAccessPolicy>, DirectDataAccessPolicy> InternalSequentialConstIterator;
typedef KisSequentialIteratorBase<WritableIteratorPolicy<DirectDataAccessPolicy>, DirectDataAccessPolicy> InternalSequentialIterator;
if (m_colorSpace == dstColorSpace || *m_colorSpace == *dstColorSpace) {
return;
}
QRect rc = m_dataManager->region().boundingRect();
const int dstPixelSize = dstColorSpace->pixelSize();
QScopedArrayPointer<quint8> dstDefaultPixel(new quint8[dstPixelSize]);
memset(dstDefaultPixel.data(), 0, dstPixelSize);
m_colorSpace->convertPixelsTo(m_dataManager->defaultPixel(), dstDefaultPixel.data(), dstColorSpace, 1, renderingIntent, conversionFlags);
KisDataManagerSP dstDataManager = new KisDataManager(dstPixelSize, dstDefaultPixel.data());
if (!rc.isEmpty()) {
- InternalSequentialConstIterator srcIt(DirectDataAccessPolicy(m_dataManager.data()), rc);
- InternalSequentialIterator dstIt(DirectDataAccessPolicy(dstDataManager.data()), rc);
+ InternalSequentialConstIterator srcIt(DirectDataAccessPolicy(m_dataManager.data(), cacheInvalidator()), rc);
+ InternalSequentialIterator dstIt(DirectDataAccessPolicy(dstDataManager.data(), cacheInvalidator()), rc);
int nConseqPixels = 0;
do {
nConseqPixels = srcIt.nConseqPixels();
const quint8 *srcData = srcIt.rawDataConst();
quint8 *dstData = dstIt.rawData();
m_colorSpace->convertPixelsTo(srcData, dstData,
dstColorSpace,
nConseqPixels,
renderingIntent, conversionFlags);
} while(srcIt.nextPixels(nConseqPixels) &&
dstIt.nextPixels(nConseqPixels));
}
// becomes owned by the parent
ChangeColorSpaceCommand *cmd =
new ChangeColorSpaceCommand(this,
m_dataManager, dstDataManager,
m_colorSpace, dstColorSpace,
parentCommand);
cmd->forcedRedo();
}
void prepareClone(const KisPaintDeviceData *srcData, bool copyContent = false) {
m_x = srcData->x();
m_y = srcData->y();
if (copyContent) {
m_dataManager = new KisDataManager(*srcData->dataManager());
} else if (m_dataManager->pixelSize() !=
srcData->dataManager()->pixelSize()) {
// NOTE: we don't check default pixel value! it is the task of
// the higher level!
m_dataManager = new KisDataManager(srcData->dataManager()->pixelSize(), srcData->dataManager()->defaultPixel());
m_cache.setupCache();
} else {
m_dataManager->clear();
const quint8 *srcDefPixel = srcData->dataManager()->defaultPixel();
const int cmp =
memcmp(srcDefPixel,
m_dataManager->defaultPixel(),
m_dataManager->pixelSize());
if (cmp != 0) {
m_dataManager->setDefaultPixel(srcDefPixel);
}
}
m_levelOfDetail = srcData->levelOfDetail();
m_colorSpace = srcData->colorSpace();
m_cache.invalidate();
}
ALWAYS_INLINE KisDataManagerSP dataManager() const {
return m_dataManager;
}
ALWAYS_INLINE KisPaintDeviceCache* cache() {
return &m_cache;
}
ALWAYS_INLINE qint32 x() const {
return m_x;
}
ALWAYS_INLINE void setX(qint32 value) {
m_x = value;
}
ALWAYS_INLINE qint32 y() const {
return m_y;
}
ALWAYS_INLINE void setY(qint32 value) {
m_y = value;
}
ALWAYS_INLINE const KoColorSpace* colorSpace() const {
return m_colorSpace;
}
ALWAYS_INLINE qint32 levelOfDetail() const {
return m_levelOfDetail;
}
ALWAYS_INLINE void setLevelOfDetail(qint32 value) {
m_levelOfDetail = value;
}
+ ALWAYS_INLINE KisIteratorCompleteListener* cacheInvalidator() {
+ return &m_cacheInvalidator;
+ }
+
+
+private:
+ struct CacheInvalidator : public KisIteratorCompleteListener {
+ CacheInvalidator(KisPaintDeviceData *_q) : q(_q) {}
+
+ void notifyWritableIteratorCompleted() {
+ q->cache()->invalidate();
+ }
+ private:
+ KisPaintDeviceData *q;
+ };
+
+
private:
KisDataManagerSP m_dataManager;
KisPaintDeviceCache m_cache;
qint32 m_x;
qint32 m_y;
const KoColorSpace* m_colorSpace;
qint32 m_levelOfDetail;
+ CacheInvalidator m_cacheInvalidator;
};
#endif /* __KIS_PAINT_DEVICE_DATA_H */
diff --git a/libs/image/kis_paint_device_strategies.h b/libs/image/kis_paint_device_strategies.h
index 3aae3f2a39..e8899b3eb7 100644
--- a/libs/image/kis_paint_device_strategies.h
+++ b/libs/image/kis_paint_device_strategies.h
@@ -1,450 +1,450 @@
/*
* 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_PAINT_DEVICE_STRATEGIES_H
#define __KIS_PAINT_DEVICE_STRATEGIES_H
#include "kis_wrapped_rect.h"
#include "kis_wrapped_hline_iterator.h"
#include "kis_wrapped_vline_iterator.h"
#include "kis_wrapped_random_accessor.h"
class KisPaintDevice::Private::KisPaintDeviceStrategy
{
public:
KisPaintDeviceStrategy(KisPaintDevice *device,
KisPaintDevice::Private *d)
: m_device(device), m_d(d)
{
}
virtual ~KisPaintDeviceStrategy() {
}
virtual void move(const QPoint& pt) {
m_d->setX(pt.x());
m_d->setY(pt.y());
m_d->cache()->invalidate();
}
virtual QRect extent() const {
QRect extent;
qint32 x, y, w, h;
m_d->dataManager()->extent(x, y, w, h);
x += m_d->x();
y += m_d->y();
extent = QRect(x, y, w, h);
quint8 defaultOpacity = m_device->defaultPixel().opacityU8();
if (defaultOpacity != OPACITY_TRANSPARENT_U8)
extent |= m_d->defaultBounds->bounds();
return extent;
}
virtual QRegion region() const {
return m_d->cache()->region().translated(m_d->x(), m_d->y());
}
virtual void crop(const QRect &rect) {
m_d->dataManager()->setExtent(rect.translated(-m_d->x(), -m_d->y()));
m_d->cache()->invalidate();
}
virtual void clear(const QRect & rc) {
KisDataManagerSP dm = m_d->dataManager();
dm->clear(rc.x() - m_d->x(), rc.y() - m_d->y(),
rc.width(), rc.height(),
dm->defaultPixel());
m_d->cache()->invalidate();
}
virtual void fill(const QRect &rc, const quint8 *fillPixel) {
m_d->dataManager()->clear(rc.x() - m_d->x(),
rc.y() - m_d->y(),
rc.width(),
rc.height(),
fillPixel);
m_d->cache()->invalidate();
}
virtual KisHLineIteratorSP createHLineIteratorNG(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY) {
- return new KisHLineIterator2(dataManager, x, y, w, offsetX, offsetY, true);
+ return new KisHLineIterator2(dataManager, x, y, w, offsetX, offsetY, true, m_d->cacheInvalidator());
}
virtual KisHLineConstIteratorSP createHLineConstIteratorNG(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY) const {
- return new KisHLineIterator2(dataManager, x, y, w, offsetX, offsetY, false);
+ return new KisHLineIterator2(dataManager, x, y, w, offsetX, offsetY, false, m_d->cacheInvalidator());
}
virtual KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 w) {
m_d->cache()->invalidate();
- return new KisVLineIterator2(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), true);
+ return new KisVLineIterator2(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), true, m_d->cacheInvalidator());
}
virtual KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const {
- return new KisVLineIterator2(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), false);
+ return new KisVLineIterator2(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), false, m_d->cacheInvalidator());
}
virtual KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y) {
m_d->cache()->invalidate();
- return new KisRandomAccessor2(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), true);
+ return new KisRandomAccessor2(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), true, m_d->cacheInvalidator());
}
virtual KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const {
- return new KisRandomAccessor2(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), false);
+ return new KisRandomAccessor2(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), false, m_d->cacheInvalidator());
}
virtual void fastBitBlt(KisPaintDeviceSP src, const QRect &rect) {
Q_ASSERT(m_device->fastBitBltPossible(src));
fastBitBltImpl(src->dataManager(), rect);
}
virtual void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) {
Q_ASSERT(m_device->fastBitBltPossible(src));
m_d->dataManager()->bitBltOldData(src->dataManager(), rect.translated(-m_d->x(), -m_d->y()));
m_d->cache()->invalidate();
}
virtual void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) {
Q_ASSERT(m_device->fastBitBltPossible(src));
fastBitBltRoughImpl(src->dataManager(), rect);
}
virtual void fastBitBltRough(KisDataManagerSP srcDataManager, const QRect &rect) {
fastBitBltRoughImpl(srcDataManager, rect);
}
virtual void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) {
Q_ASSERT(m_device->fastBitBltPossible(src));
m_d->dataManager()->bitBltRoughOldData(src->dataManager(), rect.translated(-m_d->x(), -m_d->y()));
m_d->cache()->invalidate();
}
virtual void readBytes(quint8 *data, const QRect &rect) const {
readBytesImpl(data, rect, -1);
}
virtual void writeBytes(const quint8 * data, const QRect &rect) {
writeBytesImpl(data, rect, -1);
}
virtual QVector<quint8*> readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const {
return m_d->dataManager()->readPlanarBytes(m_device->channelSizes(), x, y, w, h);
}
virtual void writePlanarBytes(QVector<quint8*> planes, qint32 x, qint32 y, qint32 w, qint32 h) {
m_d->dataManager()->writePlanarBytes(planes, m_device->channelSizes(), x, y, w, h);
m_d->cache()->invalidate();
}
protected:
virtual void readBytesImpl(quint8 *data, const QRect &rect, int dataRowStride) const {
m_d->dataManager()->readBytes(data,
rect.x() - m_d->x(),
rect.y() - m_d->y(),
rect.width(),
rect.height(),
dataRowStride);
}
virtual void writeBytesImpl(const quint8 * data, const QRect &rect, int dataRowStride) {
m_d->dataManager()->writeBytes(data,
rect.x() - m_d->x(),
rect.y() - m_d->y(),
rect.width(),
rect.height(),
dataRowStride);
m_d->cache()->invalidate();
}
virtual void fastBitBltImpl(KisDataManagerSP srcDataManager, const QRect &rect)
{
m_d->dataManager()->bitBlt(srcDataManager, rect.translated(-m_d->x(), -m_d->y()));
m_d->cache()->invalidate();
}
virtual void fastBitBltRoughImpl(KisDataManagerSP srcDataManager, const QRect &rect)
{
m_d->dataManager()->bitBltRough(srcDataManager, rect.translated(-m_d->x(), -m_d->y()));
m_d->cache()->invalidate();
}
protected:
KisPaintDevice *m_device;
KisPaintDevice::Private * const m_d;
};
class KisPaintDevice::Private::KisPaintDeviceWrappedStrategy : public KisPaintDeviceStrategy
{
public:
KisPaintDeviceWrappedStrategy(const QRect &wrapRect,
KisPaintDevice *device,
KisPaintDevice::Private *d)
: KisPaintDeviceStrategy(device, d),
m_wrapRect(wrapRect)
{
}
const QRect wrapRect() const {
return m_wrapRect;
}
void move(const QPoint& pt) {
QPoint offset (pt.x() - m_device->x(), pt.y() - m_device->y());
QRect exactBoundsBeforeMove = m_device->exactBounds();
KisPaintDeviceStrategy::move(pt);
QRegion borderRegion(exactBoundsBeforeMove.translated(offset.x(), offset.y()));
borderRegion -= m_wrapRect;
const int pixelSize = m_device->pixelSize();
Q_FOREACH (const QRect &rc, borderRegion.rects()) {
KisRandomConstAccessorSP srcIt = KisPaintDeviceStrategy::createRandomConstAccessorNG(rc.x(), rc.y());
KisRandomAccessorSP dstIt = createRandomAccessorNG(rc.x(), rc.y());
int rows = 1;
int columns = 1;
for (int y = rc.y(); y <= rc.bottom(); y += rows) {
int rows = qMin(srcIt->numContiguousRows(y), dstIt->numContiguousRows(y));
rows = qMin(rows, rc.bottom() - y + 1);
for (int x = rc.x(); x <= rc.right(); x += columns) {
int columns = qMin(srcIt->numContiguousColumns(x), dstIt->numContiguousColumns(x));
columns = qMin(columns, rc.right() - x + 1);
srcIt->moveTo(x, y);
dstIt->moveTo(x, y);
int srcRowStride = srcIt->rowStride(x, y);
int dstRowStride = dstIt->rowStride(x, y);
const quint8 *srcPtr = srcIt->rawDataConst();
quint8 *dstPtr = dstIt->rawData();
for (int i = 0; i < rows; i++) {
memcpy(dstPtr, srcPtr, pixelSize * columns);
srcPtr += srcRowStride;
dstPtr += dstRowStride;
}
}
}
}
}
QRect extent() const {
return KisPaintDeviceStrategy::extent() & m_wrapRect;
}
QRegion region() const {
return KisPaintDeviceStrategy::region() & m_wrapRect;
}
void crop(const QRect &rect) {
KisPaintDeviceStrategy::crop(rect & m_wrapRect);
}
void clear(const QRect &rect) {
KisWrappedRect splitRect(rect, m_wrapRect);
Q_FOREACH (const QRect &rc, splitRect) {
KisPaintDeviceStrategy::clear(rc);
}
}
void fill(const QRect &rect, const quint8 *fillPixel) {
KisWrappedRect splitRect(rect, m_wrapRect);
Q_FOREACH (const QRect &rc, splitRect) {
KisPaintDeviceStrategy::fill(rc, fillPixel);
}
}
virtual KisHLineIteratorSP createHLineIteratorNG(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY) {
KisWrappedRect splitRect(QRect(x, y, w, m_wrapRect.height()), m_wrapRect);
if (!splitRect.isSplit()) {
return KisPaintDeviceStrategy::createHLineIteratorNG(dataManager, x, y, w, offsetX, offsetY);
}
- return new KisWrappedHLineIterator(dataManager, splitRect, offsetX, offsetY, true);
+ return new KisWrappedHLineIterator(dataManager, splitRect, offsetX, offsetY, true, m_d->cacheInvalidator());
}
virtual KisHLineConstIteratorSP createHLineConstIteratorNG(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY) const {
KisWrappedRect splitRect(QRect(x, y, w, m_wrapRect.height()), m_wrapRect);
if (!splitRect.isSplit()) {
return KisPaintDeviceStrategy::createHLineConstIteratorNG(dataManager, x, y, w, offsetX, offsetY);
}
- return new KisWrappedHLineIterator(dataManager, splitRect, offsetX, offsetY, false);
+ return new KisWrappedHLineIterator(dataManager, splitRect, offsetX, offsetY, false, m_d->cacheInvalidator());
}
virtual KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h) {
m_d->cache()->invalidate();
KisWrappedRect splitRect(QRect(x, y, m_wrapRect.width(), h), m_wrapRect);
if (!splitRect.isSplit()) {
return KisPaintDeviceStrategy::createVLineIteratorNG(x, y, h);
}
- return new KisWrappedVLineIterator(m_d->dataManager().data(), splitRect, m_d->x(), m_d->y(), true);
+ return new KisWrappedVLineIterator(m_d->dataManager().data(), splitRect, m_d->x(), m_d->y(), true, m_d->cacheInvalidator());
}
virtual KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const {
KisWrappedRect splitRect(QRect(x, y, m_wrapRect.width(), h), m_wrapRect);
if (!splitRect.isSplit()) {
return KisPaintDeviceStrategy::createVLineConstIteratorNG(x, y, h);
}
- return new KisWrappedVLineIterator(m_d->dataManager().data(), splitRect, m_d->x(), m_d->y(), false);
+ return new KisWrappedVLineIterator(m_d->dataManager().data(), splitRect, m_d->x(), m_d->y(), false, m_d->cacheInvalidator());
}
virtual KisRandomAccessorSP createRandomAccessorNG(qint32 x, qint32 y) {
m_d->cache()->invalidate();
- return new KisWrappedRandomAccessor(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), true, m_wrapRect);
+ return new KisWrappedRandomAccessor(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), true, m_d->cacheInvalidator(), m_wrapRect);
}
virtual KisRandomConstAccessorSP createRandomConstAccessorNG(qint32 x, qint32 y) const {
- return new KisWrappedRandomAccessor(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), false, m_wrapRect);
+ return new KisWrappedRandomAccessor(m_d->dataManager().data(), x, y, m_d->x(), m_d->y(), false, m_d->cacheInvalidator(), m_wrapRect);
}
void fastBitBltImpl(KisDataManagerSP srcDataManager, const QRect &rect) {
KisWrappedRect splitRect(rect, m_wrapRect);
Q_FOREACH (const QRect &rc, splitRect) {
KisPaintDeviceStrategy::fastBitBltImpl(srcDataManager, rc);
}
}
void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) {
KisWrappedRect splitRect(rect, m_wrapRect);
Q_FOREACH (const QRect &rc, splitRect) {
KisPaintDeviceStrategy::fastBitBltOldData(src, rc);
}
}
virtual void fastBitBltRoughImpl(KisDataManagerSP srcDataManager, const QRect &rect)
{
// no rough version in wrapped mode
fastBitBltImpl(srcDataManager, rect);
}
void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) {
// no rough version in wrapped mode
fastBitBltOldData(src, rect);
}
void readBytes(quint8 *data, const QRect &rect) const {
KisWrappedRect splitRect(rect, m_wrapRect);
if (!splitRect.isSplit()) {
KisPaintDeviceStrategy::readBytes(data, rect);
} else {
const int pixelSize = m_device->pixelSize();
int leftWidth = splitRect[KisWrappedRect::TOPLEFT].width();
int rightWidth = splitRect[KisWrappedRect::TOPRIGHT].width();
int totalHeight = rect.height();
int totalWidth = rect.width();
int dataRowStride = totalWidth * pixelSize;
int bufOffset = 0;
int row = 0;
while (row < totalHeight) {
int leftIndex = KisWrappedRect::TOPLEFT + bufOffset;
int rightIndex = KisWrappedRect::TOPRIGHT + bufOffset;
QPoint leftRectOrigin = splitRect[leftIndex].topLeft();
QPoint rightRectOrigin = splitRect[rightIndex].topLeft();
int height = qMin(splitRect[leftIndex].height(), totalHeight - row);
int col = 0;
while (col < totalWidth) {
int width;
quint8 *dataPtr;
width = qMin(leftWidth, totalWidth - col);
dataPtr = data + pixelSize * (col + row * totalWidth);
readBytesImpl(dataPtr, QRect(leftRectOrigin, QSize(width, height)), dataRowStride);
col += width;
if (col >= totalWidth) break;
width = qMin(rightWidth, totalWidth - col);
dataPtr = data + pixelSize * (col + row * totalWidth);
readBytesImpl(dataPtr, QRect(rightRectOrigin, QSize(width, height)), dataRowStride);
col += width;
}
row += height;
bufOffset = (bufOffset + 2) % 4;
}
}
}
void writeBytes(const quint8 *data, const QRect &rect) {
KisWrappedRect splitRect(rect, m_wrapRect);
if (!splitRect.isSplit()) {
KisPaintDeviceStrategy::writeBytes(data, rect);
} else {
const int pixelSize = m_device->pixelSize();
int totalWidth = rect.width();
int dataRowStride = totalWidth * pixelSize;
QRect rc;
QPoint origin;
const quint8 *dataPtr;
origin.rx() = 0;
origin.ry() = 0;
rc = splitRect.topLeft();
dataPtr = data + pixelSize * (origin.x() + totalWidth * origin.y());
writeBytesImpl(dataPtr, rc, dataRowStride);
origin.rx() = splitRect.topLeft().width();
origin.ry() = 0;
rc = splitRect.topRight();
dataPtr = data + pixelSize * (origin.x() + totalWidth * origin.y());
writeBytesImpl(dataPtr, rc, dataRowStride);
origin.rx() = 0;
origin.ry() = splitRect.topLeft().height();
rc = splitRect.bottomLeft();
dataPtr = data + pixelSize * (origin.x() + totalWidth * origin.y());
writeBytesImpl(dataPtr, rc, dataRowStride);
origin.rx() = splitRect.topLeft().width();
origin.ry() = splitRect.topLeft().height();
rc = splitRect.bottomRight();
dataPtr = data + pixelSize * (origin.x() + totalWidth * origin.y());
writeBytesImpl(dataPtr, rc, dataRowStride);
}
}
private:
QRect m_wrapRect;
};
#endif /* __KIS_PAINT_DEVICE_STRATEGIES_H */
diff --git a/libs/image/kis_paint_layer.cc b/libs/image/kis_paint_layer.cc
index 25e8e1691b..83adfd0882 100644
--- a/libs/image/kis_paint_layer.cc
+++ b/libs/image/kis_paint_layer.cc
@@ -1,354 +1,350 @@
/*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2006 Bart Coppens <kde@bartcoppens.be>
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* 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.
*/
#include "kis_paint_layer.h"
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <kis_icon.h>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoCompositeOpRegistry.h>
#include <KoProperties.h>
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_paint_device.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_default_bounds.h"
#include "kis_onion_skin_compositor.h"
#include "kis_raster_keyframe_channel.h"
#include "kis_signal_auto_connection.h"
#include "kis_layer_properties_icons.h"
#include "kis_onion_skin_cache.h"
struct Q_DECL_HIDDEN KisPaintLayer::Private
{
public:
Private() : contentChannel(0) {}
KisPaintDeviceSP paintDevice;
QBitArray paintChannelFlags;
// the real pointer is owned by the paint device
KisRasterKeyframeChannel *contentChannel;
KisSignalAutoConnectionsStore onionSkinConnection;
KisOnionSkinCache onionSkinCache;
};
KisPaintLayer::KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, KisPaintDeviceSP dev)
: KisLayer(image, name, opacity)
, m_d(new Private())
{
Q_ASSERT(dev);
init(dev);
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image));
}
KisPaintLayer::KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity)
: KisLayer(image, name, opacity)
, m_d(new Private())
{
Q_ASSERT(image);
init(new KisPaintDevice(this, image->colorSpace(), new KisDefaultBounds(image)));
}
KisPaintLayer::KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, const KoColorSpace * colorSpace)
: KisLayer(image, name, opacity)
, m_d(new Private())
{
if (!colorSpace) {
Q_ASSERT(image);
colorSpace = image->colorSpace();
}
Q_ASSERT(colorSpace);
init(new KisPaintDevice(this, colorSpace, new KisDefaultBounds(image)));
}
KisPaintLayer::KisPaintLayer(const KisPaintLayer& rhs)
: KisLayer(rhs)
, KisIndirectPaintingSupport()
, m_d(new Private)
{
const bool copyFrames = (rhs.m_d->contentChannel != 0);
if (!copyFrames) {
init(new KisPaintDevice(*rhs.m_d->paintDevice.data()), rhs.m_d->paintChannelFlags);
} else {
init(new KisPaintDevice(*rhs.m_d->paintDevice.data(), true, this), rhs.m_d->paintChannelFlags);
m_d->contentChannel = m_d->paintDevice->keyframeChannel();
addKeyframeChannel(m_d->contentChannel);
m_d->contentChannel->setOnionSkinsEnabled(rhs.onionSkinEnabled());
KisLayer::enableAnimation();
}
}
void KisPaintLayer::init(KisPaintDeviceSP paintDevice, const QBitArray &paintChannelFlags)
{
m_d->paintDevice = paintDevice;
m_d->paintDevice->setParentNode(this);
m_d->paintChannelFlags = paintChannelFlags;
}
KisPaintLayer::~KisPaintLayer()
{
delete m_d;
}
bool KisPaintLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
KisPaintDeviceSP KisPaintLayer::original() const
{
return m_d->paintDevice;
}
KisPaintDeviceSP KisPaintLayer::paintDevice() const
{
return m_d->paintDevice;
}
bool KisPaintLayer::needProjection() const
{
return hasTemporaryTarget() || (isAnimated() && onionSkinEnabled());
}
void KisPaintLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const
{
KisIndirectPaintingSupport::ReadLocker l(this);
KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect);
if (hasTemporaryTarget()) {
KisPainter gc(projection);
setupTemporaryPainter(&gc);
gc.bitBlt(rect.topLeft(), temporaryTarget(), rect);
}
if (m_d->contentChannel &&
m_d->contentChannel->keyframeCount() > 1 &&
onionSkinEnabled() &&
!m_d->paintDevice->defaultBounds()->externalFrameActive()) {
KisPaintDeviceSP skins = m_d->onionSkinCache.projection(m_d->paintDevice);
KisPainter gcDest(projection);
gcDest.setCompositeOp(m_d->paintDevice->colorSpace()->compositeOp(COMPOSITE_BEHIND));
gcDest.bitBlt(rect.topLeft(), skins, rect);
gcDest.end();
}
if (!m_d->contentChannel ||
(m_d->contentChannel && m_d->contentChannel->keyframeCount() <= 1) ||
!onionSkinEnabled()) {
m_d->onionSkinCache.reset();
}
}
-void KisPaintLayer::setDirty(const QRect & rect)
-{
- KisLayer::setDirty(rect);
-}
-
QIcon KisPaintLayer::icon() const
{
return KisIconUtils::loadIcon("paintLayer");
}
void KisPaintLayer::setImage(KisImageWSP image)
{
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(image));
KisLayer::setImage(image);
}
KisBaseNode::PropertyList KisPaintLayer::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::alphaLocked, alphaLocked());
if (isAnimated()) {
l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::onionSkins, onionSkinEnabled());
}
return l;
}
void KisPaintLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
{
Q_FOREACH (const KisBaseNode::Property &property, properties) {
if (property.name == i18n("Alpha Locked")) {
setAlphaLocked(property.state.toBool());
}
else if (property.name == i18n("Onion Skins")) {
setOnionSkinEnabled(property.state.toBool());
}
}
KisLayer::setSectionModelProperties(properties);
}
const KoColorSpace * KisPaintLayer::colorSpace() const
{
return m_d->paintDevice->colorSpace();
}
bool KisPaintLayer::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisPaintLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
void KisPaintLayer::setChannelLockFlags(const QBitArray& channelFlags)
{
Q_ASSERT(((quint32)channelFlags.count() == colorSpace()->channelCount() || channelFlags.isEmpty()));
m_d->paintChannelFlags = channelFlags;
}
const QBitArray& KisPaintLayer::channelLockFlags() const
{
return m_d->paintChannelFlags;
}
QRect KisPaintLayer::extent() const
{
KisPaintDeviceSP t = temporaryTarget();
QRect rect = t ? t->extent() : QRect();
if (onionSkinEnabled()) rect |= KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice);
return rect | KisLayer::extent();
}
QRect KisPaintLayer::exactBounds() const
{
KisPaintDeviceSP t = temporaryTarget();
QRect rect = t ? t->extent() : QRect();
if (onionSkinEnabled()) rect |= KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice);
return rect | KisLayer::exactBounds();
}
bool KisPaintLayer::alphaLocked() const
{
QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->paintChannelFlags;
return flags.count(true) == 0 && !m_d->paintChannelFlags.isEmpty();
}
void KisPaintLayer::setAlphaLocked(bool lock)
{
if(m_d->paintChannelFlags.isEmpty())
m_d->paintChannelFlags = colorSpace()->channelFlags(true, true);
if(lock)
m_d->paintChannelFlags &= colorSpace()->channelFlags(true, false);
else
m_d->paintChannelFlags |= colorSpace()->channelFlags(false, true);
baseNodeChangedCallback();
}
bool KisPaintLayer::onionSkinEnabled() const
{
return nodeProperties().boolProperty("onionskin", false);
}
void KisPaintLayer::setOnionSkinEnabled(bool state)
{
int oldState = onionSkinEnabled();
if (oldState == state) return;
if (state == false && oldState) {
// FIXME: change ordering! race condition possible!
// Turning off onionskins shrinks our extent. Let's clean up the onion skins first
setDirty(KisOnionSkinCompositor::instance()->calculateExtent(m_d->paintDevice));
}
if (state) {
m_d->onionSkinConnection.addConnection(KisOnionSkinCompositor::instance(),
SIGNAL(sigOnionSkinChanged()),
this,
SLOT(slotExternalUpdateOnionSkins()));
} else {
m_d->onionSkinConnection.clear();
}
nodeProperties().setProperty("onionskin", state);
if (m_d->contentChannel) {
m_d->contentChannel->setOnionSkinsEnabled(state);
}
baseNodeChangedCallback();
}
void KisPaintLayer::slotExternalUpdateOnionSkins()
{
if (!onionSkinEnabled()) return;
const QRect dirtyRect =
KisOnionSkinCompositor::instance()->calculateFullExtent(m_d->paintDevice);
setDirty(dirtyRect);
}
KisKeyframeChannel *KisPaintLayer::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::Content.id()) {
m_d->contentChannel = m_d->paintDevice->createKeyframeChannel(KisKeyframeChannel::Content);
m_d->contentChannel->setOnionSkinsEnabled(onionSkinEnabled());
+ enableAnimation();
return m_d->contentChannel;
}
return KisLayer::requestKeyframeChannel(id);
}
KisPaintDeviceList KisPaintLayer::getLodCapableDevices() const
{
KisPaintDeviceList list = KisLayer::getLodCapableDevices();
KisPaintDeviceSP onionSkinsDevice = m_d->onionSkinCache.lodCapableDevice();
if (onionSkinsDevice) {
list << onionSkinsDevice;
}
return list;
}
diff --git a/libs/image/kis_paint_layer.h b/libs/image/kis_paint_layer.h
index 04f3612d1f..cd1f38679b 100644
--- a/libs/image/kis_paint_layer.h
+++ b/libs/image/kis_paint_layer.h
@@ -1,180 +1,177 @@
/*
* Copyright (c) 2005 C. Boemann <cbo@boemann.dk>
* 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_PAINT_LAYER_H_
#define KIS_PAINT_LAYER_H_
#include "kis_types.h"
#include "kis_layer.h"
#include "kis_indirect_painting_support.h"
#include <QBitArray>
class KoColorSpace;
/**
* This layer is of a type that can be d on. A paint layer can
* have any number of effect masks, a transparency mask, a local
* selection and a protection mask.
*
* The protection mask can be read/write, read-only or write-only.
* The transparency mask has two rendering forms: as a selection mask
* and by changing the transparency of the paint layer's pixels.
*/
class KRITAIMAGE_EXPORT KisPaintLayer : public KisLayer, public KisIndirectPaintingSupport
{
Q_OBJECT
public:
/**
* Construct a paint layer with the given parameters. The default bounds of the paintdevice are overwriten.
* @param image this layer belongs to, or null, if it shouldn't belong to any image
* @param name of the layer
* @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8
* @param dev is the paint device, that should be used
*/
KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, KisPaintDeviceSP dev);
/**
* Construct a paint layer with the given parameters
* @param image this layer belongs to. it must not be null and it must have a valid color space.
* @param name of the layer
* @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8
*/
KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity);
/**
* Construct a paint layer with the given parameters
* @param image this layer belongs to, or null, if it shouldn't belong to any image. image must not be null, if colorSpace is null
* @param name of the layer
* @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8
* @param colorSpace is the color space, that should be used to construct the paint device. it can be null, if the image is valid.
*/
KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, const KoColorSpace * colorSpace);
/**
* Copy Constructor
*/
KisPaintLayer(const KisPaintLayer& rhs);
virtual ~KisPaintLayer();
KisNodeSP clone() const override {
return KisNodeSP(new KisPaintLayer(*this));
}
bool allowAsChild(KisNodeSP) const override;
const KoColorSpace * colorSpace() const override;
bool needProjection() const override;
- using KisLayer::setDirty;
- void setDirty(const QRect & rect) override;
-
QIcon icon() const override;
void setImage(KisImageWSP image) override;
KisBaseNode::PropertyList sectionModelProperties() const override;
void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override;
public:
QRect extent() const override;
QRect exactBounds() const override;
bool accept(KisNodeVisitor &v) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
/**
* set the channelflags for locking certain channels (used by painting tools)
* for this layer to the specified bit array.
* The bit array must have exactly the same number of channels as
* the colorspace this layer is in, or be empty, in which case all
* channels are active.
*/
void setChannelLockFlags(const QBitArray& channelFlags);
/**
* Return a bit array where each bit indicates whether a
* particular channel is locked or not (used by painting tools).
* If the channelflags bit array is empty, all channels are active.
*/
const QBitArray& channelLockFlags() const;
/**
* Returns the paintDevice that accompanies this layer
*/
KisPaintDeviceSP paintDevice() const override;
/**
* Returns the original pixels before masks have been applied.
*/
KisPaintDeviceSP original() const override;
/**
* @returns true when painting should not affect the alpha channel
*/
bool alphaLocked() const;
/**
* @param l if true, the alpha channel will be protected from modification
*/
void setAlphaLocked(bool lock);
/**
* @return true if onion skins should be rendered on this layer
*/
bool onionSkinEnabled() const;
/**
* @param state whether onion skins should be rendered
*/
void setOnionSkinEnabled(bool state);
KisPaintDeviceList getLodCapableDevices() const override;
public Q_SLOTS:
void slotExternalUpdateOnionSkins();
public:
// KisIndirectPaintingSupport
KisLayer* layer() {
return this;
}
protected:
// override from KisLayer
void copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const override;
KisKeyframeChannel *requestKeyframeChannel(const QString &id) override;
private:
void init(KisPaintDeviceSP paintDevice, const QBitArray &paintChannelFlags = QBitArray());
struct Private;
Private * const m_d;
};
typedef KisSharedPtr<KisPaintLayer> KisPaintLayerSP;
#endif // KIS_PAINT_LAYER_H_
diff --git a/libs/image/kis_projection_updates_filter.cpp b/libs/image/kis_projection_updates_filter.cpp
index 5fb9b16040..e1493ec367 100644
--- a/libs/image/kis_projection_updates_filter.cpp
+++ b/libs/image/kis_projection_updates_filter.cpp
@@ -1,35 +1,36 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_projection_updates_filter.h"
#include <QtGlobal>
#include <QRect>
KisProjectionUpdatesFilter::~KisProjectionUpdatesFilter()
{
}
-bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect)
+bool KisDropAllProjectionUpdatesFilter::filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache)
{
Q_UNUSED(image);
Q_UNUSED(node);
Q_UNUSED(rect);
+ Q_UNUSED(resetAnimationCache);
return true;
}
diff --git a/libs/image/kis_projection_updates_filter.h b/libs/image/kis_projection_updates_filter.h
index 3e3a0206df..8cb9f2240d 100644
--- a/libs/image/kis_projection_updates_filter.h
+++ b/libs/image/kis_projection_updates_filter.h
@@ -1,50 +1,50 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_PROJECTION_UPDATES_FILTER_H
#define __KIS_PROJECTION_UPDATES_FILTER_H
#include <QSharedPointer>
class KisImage;
class KisNode;
class QRect;
class KisProjectionUpdatesFilter
{
public:
virtual ~KisProjectionUpdatesFilter();
/**
* \return true if an update should be dropped by the image
*/
- virtual bool filter(KisImage *image, KisNode *node, const QRect& rect) = 0;
+ virtual bool filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache) = 0;
};
/**
* A dummy filter implementation that eats all the updates
*/
class KisDropAllProjectionUpdatesFilter : public KisProjectionUpdatesFilter
{
public:
- bool filter(KisImage *image, KisNode *node, const QRect& rect);
+ bool filter(KisImage *image, KisNode *node, const QRect& rect, bool resetAnimationCache);
};
#endif /* __KIS_PROJECTION_UPDATES_FILTER_H */
diff --git a/libs/image/kis_properties_configuration.h b/libs/image/kis_properties_configuration.h
index 0b8ba8169e..c7e6004dc3 100644
--- a/libs/image/kis_properties_configuration.h
+++ b/libs/image/kis_properties_configuration.h
@@ -1,164 +1,164 @@
/*
* 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();
virtual ~KisPropertiesConfiguration();
/**
* Deep copy the properties configFile
*/
KisPropertiesConfiguration(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
*/
virtual bool fromXML(const QString& xml, bool clear = true);
/**
* 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
*/
virtual void fromXML(const QDomElement&);
/**
* Create a serialized version of this properties config
* This function use the "Legacy" style XML of the 1.x .kra file format.
*/
virtual void toXML(QDomDocument&, QDomElement&) const;
/**
* Create a serialized version of this properties config
* This function use the "Legacy" style XML of the 1.x .kra file format.
*/
virtual QString toXML() const;
/**
* @return true if the map contains a property with the specified name
*/
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;
}
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;
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
+ /// Marks a property that should not be saved by toXML
void setPropertyNotSaved(const QString & name);
void removeProperty(const QString & name);
public:
void dump() const;
private:
struct Private;
Private* const d;
};
class KRITAIMAGE_EXPORT KisPropertiesConfigurationFactory : public KisSerializableConfigurationFactory
{
public:
KisPropertiesConfigurationFactory();
virtual ~KisPropertiesConfigurationFactory();
virtual KisSerializableConfigurationSP createDefault();
virtual KisSerializableConfigurationSP create(const QDomElement& e);
private:
struct Private;
Private* const d;
};
#endif
diff --git a/libs/image/kis_repeat_iterators_pixel.h b/libs/image/kis_repeat_iterators_pixel.h
index 8d21ba005d..4bfa09643c 100644
--- a/libs/image/kis_repeat_iterators_pixel.h
+++ b/libs/image/kis_repeat_iterators_pixel.h
@@ -1,266 +1,268 @@
/*
* Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_REPEAT_ITERATORS_PIXEL_H_
#define _KIS_REPEAT_ITERATORS_PIXEL_H_
#include <QRect>
#include "kis_shared.h"
#include "tiles3/kis_hline_iterator.h"
#include "tiles3/kis_vline_iterator.h"
template<class T>
class KisRepeatHLineIteratorPixelBase;
template<class T>
class KisRepeatVLineIteratorPixelBase;
/**
* This iterator is an iterator that will "artificially" extend the paint device with the
* value of the border when trying to access values outside the range of data.
*/
template<class T>
class KisRepeatLineIteratorPixelBase : public KisShared
{
Q_DISABLE_COPY(KisRepeatLineIteratorPixelBase)
public:
friend class KisRepeatHLineIteratorPixelBase<T>;
friend class KisRepeatVLineIteratorPixelBase<T>;
/**
* @param rc indicates the rectangle that truly contains data
*/
- inline KisRepeatLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 offsetx, qint32 offsety, const QRect& _rc);
+ inline KisRepeatLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 offsetx, qint32 offsety, const QRect& _rc, KisIteratorCompleteListener *completeListener);
virtual inline ~KisRepeatLineIteratorPixelBase();
public:
inline qint32 x() const {
return m_realX;
}
inline qint32 y() const {
return m_realY;
}
inline const quint8 * oldRawData() const {
return m_iterator->oldRawData();
}
private:
KisDataManager* m_dm;
qint32 m_realX, m_realY;
qint32 m_offsetX, m_offsetY;
QRect m_dataRect;
T* m_iterator;
+ KisIteratorCompleteListener *m_completeListener;
};
/**
* This iterator is an iterator that will "artificially" extend the paint device with the
* value of the border when trying to access values outside the range of data.
*/
template<class T>
class KisRepeatHLineIteratorPixelBase : public KisRepeatLineIteratorPixelBase<T>
{
public:
/**
* @param rc indicates the rectangle that trully contains data
*/
- inline KisRepeatHLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 w, qint32 offsetx, qint32 offsety, const QRect& _rc);
+ inline KisRepeatHLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 w, qint32 offsetx, qint32 offsety, const QRect& _rc, KisIteratorCompleteListener *completeListener);
virtual inline ~KisRepeatHLineIteratorPixelBase();
inline bool nextPixel();
/**
* Reach next row.
*/
inline void nextRow();
private:
void createIterator();
private:
qint32 m_startX;
qint32 m_startIteratorX;
qint32 m_width;
};
/**
* This iterator is an iterator that will "artificially" extend the paint device with the
* value of the border when trying to access values outside the range of data.
*/
template<class T>
class KisRepeatVLineIteratorPixelBase : public KisRepeatLineIteratorPixelBase<T>
{
public:
/**
* @param rc indicates the rectangle that trully contains data
*/
- inline KisRepeatVLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 h, qint32 offsetx, qint32 offsety, const QRect& _rc);
+ inline KisRepeatVLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 h, qint32 offsetx, qint32 offsety, const QRect& _rc, KisIteratorCompleteListener *completeListener);
virtual inline ~KisRepeatVLineIteratorPixelBase();
inline KisRepeatVLineIteratorPixelBase<T> & operator ++();
inline bool nextPixel();
/**
* Reach next row.
*/
inline void nextColumn();
private:
void createIterator();
private:
qint32 m_startY;
qint32 m_startIteratorY;
qint32 m_height;
};
//------------------------ Implementations ------------------------//
//---------------- KisRepeatLineIteratorPixelBase -----------------//
template<class T>
-KisRepeatLineIteratorPixelBase<T>::KisRepeatLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 offsetx, qint32 offsety, const QRect& _rc) :
+KisRepeatLineIteratorPixelBase<T>::KisRepeatLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 offsetx, qint32 offsety, const QRect& _rc, KisIteratorCompleteListener *completeListener) :
m_dm(dm),
m_realX(x), m_realY(y),
m_offsetX(offsetx), m_offsetY(offsety),
m_dataRect(_rc),
- m_iterator(0)
+ m_iterator(0),
+ m_completeListener(completeListener)
{
}
template<class T>
KisRepeatLineIteratorPixelBase<T>::~KisRepeatLineIteratorPixelBase()
{
delete m_iterator;
}
//---------------- KisRepeatHLineIteratorPixelBase ----------------//
template<class T>
-KisRepeatHLineIteratorPixelBase<T>::KisRepeatHLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 w, qint32 offsetx, qint32 offsety, const QRect& _rc)
- : KisRepeatLineIteratorPixelBase<T>(dm, x, y, offsetx, offsety , _rc),
+KisRepeatHLineIteratorPixelBase<T>::KisRepeatHLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 w, qint32 offsetx, qint32 offsety, const QRect& _rc, KisIteratorCompleteListener *completeListener)
+ : KisRepeatLineIteratorPixelBase<T>(dm, x, y, offsetx, offsety , _rc, completeListener),
m_startX(x), m_startIteratorX(x),
m_width(w)
{
// Compute the startx value of the iterator
if (m_startIteratorX < _rc.left()) {
m_startIteratorX = _rc.left();
}
createIterator();
}
template<class T>
KisRepeatHLineIteratorPixelBase<T>::~KisRepeatHLineIteratorPixelBase()
{
}
template<class T>
inline bool KisRepeatHLineIteratorPixelBase<T>::nextPixel()
{
Q_ASSERT(this->m_iterator);
if (this->m_realX >= this->m_dataRect.x() && this->m_realX < this->m_dataRect.x() + this->m_dataRect.width() - 1) {
this->m_iterator->nextPixel();
}
++this->m_realX;
return (this->m_realX < m_startX + m_width);
}
template<class T>
inline void KisRepeatHLineIteratorPixelBase<T>::nextRow()
{
if (this->m_realY >= this->m_dataRect.y() && this->m_realY < this->m_dataRect.y() + this->m_dataRect.height() - 1) {
this->m_iterator->nextRow();
} else {
createIterator();
}
this->m_realX = this->m_startX;
++this->m_realY;
}
template<class T>
void KisRepeatHLineIteratorPixelBase<T>::createIterator()
{
// Cleanup
delete this->m_iterator;
qint32 startY = this->m_realY;
if (startY < this->m_dataRect.y()) {
startY = this->m_dataRect.top();
}
if (startY > (this->m_dataRect.y() + this->m_dataRect.height() - 1)) {
startY = (this->m_dataRect.y() + this->m_dataRect.height() - 1);
}
int width = this->m_dataRect.x() + this->m_dataRect.width() - this->m_startIteratorX;
- this->m_iterator = new T(this->m_dm, this->m_startIteratorX, startY, width, this->m_offsetX, this->m_offsetY, false);
+ this->m_iterator = new T(this->m_dm, this->m_startIteratorX, startY, width, this->m_offsetX, this->m_offsetY, false, this->m_completeListener);
this->m_realX = this->m_startX;
}
//---------------- KisRepeatVLineIteratorPixelBase ----------------//
template<class T>
-KisRepeatVLineIteratorPixelBase<T>::KisRepeatVLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 h, qint32 offsetx, qint32 offsety, const QRect& _rc)
- : KisRepeatLineIteratorPixelBase<T>(dm, x, y, offsetx, offsety , _rc),
+KisRepeatVLineIteratorPixelBase<T>::KisRepeatVLineIteratorPixelBase(KisDataManager *dm, qint32 x, qint32 y, qint32 h, qint32 offsetx, qint32 offsety, const QRect& _rc, KisIteratorCompleteListener *completeListener)
+ : KisRepeatLineIteratorPixelBase<T>(dm, x, y, offsetx, offsety , _rc, completeListener),
m_startY(y), m_startIteratorY(y),
m_height(h)
{
// Compute the startx value of the iterator
if (m_startIteratorY < _rc.top()) {
m_startIteratorY = _rc.top();
}
createIterator();
}
template<class T>
KisRepeatVLineIteratorPixelBase<T>::~KisRepeatVLineIteratorPixelBase()
{
}
template<class T>
inline bool KisRepeatVLineIteratorPixelBase<T>::nextPixel()
{
Q_ASSERT(this->m_iterator);
if (this->m_realY >= this->m_dataRect.y() && this->m_realY < this->m_dataRect.y() + this->m_dataRect.height() - 1) {
this->m_iterator->nextPixel();
}
++this->m_realY;
return (this->m_realY < m_startY + m_height);
}
template<class T>
inline void KisRepeatVLineIteratorPixelBase<T>::nextColumn()
{
if (this->m_realX >= this->m_dataRect.x() && this->m_realX < this->m_dataRect.x() + this->m_dataRect.width() - 1) {
this->m_iterator->nextColumn();
} else {
createIterator();
}
this->m_realY = this->m_startY;
++this->m_realX;
}
template<class T>
void KisRepeatVLineIteratorPixelBase<T>::createIterator()
{
// Cleanup
delete this->m_iterator;
qint32 startX = this->m_realX;
if (startX < this->m_dataRect.x()) {
startX = this->m_dataRect.x();
}
if (startX > (this->m_dataRect.x() + this->m_dataRect.width() - 1)) {
startX = (this->m_dataRect.x() + this->m_dataRect.width() - 1);
}
int height = this->m_dataRect.y() + this->m_dataRect.height() - this->m_startIteratorY;
- this->m_iterator = new T(this->m_dm, startX, this->m_startIteratorY, height, this->m_offsetX, this->m_offsetY, false);
+ this->m_iterator = new T(this->m_dm, startX, this->m_startIteratorY, height, this->m_offsetX, this->m_offsetY, false, this->m_completeListener);
this->m_realY = this->m_startY;
}
#endif
diff --git a/libs/image/kis_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp
index 80cd1d51f4..4e327bc50e 100644
--- a/libs/image/kis_selection_based_layer.cpp
+++ b/libs/image/kis_selection_based_layer.cpp
@@ -1,320 +1,315 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* 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.
*/
#include "kis_selection_based_layer.h"
#include <klocalizedstring.h>
#include "kis_debug.h"
#include <KoCompositeOpRegistry.h>
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_default_bounds.h"
#include "kis_selection.h"
#include "kis_pixel_selection.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "filter/kis_filter.h"
#include "kis_raster_keyframe_channel.h"
struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private
{
public:
Private() : useSelectionInProjection(true) {}
KisSelectionSP selection;
KisPaintDeviceSP paintDevice;
bool useSelectionInProjection;
};
KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image,
const QString &name,
KisSelectionSP selection,
KisFilterConfigurationSP filterConfig,
bool useGeneratorRegistry)
: KisLayer(image.data(), name, OPACITY_OPAQUE_U8),
KisNodeFilterInterface(filterConfig, useGeneratorRegistry),
m_d(new Private())
{
if (!selection)
initSelection();
else
setInternalSelection(selection);
KisImageSP imageSP = image.toStrongRef();
if (!imageSP) {
return;
}
m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(this, imageSP->colorSpace(), KisDefaultBoundsSP(new KisDefaultBounds(image))));
connect(imageSP.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged()));
}
KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs)
: KisLayer(rhs)
, KisIndirectPaintingSupport()
, KisNodeFilterInterface(rhs)
, m_d(new Private())
{
setInternalSelection(rhs.m_d->selection);
m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data());
}
KisSelectionBasedLayer::~KisSelectionBasedLayer()
{
delete m_d;
}
void KisSelectionBasedLayer::initSelection()
{
m_d->selection = KisSelectionSP(new KisSelection(KisDefaultBoundsSP(new KisDefaultBounds(image()))));
m_d->selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, m_d->selection->pixelSelection()->colorSpace()));
m_d->selection->setParentNode(this);
m_d->selection->updateProjection();
}
void KisSelectionBasedLayer::slotImageSizeChanged()
{
if (m_d->selection) {
/**
* Make sure exactBounds() of the selection got recalculated after
* the image changed
*/
m_d->selection->pixelSelection()->setDirty();
setDirty();
}
}
void KisSelectionBasedLayer::setImage(KisImageWSP image)
{
m_d->paintDevice->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image)));
KisLayer::setImage(image);
connect(image.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged()));
}
bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
KisPaintDeviceSP KisSelectionBasedLayer::original() const
{
return m_d->paintDevice;
}
KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const
{
return m_d->selection->pixelSelection();
}
bool KisSelectionBasedLayer::needProjection() const
{
return m_d->selection;
}
void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const
{
m_d->useSelectionInProjection = value;
}
KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const
{
if (!m_d->selection) return KisSelectionSP();
m_d->selection->updateProjection(rect);
KisSelectionSP tempSelection = m_d->selection;
KisIndirectPaintingSupport::ReadLocker l(this);
if (hasTemporaryTarget()) {
/**
* Cloning a selection with COW
* FIXME: check whether it's faster than usual bitBlt'ing
*/
tempSelection = new KisSelection(*tempSelection);
KisPainter gc2(tempSelection->pixelSelection());
setupTemporaryPainter(&gc2);
gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect);
}
return tempSelection;
}
void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const
{
KisSelectionSP tempSelection;
if (m_d->useSelectionInProjection) {
tempSelection = fetchComposedInternalSelection(rect);
}
projection->clear(rect);
KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection);
}
QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const
{
return m_d->selection ?
rect & m_d->selection->selectedRect() :
rect;
}
QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
void KisSelectionBasedLayer::resetCache()
{
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
if (!m_d->paintDevice || *m_d->paintDevice->colorSpace() != *imageSP->colorSpace()) {
m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(KisNodeWSP(this), imageSP->colorSpace(), new KisDefaultBounds(image())));
} else {
m_d->paintDevice->clear();
}
}
KisSelectionSP KisSelectionBasedLayer::internalSelection() const
{
return m_d->selection;
}
void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection)
{
if (selection) {
m_d->selection = new KisSelection(*selection.data());
m_d->selection->setParentNode(this);
m_d->selection->updateProjection();
} else {
m_d->selection = 0;
}
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
if (selection->pixelSelection()->defaultBounds()->bounds() != imageSP->bounds()) {
qWarning() << "WARNING: KisSelectionBasedLayer::setInternalSelection"
<< "New selection has suspicious default bounds";
qWarning() << "WARNING:" << ppVar(selection->pixelSelection()->defaultBounds()->bounds());
qWarning() << "WARNING:" << ppVar(imageSP->bounds());
}
}
qint32 KisSelectionBasedLayer::x() const
{
return m_d->selection ? m_d->selection->x() : 0;
}
qint32 KisSelectionBasedLayer::y() const
{
return m_d->selection ? m_d->selection->y() : 0;
}
void KisSelectionBasedLayer::setX(qint32 x)
{
if (m_d->selection) {
m_d->selection->setX(x);
}
}
void KisSelectionBasedLayer::setY(qint32 y)
{
if (m_d->selection) {
m_d->selection->setY(y);
}
}
-void KisSelectionBasedLayer::setDirty(const QRect & rect)
-{
- KisLayer::setDirty(rect);
-}
-
KisKeyframeChannel *KisSelectionBasedLayer::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::Content.id()) {
KisRasterKeyframeChannel *contentChannel = m_d->selection->pixelSelection()->createKeyframeChannel(KisKeyframeChannel::Content);
contentChannel->setFilenameSuffix(".pixelselection");
return contentChannel;
}
return KisLayer::requestKeyframeChannel(id);
}
void KisSelectionBasedLayer::setDirty()
{
Q_ASSERT(image());
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return;
}
setDirty(imageSP->bounds());
}
QRect KisSelectionBasedLayer::extent() const
{
Q_ASSERT(image());
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return QRect();
}
return m_d->selection ?
m_d->selection->selectedRect() : imageSP->bounds();
}
QRect KisSelectionBasedLayer::exactBounds() const
{
Q_ASSERT(image());
KisImageSP imageSP = image().toStrongRef();
if (!imageSP) {
return QRect();
}
return m_d->selection ?
m_d->selection->selectedExactRect() : imageSP->bounds();
}
QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h)
{
KisSelectionSP originalSelection = internalSelection();
KisPaintDeviceSP originalDevice = original();
return originalDevice && originalSelection ?
originalDevice->createThumbnail(w, h, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags()) :
QImage();
}
diff --git a/libs/image/kis_selection_based_layer.h b/libs/image/kis_selection_based_layer.h
index 8362c5f5cf..e6f455865d 100644
--- a/libs/image/kis_selection_based_layer.h
+++ b/libs/image/kis_selection_based_layer.h
@@ -1,211 +1,210 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
* (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_SELECTION_BASED_LAYER_H_
#define KIS_SELECTION_BASED_LAYER_H_
#include <QObject>
#include "kis_types.h"
#include "kis_layer.h"
#include "kis_indirect_painting_support.h"
#include <kritaimage_export.h>
#include "kis_node_filter_interface.h"
class KisFilterConfiguration;
/**
* @class KisSelectionBasedLayer describes base behaviour for
* selection base classes like KisAdjustmentLayer and KisGeneratorLayer.
* These classes should have a persistent selection that controls
* the area where filter/generators are applied. The area outside
* this selection is not affected by the layer
*/
class KRITAIMAGE_EXPORT KisSelectionBasedLayer : public KisLayer, public KisIndirectPaintingSupport, public KisNodeFilterInterface
{
Q_OBJECT
public:
/**
* creates a new layer with the given selection.
* Note that the selection will be _copied_ (with COW, though).
* @param image the image to set this layer to
* @param name name of the layer
* @param selection is a mask used by the layer to know
* where to apply the filter/generator.
*/
KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig, bool useGeneratorRegistry = false);
KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs);
virtual ~KisSelectionBasedLayer();
/**
* tells whether the @node can be a child of this layer
* @param node to be connected node
* @return tells if to be connected is a child of KisMask
*/
bool allowAsChild(KisNodeSP node) const override;
void setImage(KisImageWSP image) override;
KisPaintDeviceSP original() const override;
KisPaintDeviceSP paintDevice() const override;
bool needProjection() const override;
/**
* resets cached projection of lower layer to a new device
* @return void
*/
void resetCache();
/**
* for KisLayer::setDirty(const QRegion&)
*/
using KisLayer::setDirty;
/**
* Mark a layer as dirty. We can't use KisLayer's one
* as our extent() function doesn't fit for this
*/
void setDirty() override;
- void setDirty(const QRect & rect) override;
public:
/**
* Returns the selection of the layer
*
* Do not mix it with selection() which returns
* the currently active selection of the image
*/
KisSelectionSP internalSelection() const;
/**
* sets the selection of this layer to a copy of
* selection
* @param selection the selection to set
* @return void
*/
void setInternalSelection(KisSelectionSP selection);
/**
* When painted in indirect painting mode, the internal selection
* might not contain actual selection, because a part of it is
* stored on an indirect painting device. This method returns the
* merged copy of the real selection. The area in \p rect only is
* guaranteed to be prepared. The content of the rest of the
* selection is undefined.
*/
KisSelectionSP fetchComposedInternalSelection(const QRect &rect) const;
/**
* gets this layer's x coordinate, taking selection into account
* @return x-coordinate value
*/
qint32 x() const override;
/**
* gets this layer's y coordinate, taking selection into account
* @return y-coordinate value
*/
qint32 y() const override;
/**
* sets this layer's y coordinate, taking selection into account
* @param x x coordinate
*/
void setX(qint32 x) override;
/**
* sets this layer's y coordinate, taking selection into account
* @param y y coordinate
*/
void setY(qint32 y) override;
public:
/**
* gets an approximation of where the bounds on actual data
* are in this layer, taking selection into account
*/
QRect extent() const override;
/**
* returns the exact bounds of where the actual data resides
* in this layer, taking selection into account
*/
QRect exactBounds() const override;
/**
* copies the image and reformats it to thumbnail size
* and returns the new thumbnail image.
* @param w width of the thumbnail to create
* @param h height of the thumbnail to create
* @return the thumbnail image created.
*/
QImage createThumbnail(qint32 w, qint32 h) override;
protected:
// override from KisLayer
void copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const override;
// override from KisNode
QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override;
protected:
void initSelection();
QRect cropChangeRectBySelection(const QRect &rect) const;
/**
* Sets if the selection should be used in
* copyOriginalToProjection() method.
*
* Default value is 'true'. The descendants should override it to
* get desired behaviour.
*
* Must be called only once in the child's constructor
*/
void setUseSelectionInProjection(bool value) const;
KisKeyframeChannel *requestKeyframeChannel(const QString &id) override;
public Q_SLOTS:
void slotImageSizeChanged();
/**
* gets this layer. Overriddes function in
* KisIndirectPaintingSupport
* @return this AdjustmentLayer
*/
KisLayer* layer() {
return this;
}
private:
struct Private;
Private * const m_d;
};
#endif /* KIS_SELECTION_BASED_LAYER_H_ */
diff --git a/libs/image/kis_selection_filters.h b/libs/image/kis_selection_filters.h
index 1f39fa776b..1a72adf1cf 100644
--- a/libs/image/kis_selection_filters.h
+++ b/libs/image/kis_selection_filters.h
@@ -1,154 +1,155 @@
/*
* 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.
*/
#ifndef KIS_SELECTION_FILTERS_H
#define KIS_SELECTION_FILTERS_H
#include "kis_types.h"
#include "kritaimage_export.h"
#include <QRect>
#include <QString>
class KUndo2MagicString;
class KRITAIMAGE_EXPORT KisSelectionFilter
{
public:
virtual ~KisSelectionFilter();
virtual void process(KisPixelSelectionSP pixelSelection,
const QRect &rect) = 0;
virtual KUndo2MagicString name();
virtual QRect changeRect(const QRect &rect);
protected:
void computeBorder(qint32 *circ, qint32 xradius, qint32 yradius);
void rotatePointers(quint8 **p, quint32 n);
void computeTransition(quint8* transition, quint8** buf, qint32 width);
};
class KRITAIMAGE_EXPORT KisErodeSelectionFilter : public KisSelectionFilter
{
public:
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
};
class KRITAIMAGE_EXPORT KisDilateSelectionFilter : public KisSelectionFilter
{
public:
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
};
class KRITAIMAGE_EXPORT KisBorderSelectionFilter : public KisSelectionFilter
{
public:
KisBorderSelectionFilter(qint32 xRadius, qint32 yRadius);
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
private:
qint32 m_xRadius;
qint32 m_yRadius;
};
class KRITAIMAGE_EXPORT KisFeatherSelectionFilter : public KisSelectionFilter
{
public:
KisFeatherSelectionFilter(qint32 radius);
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
private:
qint32 m_radius;
};
class KRITAIMAGE_EXPORT KisGrowSelectionFilter : public KisSelectionFilter
{
public:
KisGrowSelectionFilter(qint32 xRadius, qint32 yRadius);
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
private:
qint32 m_xRadius;
qint32 m_yRadius;
};
class KRITAIMAGE_EXPORT KisShrinkSelectionFilter : public KisSelectionFilter
{
public:
KisShrinkSelectionFilter(qint32 xRadius, qint32 yRadius, bool edgeLock);
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
private:
qint32 m_xRadius;
qint32 m_yRadius;
qint32 m_edgeLock;
};
class KRITAIMAGE_EXPORT KisSmoothSelectionFilter : public KisSelectionFilter
{
public:
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
};
class KRITAIMAGE_EXPORT KisInvertSelectionFilter : public KisSelectionFilter
{
+public:
KUndo2MagicString name();
QRect changeRect(const QRect &rect);
void process(KisPixelSelectionSP pixelSelection, const QRect &rect);
};
#endif // KIS_SELECTION_FILTERS_H
diff --git a/libs/image/kis_signal_compressor.h b/libs/image/kis_signal_compressor.h
deleted file mode 100644
index cdef0754da..0000000000
--- a/libs/image/kis_signal_compressor.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef __KIS_SIGNAL_COMPRESSOR_H
-#define __KIS_SIGNAL_COMPRESSOR_H
-
-#include <QTimer>
-#include "kritaimage_export.h"
-
-class QTimer;
-
-/**
- * Sets a timer to delay or throttle activation of a Qt slot. One example of
- * where this is used is to limit the amount of expensive redraw activity on the
- * canvas.
- *
- * There are three behaviors to choose from.
- *
- * POSTPONE resets the timer after each call. Therefore if the calls are made
- * quickly enough, the timer will never be activated.
- *
- * FIRST_ACTIVE_POSTPONE_NEXT emits the first signal and postpones all
- * the other actions the other action like in POSTPONE. This mode is
- * used e.g. in move/remove layer functionality. If you remove a
- * single layer, you'll see the result immediately. But if you want to
- * remove multiple layers, you should wait until all the actions are
- * finished.
- *
- * FIRST_ACTIVE emits the timeout() event immediately and sets a timer of
- * duration \p delay. If the compressor is triggered during this time, it will
- * wait until the end of the delay period to fire the signal. Further events are
- * ignored until the timer elapses. Think of it as a queue with size 1, and
- * where the leading element is popped every \p delay ms.
- *
- * FIRST_INACTIVE emits the timeout() event at the end of a timer of duration \p
- * delay ms. The compressor becomes inactive and all events are ignored until
- * the timer has elapsed.
- *
- */
-class KRITAIMAGE_EXPORT KisSignalCompressor : public QObject
-{
- Q_OBJECT
-
-public:
- enum Mode {
- POSTPONE, /* Calling start() resets the timer to \p delay ms */
- FIRST_ACTIVE_POSTPONE_NEXT, /* emits the first signal and postpones all the next ones */
- FIRST_ACTIVE, /* Emit timeout() signal immediately. Throttle further timeout() to rate of one per \p delay ms */
- FIRST_INACTIVE, /* Set a timer \p delay ms, emit timeout() when it elapses. Ignore all events meanwhile. */
- UNDEFINED /* KisSignalCompressor is created without an explicit mode */
- };
-
-public:
- KisSignalCompressor();
- KisSignalCompressor(int delay, Mode mode, QObject *parent = 0);
- bool isActive() const;
- void setMode(Mode mode);
- void setDelay(int delay);
-
-public Q_SLOTS:
- void start();
- void stop();
-
-private Q_SLOTS:
- void slotTimerExpired();
-
-Q_SIGNALS:
- void timeout();
-
-private:
- QTimer *m_timer;
- Mode m_mode;
- bool m_gotSignals;
-};
-
-#endif /* __KIS_SIGNAL_COMPRESSOR_H */
diff --git a/libs/image/kis_signal_compressor_with_param.h b/libs/image/kis_signal_compressor_with_param.h
deleted file mode 100644
index 6b01235345..0000000000
--- a/libs/image/kis_signal_compressor_with_param.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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_SIGNAL_COMPRESSOR_WITH_PARAM_H
-#define __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H
-
-#include <kis_signal_compressor.h>
-#include <functional>
-
-
-/**
- * A special class that converts a Qt signal into a std::function call.
- *
- * Example:
- *
- * std::function<void ()> destinationFunctionCall(std::bind(someNiceFunc, firstParam, secondParam));
- * SignalToFunctionProxy proxy(destinationFunctionCall);
- * connect(srcObject, SIGNAL(sigSomethingChanged()), &proxy, SLOT(start()));
- *
- * Now every time sigSomethingChanged() is emitted, someNiceFunc is
- * called. std::bind allows us to call any method of any class without
- * changing signature of the class or creating special wrappers.
- */
-class KRITAIMAGE_EXPORT SignalToFunctionProxy : public QObject
-{
- Q_OBJECT
-public:
- using TrivialFunction = std::function<void ()>;
-
-public:
- SignalToFunctionProxy(TrivialFunction function)
- : m_function(function)
- {
- }
-
-public Q_SLOTS:
- void start() {
- m_function();
- }
-
-private:
- TrivialFunction m_function;
-};
-
-
-/**
- * A special class for deferring and comressing events with one
- * parameter of type T. This works like KisSignalCompressor but can
- * handle events with one parameter. Due to limitation of the Qt this
- * doesn't allow signal/slots, so it uses std::function instead.
- *
- * In the end (after a timeout) the latest param value is returned to
- * the callback.
- *
- * Usage:
- *
- * \code{.cpp}
- *
- * using namespace std::placeholders; // For _1 placeholder
- *
- * // prepare the callback function
- * std::function<void (qreal)> callback(
- * std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1));
- *
- * // Create the compressor object
- * KisSignalCompressorWithParam<qreal> compressor(40, callback);
- *
- * // When event comes:
- * compressor.start(0.123456);
- *
- * \endcode
- */
-
-template <typename T>
-class KisSignalCompressorWithParam
-{
-public:
- using CallbackFunction = std::function<void (T)>;
-
-public:
-KisSignalCompressorWithParam(int delay, CallbackFunction function, KisSignalCompressor::Mode mode = KisSignalCompressor::FIRST_ACTIVE)
- : m_compressor(delay, mode),
- m_function(function)
- {
- std::function<void ()> callback(
- std::bind(&KisSignalCompressorWithParam<T>::fakeSlotTimeout, this));
- m_signalProxy.reset(new SignalToFunctionProxy(callback));
-
- m_compressor.connect(&m_compressor, SIGNAL(timeout()), m_signalProxy.data(), SLOT(start()));
- }
-
- ~KisSignalCompressorWithParam()
- {
- }
-
- void start(T param) {
- m_currentParamValue = param;
- m_compressor.start();
- }
-
- void stop() {
- m_compressor.stop();
- }
-
- bool isActive() const {
- return m_compressor.isActive();
- }
-
- void setDelay(int value) {
- m_compressor.setDelay(value);
- }
-
-private:
- void fakeSlotTimeout() {
- m_function(m_currentParamValue);
- }
-
-private:
- KisSignalCompressor m_compressor;
- CallbackFunction m_function;
- QScopedPointer<SignalToFunctionProxy> m_signalProxy;
- T m_currentParamValue;
-};
-
-#endif /* __KIS_SIGNAL_COMPRESSOR_WITH_PARAM_H */
diff --git a/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp b/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp
index cabc0991d9..3f890c5dc5 100644
--- a/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp
+++ b/libs/image/kis_suspend_projection_updates_stroke_strategy.cpp
@@ -1,302 +1,303 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_suspend_projection_updates_stroke_strategy.h"
#include <kis_image.h>
#include <krita_utils.h>
#include <kis_projection_updates_filter.h>
inline uint qHash(const QRect &rc) {
return rc.x() +
(rc.y() << 16) +
(rc.width() << 8) +
(rc.height() << 24);
}
struct KisSuspendProjectionUpdatesStrokeStrategy::Private
{
KisImageWSP image;
bool suspend;
class SuspendLod0Updates : public KisProjectionUpdatesFilter
{
- typedef QHash<KisNodeSP, QVector<QRect> > RectsHash;
- public:
struct Request {
- Request() {}
- Request(KisNodeSP _node, QRect _rect) : node(_node), rect(_rect) {}
- KisNodeSP node;
+ Request() : resetAnimationCache(false) {}
+ Request(const QRect &_rect, bool _resetAnimationCache)
+ : rect(_rect), resetAnimationCache(_resetAnimationCache)
+ {
+ }
+
QRect rect;
+ bool resetAnimationCache;
};
+
+ typedef QHash<KisNodeSP, QVector<Request> > RectsHash;
public:
SuspendLod0Updates()
{
}
- bool filter(KisImage *image, KisNode *node, const QRect &rect) override {
+ bool filter(KisImage *image, KisNode *node, const QRect &rect, bool resetAnimationCache) override {
if (image->currentLevelOfDetail() > 0) return false;
QMutexLocker l(&m_mutex);
- m_requestsHash[KisNodeSP(node)].append(rect);
+ m_requestsHash[KisNodeSP(node)].append(Request(rect, resetAnimationCache));
return true;
}
static inline QRect alignRect(const QRect &rc, const int step) {
static const int decstep = step - 1;
static const int invstep = ~decstep;
int x0, y0, x1, y1;
rc.getCoords(&x0, &y0, &x1, &y1);
x0 &= invstep;
y0 &= invstep;
x1 |= decstep;
y1 |= decstep;
QRect result;
result.setCoords(x0, y0, x1, y1);
return result;
}
void notifyUpdates(KisNodeGraphListener *listener) {
RectsHash::const_iterator it = m_requestsHash.constBegin();
RectsHash::const_iterator end = m_requestsHash.constEnd();
const int step = 64;
for (; it != end; ++it) {
KisNodeSP node = it.key();
- const QVector<QRect> &rects = it.value();
-
- QVector<QRect>::const_iterator it = rects.constBegin();
- QVector<QRect>::const_iterator end = rects.constEnd();
QRegion region;
- for (; it != end; ++it) {
- region += alignRect(*it, step);
+ bool resetAnimationCache = false;
+ Q_FOREACH (const Request &req, it.value()) {
+ region += alignRect(req.rect, step);
+ resetAnimationCache |= req.resetAnimationCache;
}
Q_FOREACH (const QRect &rc, region.rects()) {
// FIXME: constness: port rPU to SP
- listener->requestProjectionUpdate(const_cast<KisNode*>(node.data()), rc);
-
+ listener->requestProjectionUpdate(const_cast<KisNode*>(node.data()), rc, resetAnimationCache);
}
}
}
private:
RectsHash m_requestsHash;
QMutex m_mutex;
};
class SuspendData : public KisStrokeJobData {
public:
SuspendData()
: KisStrokeJobData(SEQUENTIAL)
{}
};
class ResumeAndIssueGraphUpdatesData : public KisStrokeJobData {
public:
ResumeAndIssueGraphUpdatesData()
: KisStrokeJobData(SEQUENTIAL)
{}
};
class UpdatesBarrierData : public KisStrokeJobData {
public:
UpdatesBarrierData()
: KisStrokeJobData(BARRIER)
{}
};
class IssueCanvasUpdatesData : public KisStrokeJobData {
public:
IssueCanvasUpdatesData(QRect _updateRect)
: KisStrokeJobData(CONCURRENT),
updateRect(_updateRect)
{}
QRect updateRect;
};
};
KisSuspendProjectionUpdatesStrokeStrategy::KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP image, bool suspend)
: KisSimpleStrokeStrategy(suspend ? "suspend_stroke_strategy" : "resume_stroke_strategy"),
m_d(new Private)
{
m_d->image = image;
m_d->suspend = suspend;
/**
* Here we add a dumb INIT job so that KisStrokesQueue would know that the
* stroke has already started or not. When the queue reaches the resume
* stroke ans starts its execution, no Lod0 can execute anymore. So all the
* new Lod0 strokes should go to the end of the queue and wrapped into
* their own Suspend/Resume pair.
*/
enableJob(JOB_INIT, true);
enableJob(JOB_DOSTROKE, true);
enableJob(JOB_CANCEL, true);
setNeedsExplicitCancel(true);
}
KisSuspendProjectionUpdatesStrokeStrategy::~KisSuspendProjectionUpdatesStrokeStrategy()
{
}
/**
* When the Lod0 stroke is being recalculated in the background we
* should block all the updates it issues to avoid user distraction.
* The result of the final stroke should be shown to the user in the
* very end when everything is fully ready. Ideally the use should not
* notice that the image has changed :)
*
* (Don't mix this process with suspend/resume capabilities of a
* single stroke. That is a different system!)
*
* The process of the Lod0 regeneration consists of the following:
*
* 1) Suspend stroke executes. It sets a special updates filter on the
* image. The filter blocks all the updates and saves them in an
* internal structure to be emitted in the future.
*
* 2) Lod0 strokes are being recalculated. All their updates are
* blocked and saved in the filter.
*
* 3) Resume stroke starts:
*
* 3.1) First it disables emitting of sigImageUpdated() so the gui
* will not get any update notifications.
*
* 3.2) Then it enables updates themselves.
*
* 3.3) Initiates all the updates that were requested by the Lod0
* stroke. The node graph is regenerated, but the GUI does
* not get this change.
*
* 3.4) Special barrier job waits for all the updates to finish
* and, when they are done, enables GUI notifications again.
*
* 3.5) In a multithreaded way emits the GUI notifications for the
* entire image. Multithreaded way is used to conform the
* double-stage update principle of KisCanvas2.
*/
void KisSuspendProjectionUpdatesStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
Private::SuspendData *suspendData = dynamic_cast<Private::SuspendData*>(data);
Private::ResumeAndIssueGraphUpdatesData *resumeData = dynamic_cast<Private::ResumeAndIssueGraphUpdatesData*>(data);
Private::UpdatesBarrierData *barrierData = dynamic_cast<Private::UpdatesBarrierData*>(data);
Private::IssueCanvasUpdatesData *canvasUpdates = dynamic_cast<Private::IssueCanvasUpdatesData*>(data);
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return;
}
if (suspendData) {
image->setProjectionUpdatesFilter(
KisProjectionUpdatesFilterSP(new Private::SuspendLod0Updates()));
} else if (resumeData) {
image->disableUIUpdates();
resumeAndIssueUpdates(false);
} else if (barrierData) {
image->enableUIUpdates();
} else if (canvasUpdates) {
image->notifyProjectionUpdated(canvasUpdates->updateRect);
}
}
QList<KisStrokeJobData*> KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP /*image*/)
{
QList<KisStrokeJobData*> jobsData;
jobsData << new Private::SuspendData();
return jobsData;
}
QList<KisStrokeJobData*> KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP _image)
{
QList<KisStrokeJobData*> jobsData;
jobsData << new Private::ResumeAndIssueGraphUpdatesData();
jobsData << new Private::UpdatesBarrierData();
using KritaUtils::splitRectIntoPatches;
using KritaUtils::optimalPatchSize;
KisImageSP image = _image;
QVector<QRect> rects = splitRectIntoPatches(image->bounds(), optimalPatchSize());
Q_FOREACH (const QRect &rc, rects) {
jobsData << new Private::IssueCanvasUpdatesData(rc);
}
return jobsData;
}
void KisSuspendProjectionUpdatesStrokeStrategy::resumeAndIssueUpdates(bool dropUpdates)
{
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return;
}
KisProjectionUpdatesFilterSP filter =
image->projectionUpdatesFilter();
if (!filter) return;
Private::SuspendLod0Updates *localFilter =
dynamic_cast<Private::SuspendLod0Updates*>(filter.data());
if (localFilter) {
image->setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP());
if (!dropUpdates) {
localFilter->notifyUpdates(image.data());
}
}
}
void KisSuspendProjectionUpdatesStrokeStrategy::cancelStrokeCallback()
{
KisImageSP image = m_d->image.toStrongRef();
if (!image) {
return;
}
/**
* We shouldn't emit any ad-hoc updates when cancelling the
* stroke. It generates weird temporary holes on the canvas,
* making the user feel awful, thinking his image got
* corrupted. We will just emit a common refreshGraphAsync() that
* will do all the work in a beautiful way
*/
resumeAndIssueUpdates(true);
if (!m_d->suspend) {
// FIXME: optimize
image->refreshGraphAsync();
}
}
diff --git a/libs/image/kis_transform_mask.cpp b/libs/image/kis_transform_mask.cpp
index 1816e39a74..993dacc5d4 100644
--- a/libs/image/kis_transform_mask.cpp
+++ b/libs/image/kis_transform_mask.cpp
@@ -1,455 +1,466 @@
/*
* 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 <KoIcon.h>
#include <kis_icon.h>
#include <KoCompositeOpRegistry.h>
#include "kis_layer.h"
#include "kis_transform_mask.h"
#include "filter/kis_filter.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter_registry.h"
#include "kis_selection.h"
#include "kis_processing_information.h"
#include "kis_node.h"
#include "kis_node_visitor.h"
#include "kis_processing_visitor.h"
#include "kis_node_progress_proxy.h"
#include "kis_transaction.h"
#include "kis_painter.h"
#include "kis_busy_progress_indicator.h"
#include "kis_perspectivetransform_worker.h"
#include "kis_transform_mask_params_interface.h"
#include "kis_transform_mask_params_factory_registry.h"
#include "kis_recalculate_transform_mask_job.h"
#include "kis_signal_compressor.h"
#include "kis_algebra_2d.h"
#include "kis_safe_transform.h"
#include "kis_keyframe_channel.h"
#include "kis_image_config.h"
//#include "kis_paint_device_debug_utils.h"
//#define DEBUG_RENDERING
//#define DUMP_RECT QRect(0,0,512,512)
#define UPDATE_DELAY 3000 /*ms */
struct Q_DECL_HIDDEN KisTransformMask::Private
{
Private()
: worker(0, QTransform(), 0),
staticCacheValid(false),
recalculatingStaticImage(false),
updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE),
offBoundsReadArea(0.5)
{
}
Private(const Private &rhs)
: worker(rhs.worker),
params(rhs.params),
staticCacheValid(false),
recalculatingStaticImage(rhs.recalculatingStaticImage),
updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE)
{
}
void reloadParameters()
{
QTransform affineTransform;
if (params->isAffine()) {
affineTransform = params->finalAffineTransform();
}
worker.setForwardTransform(affineTransform);
params->clearChangedFlag();
staticCacheValid = false;
}
KisPerspectiveTransformWorker worker;
KisTransformMaskParamsInterfaceSP params;
bool staticCacheValid;
bool recalculatingStaticImage;
KisPaintDeviceSP staticCacheDevice;
KisSignalCompressor updateSignalCompressor;
qreal offBoundsReadArea;
};
KisTransformMask::KisTransformMask()
: KisEffectMask(),
m_d(new Private)
{
setTransformParams(
KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams()));
connect(this, SIGNAL(initiateDelayedStaticUpdate()), &m_d->updateSignalCompressor, SLOT(start()));
+ connect(this, SIGNAL(forceTerminateDelayedStaticUpdate()), &m_d->updateSignalCompressor, SLOT(stop()));
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
KisImageConfig cfg;
m_d->offBoundsReadArea = cfg.transformMaskOffBoundsReadArea();
}
KisTransformMask::~KisTransformMask()
{
}
KisTransformMask::KisTransformMask(const KisTransformMask& rhs)
: KisEffectMask(rhs),
m_d(new Private(*rhs.m_d))
{
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDelayedStaticUpdate()));
}
KisPaintDeviceSP KisTransformMask::paintDevice() const
{
return 0;
}
QIcon KisTransformMask::icon() const
{
return KisIconUtils::loadIcon("transformMask");
}
void KisTransformMask::setTransformParams(KisTransformMaskParamsInterfaceSP params)
{
KIS_ASSERT_RECOVER(params) {
params = KisTransformMaskParamsInterfaceSP(
new KisDumbTransformMaskParams());
}
m_d->params = params;
m_d->reloadParameters();
- m_d->updateSignalCompressor.stop();
+ emit forceTerminateDelayedStaticUpdate();
}
KisTransformMaskParamsInterfaceSP KisTransformMask::transformParams() const
{
return m_d->params;
}
void KisTransformMask::slotDelayedStaticUpdate()
{
/**
* The mask might have been deleted from the layers stack in the
* meanwhile. Just ignore the updates in the case.
*/
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
if (!parentLayer) return;
KisImageSP image = parentLayer->image();
if (image) {
image->addSpontaneousJob(new KisRecalculateTransformMaskJob(this));
}
}
KisPaintDeviceSP KisTransformMask::buildPreviewDevice()
{
/**
* Note: this function must be called from within the scheduler's
* context. We are accessing parent's updateProjection(), which
* is not entirely safe.
*/
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
KIS_ASSERT_RECOVER(parentLayer) { return new KisPaintDevice(colorSpace()); }
KisPaintDeviceSP device =
new KisPaintDevice(parentLayer->original()->colorSpace());
QRect requestedRect = parentLayer->original()->exactBounds();
parentLayer->buildProjectionUpToNode(device, this, requestedRect);
return device;
}
void KisTransformMask::recaclulateStaticImage()
{
/**
* Note: this function must be called from within the scheduler's
* context. We are accessing parent's updateProjection(), which
* is not entirely safe.
*/
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
KIS_ASSERT_RECOVER_RETURN(parentLayer);
if (!m_d->staticCacheDevice) {
m_d->staticCacheDevice =
new KisPaintDevice(parentLayer->original()->colorSpace());
}
m_d->recalculatingStaticImage = true;
/**
* updateProjection() is assuming that the requestedRect takes
* into account all the change rects of all the masks. Usually,
* this work is done by the walkers.
*/
QRect requestedRect = parentLayer->changeRect(parentLayer->original()->exactBounds());
/**
* Here we use updateProjection() to regenerate the projection of
* the layer and after that a special update call (no-filthy) will
* be issued to pass the changed further through the stack.
*/
parentLayer->updateProjection(requestedRect, this);
m_d->recalculatingStaticImage = false;
m_d->staticCacheValid = true;
}
QRect KisTransformMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc,
PositionToFilthy maskPos) const
{
Q_ASSERT_X(src != dst, "KisTransformMask::decorateRect",
"src must be != dst, because we cant create transactions "
"during merge, as it breaks reentrancy");
KIS_ASSERT_RECOVER(m_d->params) { return rc; }
if (m_d->params->isHidden()) return rc;
KIS_ASSERT_RECOVER_NOOP(maskPos == N_FILTHY ||
maskPos == N_ABOVE_FILTHY ||
maskPos == N_BELOW_FILTHY);
if (m_d->params->hasChanged()) m_d->reloadParameters();
if (!m_d->recalculatingStaticImage &&
(maskPos == N_FILTHY || maskPos == N_ABOVE_FILTHY)) {
m_d->staticCacheValid = false;
emit initiateDelayedStaticUpdate();
}
if (m_d->recalculatingStaticImage) {
m_d->staticCacheDevice->clear();
m_d->params->transformDevice(const_cast<KisTransformMask*>(this), src, m_d->staticCacheDevice);
QRect updatedRect = m_d->staticCacheDevice->extent();
KisPainter::copyAreaOptimized(updatedRect.topLeft(), m_d->staticCacheDevice, dst, updatedRect);
#ifdef DEBUG_RENDERING
qDebug() << "Recalculate" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc);
KIS_DUMP_DEVICE_2(src, DUMP_RECT, "recalc_src", "dd");
KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "recalc_dst", "dd");
#endif /* DEBUG_RENDERING */
} else if (!m_d->staticCacheValid && m_d->params->isAffine()) {
m_d->worker.runPartialDst(src, dst, rc);
#ifdef DEBUG_RENDERING
qDebug() << "Partial" << name() << ppVar(src->exactBounds()) << ppVar(src->extent()) << ppVar(dst->exactBounds()) << ppVar(dst->extent()) << ppVar(rc);
KIS_DUMP_DEVICE_2(src, DUMP_RECT, "partial_src", "dd");
KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "partial_dst", "dd");
#endif /* DEBUG_RENDERING */
} else if (m_d->staticCacheDevice && m_d->staticCacheValid) {
KisPainter::copyAreaOptimized(rc.topLeft(), m_d->staticCacheDevice, dst, rc);
#ifdef DEBUG_RENDERING
qDebug() << "Fetch" << name() << ppVar(src->exactBounds()) << ppVar(dst->exactBounds()) << ppVar(rc);
KIS_DUMP_DEVICE_2(src, DUMP_RECT, "fetch_src", "dd");
KIS_DUMP_DEVICE_2(dst, DUMP_RECT, "fetch_dst", "dd");
#endif /* DEBUG_RENDERING */
}
KIS_ASSERT_RECOVER_NOOP(this->busyProgressIndicator());
this->busyProgressIndicator()->update();
return rc;
}
bool KisTransformMask::accept(KisNodeVisitor &v)
{
return v.visit(this);
}
void KisTransformMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
/**
* FIXME: This check of the emptiness should be done
* on the higher/lower level
*/
if (rect.isEmpty()) return rect;
QRect changeRect = rect;
if (m_d->params->isAffine()) {
QRect bounds;
QRect interestRect;
KisNodeSP parentNode = parent();
if (parentNode) {
bounds = parentNode->original()->defaultBounds()->bounds();
interestRect = parentNode->original()->extent();
} else {
bounds = QRect(0,0,777,777);
interestRect = QRect(0,0,888,888);
warnKrita << "WARNING: transform mask has no parent (change rect)."
<< "Cannot run safe transformations."
<< "Will limit bounds to" << ppVar(bounds);
}
const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea);
if (m_d->params->hasChanged()) m_d->reloadParameters();
KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
changeRect = transform.mapRectForward(rect);
} else {
QRect interestRect;
interestRect = parent() ? parent()->original()->extent() : QRect();
changeRect = m_d->params->nonAffineChangeRect(rect);
}
return changeRect;
}
QRect KisTransformMask::needRect(const QRect& rect, PositionToFilthy pos) const
{
Q_UNUSED(pos);
/**
* FIXME: This check of the emptiness should be done
* on the higher/lower level
*/
if (rect.isEmpty()) return rect;
if (!m_d->params->isAffine()) return rect;
QRect bounds;
QRect interestRect;
KisNodeSP parentNode = parent();
if (parentNode) {
bounds = parentNode->original()->defaultBounds()->bounds();
interestRect = parentNode->original()->extent();
} else {
bounds = QRect(0,0,777,777);
interestRect = QRect(0,0,888,888);
warnKrita << "WARNING: transform mask has no parent (need rect)."
<< "Cannot run safe transformations."
<< "Will limit bounds to" << ppVar(bounds);
}
QRect needRect = rect;
if (m_d->params->isAffine()) {
const QRect limitingRect = KisAlgebra2D::blowRect(bounds, m_d->offBoundsReadArea);
if (m_d->params->hasChanged()) m_d->reloadParameters();
KisSafeTransform transform(m_d->worker.forwardTransform(), limitingRect, interestRect);
needRect = transform.mapRectBackward(rect);
} else {
needRect = m_d->params->nonAffineNeedRect(rect, interestRect);
}
return needRect;
}
QRect KisTransformMask::extent() const
{
QRect rc = KisMask::extent();
QRect partialChangeRect;
QRect existentProjection;
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
if (parentLayer) {
partialChangeRect = parentLayer->partialChangeRect(const_cast<KisTransformMask*>(this), rc);
existentProjection = parentLayer->projection()->extent();
}
return changeRect(partialChangeRect) | existentProjection;
}
QRect KisTransformMask::exactBounds() const
{
QRect rc = KisMask::exactBounds();
QRect partialChangeRect;
QRect existentProjection;
KisLayerSP parentLayer = qobject_cast<KisLayer*>(parent().data());
if (parentLayer) {
partialChangeRect = parentLayer->partialChangeRect(const_cast<KisTransformMask*>(this), rc);
existentProjection = parentLayer->projection()->exactBounds();
}
return changeRect(partialChangeRect) | existentProjection;
}
void KisTransformMask::setX(qint32 x)
{
m_d->params->translate(QPointF(x - this->x(), 0));
setTransformParams(m_d->params);
KisEffectMask::setX(x);
}
void KisTransformMask::setY(qint32 y)
{
m_d->params->translate(QPointF(0, y - this->y()));
setTransformParams(m_d->params);
KisEffectMask::setY(y);
}
+void KisTransformMask::forceUpdateTimedNode()
+{
+ if (m_d->updateSignalCompressor.isActive()) {
+ KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->staticCacheValid);
+
+ emit forceTerminateDelayedStaticUpdate();
+ slotDelayedStaticUpdate();
+ }
+}
+
KisKeyframeChannel *KisTransformMask::requestKeyframeChannel(const QString &id)
{
if (id == KisKeyframeChannel::TransformArguments.id() ||
id == KisKeyframeChannel::TransformPositionX.id() ||
id == KisKeyframeChannel::TransformPositionY.id() ||
id == KisKeyframeChannel::TransformScaleX.id() ||
id == KisKeyframeChannel::TransformScaleY.id() ||
id == KisKeyframeChannel::TransformShearX.id() ||
id == KisKeyframeChannel::TransformShearY.id() ||
id == KisKeyframeChannel::TransformRotationX.id() ||
id == KisKeyframeChannel::TransformRotationY.id() ||
id == KisKeyframeChannel::TransformRotationZ.id()) {
KisAnimatedTransformParamsInterface *animatedParams = dynamic_cast<KisAnimatedTransformParamsInterface*>(m_d->params.data());
if (!animatedParams) {
auto converted = KisTransformMaskParamsFactoryRegistry::instance()->animateParams(m_d->params);
if (converted.isNull()) return KisEffectMask::requestKeyframeChannel(id);
m_d->params = converted;
animatedParams = dynamic_cast<KisAnimatedTransformParamsInterface*>(converted.data());
}
KisKeyframeChannel *channel = animatedParams->getKeyframeChannel(id, parent()->original()->defaultBounds());
if (channel) return channel;
}
return KisEffectMask::requestKeyframeChannel(id);
}
diff --git a/libs/image/kis_transform_mask.h b/libs/image/kis_transform_mask.h
index a51c73f734..7c96afc5c3 100644
--- a/libs/image/kis_transform_mask.h
+++ b/libs/image/kis_transform_mask.h
@@ -1,90 +1,94 @@
/*
* 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_TRANSFORM_MASK_
#define _KIS_TRANSFORM_MASK_
#include <QScopedPointer>
#include "kis_types.h"
#include "kis_effect_mask.h"
+#include "KisDelayedUpdateNodeInterface.h"
/**
Transform a layer according to a matrix transform
*/
-class KRITAIMAGE_EXPORT KisTransformMask : public KisEffectMask
+class KRITAIMAGE_EXPORT KisTransformMask : public KisEffectMask, public KisDelayedUpdateNodeInterface
{
Q_OBJECT
public:
/**
* Create an empty filter mask.
*/
KisTransformMask();
virtual ~KisTransformMask();
QIcon icon() const;
KisNodeSP clone() const {
return KisNodeSP(new KisTransformMask(*this));
}
KisPaintDeviceSP paintDevice() const;
bool accept(KisNodeVisitor &v);
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
KisTransformMask(const KisTransformMask& rhs);
QRect decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc,
PositionToFilthy maskPos) const;
QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
QRect extent() const;
QRect exactBounds() const;
void setTransformParams(KisTransformMaskParamsInterfaceSP params);
KisTransformMaskParamsInterfaceSP transformParams() const;
void recaclulateStaticImage();
KisPaintDeviceSP buildPreviewDevice();
void setX(qint32 x);
void setY(qint32 y);
+ void forceUpdateTimedNode() override;
+
protected:
KisKeyframeChannel *requestKeyframeChannel(const QString &id);
private Q_SLOTS:
void slotDelayedStaticUpdate();
Q_SIGNALS:
void initiateDelayedStaticUpdate() const;
+ void forceTerminateDelayedStaticUpdate() const;
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif //_KIS_TRANSFORM_MASK_
diff --git a/libs/image/kis_undo_adapter.h b/libs/image/kis_undo_adapter.h
index 90059b4efa..d72b2be1fb 100644
--- a/libs/image/kis_undo_adapter.h
+++ b/libs/image/kis_undo_adapter.h
@@ -1,64 +1,64 @@
/*
* 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_ADAPTER_H_
#define KIS_UNDO_ADAPTER_H_
#include <QObject>
#include <kritaimage_export.h>
-#include "kis_undo_store.h"
+#include <kis_undo_store.h>
class KRITAIMAGE_EXPORT KisUndoAdapter : public QObject
{
Q_OBJECT
public:
KisUndoAdapter(KisUndoStore *undoStore);
virtual ~KisUndoAdapter();
public:
void emitSelectionChanged();
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;
inline void setUndoStore(KisUndoStore *undoStore) {
m_undoStore = undoStore;
}
Q_SIGNALS:
void selectionChanged();
protected:
inline KisUndoStore* undoStore() {
return m_undoStore;
}
private:
Q_DISABLE_COPY(KisUndoAdapter)
KisUndoStore *m_undoStore;
};
#endif // KIS_UNDO_ADAPTER_H_
diff --git a/libs/image/kis_undo_store.h b/libs/image/kis_undo_store.h
deleted file mode 100644
index 36a426e52d..0000000000
--- a/libs/image/kis_undo_store.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 <kritaimage_export.h>
-
-class KUndo2Command;
-class KUndo2MagicString;
-
-
-/**
- * See also: http://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 KRITAIMAGE_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/image/kis_undo_stores.h b/libs/image/kis_undo_stores.h
deleted file mode 100644
index 27b100a821..0000000000
--- a/libs/image/kis_undo_stores.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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_UNDO_STORES_H
-#define __KIS_UNDO_STORES_H
-
-#include "kis_undo_store.h"
-
-class KUndo2Stack;
-class KUndo2MagicString;
-
-
-/**
- * KisSurrogateUndoAdapter -- saves commands directly to the
- * internal stack. Used for wrapping around legacy code into
- * a single command.
- */
-class KRITAIMAGE_EXPORT KisSurrogateUndoStore : public KisUndoStore
-{
-public:
- KisSurrogateUndoStore();
- ~KisSurrogateUndoStore();
-
- const KUndo2Command* presentCommand();
- void undoLastCommand();
- void addCommand(KUndo2Command *cmd);
- void beginMacro(const KUndo2MagicString& macroName);
- void endMacro();
-
- void undo();
- void redo();
-
- void undoAll();
- void redoAll();
-
- void purgeRedoState();
-
- void clear();
-
-private:
- KUndo2Stack *m_undoStack;
-};
-
-/**
- * @brief The KisDumbUndoStore class doesn't actually save commands,
- * so you cannot undo or redo!
- */
-class KRITAIMAGE_EXPORT KisDumbUndoStore : public KisUndoStore
-{
-public:
- const KUndo2Command* presentCommand();
- void undoLastCommand();
- void addCommand(KUndo2Command *cmd);
- void beginMacro(const KUndo2MagicString& macroName);
- void endMacro();
- void purgeRedoState();
-};
-
-#endif /* __KIS_UNDO_STORES_H */
diff --git a/libs/image/kis_update_scheduler.cpp b/libs/image/kis_update_scheduler.cpp
index 17440739f0..e12adc13c7 100644
--- a/libs/image/kis_update_scheduler.cpp
+++ b/libs/image/kis_update_scheduler.cpp
@@ -1,465 +1,467 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_update_scheduler.h"
#include "klocalizedstring.h"
#include "kis_image_config.h"
#include "kis_merge_walker.h"
#include "kis_full_refresh_walker.h"
#include "kis_updater_context.h"
#include "kis_simple_update_queue.h"
#include "kis_strokes_queue.h"
#include "kis_queues_progress_updater.h"
#include <QReadWriteLock>
#include "kis_lazy_wait_condition.h"
//#define DEBUG_BALANCING
#ifdef DEBUG_BALANCING
#define DEBUG_BALANCING_METRICS(decidedFirst, excl) \
dbgKrita << "Balance decision:" << decidedFirst \
<< "(" << excl << ")" \
<< "updates:" << m_d->updatesQueue.sizeMetric() \
<< "strokes:" << m_d->strokesQueue.sizeMetric()
#else
#define DEBUG_BALANCING_METRICS(decidedFirst, excl)
#endif
struct Q_DECL_HIDDEN KisUpdateScheduler::Private {
Private(KisUpdateScheduler *_q, KisProjectionUpdateListener *p)
: q(_q)
, projectionUpdateListener(p)
{}
KisUpdateScheduler *q;
KisSimpleUpdateQueue updatesQueue;
KisStrokesQueue strokesQueue;
KisUpdaterContext updaterContext;
bool processingBlocked = false;
qreal balancingRatio = 1.0; // updates-queue-size/strokes-queue-size
KisProjectionUpdateListener *projectionUpdateListener;
KisQueuesProgressUpdater *progressUpdater = 0;
QAtomicInt updatesLockCounter;
QReadWriteLock updatesStartLock;
KisLazyWaitCondition updatesFinishedCondition;
};
KisUpdateScheduler::KisUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener)
: m_d(new Private(this, projectionUpdateListener))
{
updateSettings();
connectSignals();
}
KisUpdateScheduler::KisUpdateScheduler()
: m_d(new Private(this, 0))
{
}
KisUpdateScheduler::~KisUpdateScheduler()
{
delete m_d->progressUpdater;
delete m_d;
}
void KisUpdateScheduler::connectSignals()
{
connect(&m_d->updaterContext, SIGNAL(sigContinueUpdate(const QRect&)),
SLOT(continueUpdate(const QRect&)),
Qt::DirectConnection);
connect(&m_d->updaterContext, SIGNAL(sigDoSomeUsefulWork()),
SLOT(doSomeUsefulWork()), Qt::DirectConnection);
connect(&m_d->updaterContext, SIGNAL(sigSpareThreadAppeared()),
SLOT(spareThreadAppeared()), Qt::DirectConnection);
}
void KisUpdateScheduler::setProgressProxy(KoProgressProxy *progressProxy)
{
delete m_d->progressUpdater;
m_d->progressUpdater = progressProxy ?
new KisQueuesProgressUpdater(progressProxy) : 0;
}
void KisUpdateScheduler::progressUpdate()
{
if (!m_d->progressUpdater) return;
if(!m_d->strokesQueue.hasOpenedStrokes()) {
QString jobName = m_d->strokesQueue.currentStrokeName().toString();
if(jobName.isEmpty()) {
jobName = i18n("Updating...");
}
int sizeMetric = m_d->strokesQueue.sizeMetric();
if (!sizeMetric) {
sizeMetric = m_d->updatesQueue.sizeMetric();
}
m_d->progressUpdater->updateProgress(sizeMetric, jobName);
}
else {
m_d->progressUpdater->hide();
}
}
void KisUpdateScheduler::updateProjection(KisNodeSP node, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateJob(node, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::updateProjectionNoFilthy(KisNodeSP node, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addUpdateNoFilthyJob(node, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::fullRefreshAsync(KisNodeSP root, const QRect& rc, const QRect &cropRect)
{
m_d->updatesQueue.addFullRefreshJob(root, rc, cropRect, currentLevelOfDetail());
processQueues();
}
void KisUpdateScheduler::fullRefresh(KisNodeSP root, const QRect& rc, const QRect &cropRect)
{
KisBaseRectsWalkerSP walker = new KisFullRefreshWalker(cropRect);
walker->collectRects(root, rc);
bool needLock = true;
if(m_d->processingBlocked) {
warnImage << "WARNING: Calling synchronous fullRefresh under a scheduler lock held";
warnImage << "We will not assert for now, but please port caller's to strokes";
warnImage << "to avoid this warning";
needLock = false;
}
if(needLock) lock();
m_d->updaterContext.lock();
Q_ASSERT(m_d->updaterContext.isJobAllowed(walker));
m_d->updaterContext.addMergeJob(walker);
m_d->updaterContext.waitForDone();
m_d->updaterContext.unlock();
if(needLock) unlock(true);
}
void KisUpdateScheduler::addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
{
m_d->updatesQueue.addSpontaneousJob(spontaneousJob);
processQueues();
}
KisStrokeId KisUpdateScheduler::startStroke(KisStrokeStrategy *strokeStrategy)
{
KisStrokeId id = m_d->strokesQueue.startStroke(strokeStrategy);
processQueues();
return id;
}
void KisUpdateScheduler::addJob(KisStrokeId id, KisStrokeJobData *data)
{
m_d->strokesQueue.addJob(id, data);
processQueues();
}
void KisUpdateScheduler::endStroke(KisStrokeId id)
{
m_d->strokesQueue.endStroke(id);
processQueues();
}
bool KisUpdateScheduler::cancelStroke(KisStrokeId id)
{
bool result = m_d->strokesQueue.cancelStroke(id);
processQueues();
return result;
}
bool KisUpdateScheduler::tryCancelCurrentStrokeAsync()
{
return m_d->strokesQueue.tryCancelCurrentStrokeAsync();
}
UndoResult KisUpdateScheduler::tryUndoLastStrokeAsync()
{
return m_d->strokesQueue.tryUndoLastStrokeAsync();
}
bool KisUpdateScheduler::wrapAroundModeSupported() const
{
return m_d->strokesQueue.wrapAroundModeSupported();
}
void KisUpdateScheduler::setDesiredLevelOfDetail(int lod)
{
m_d->strokesQueue.setDesiredLevelOfDetail(lod);
/**
* The queue might have started an internal stroke for
* cache synchronization. Process the queues to execute
* it if needed.
*/
processQueues();
}
void KisUpdateScheduler::explicitRegenerateLevelOfDetail()
{
m_d->strokesQueue.explicitRegenerateLevelOfDetail();
// \see a comment in setDesiredLevelOfDetail()
processQueues();
}
int KisUpdateScheduler::currentLevelOfDetail() const
{
int levelOfDetail = -1;
if (levelOfDetail < 0) {
levelOfDetail = m_d->updaterContext.currentLevelOfDetail();
}
if (levelOfDetail < 0) {
levelOfDetail = m_d->updatesQueue.overrideLevelOfDetail();
}
if (levelOfDetail < 0) {
levelOfDetail = 0;
}
return levelOfDetail;
}
void KisUpdateScheduler::setLod0ToNStrokeStrategyFactory(const KisLodSyncStrokeStrategyFactory &factory)
{
m_d->strokesQueue.setLod0ToNStrokeStrategyFactory(factory);
}
void KisUpdateScheduler::setSuspendUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->strokesQueue.setSuspendUpdatesStrokeStrategyFactory(factory);
}
void KisUpdateScheduler::setResumeUpdatesStrokeStrategyFactory(const KisSuspendResumeStrategyFactory &factory)
{
m_d->strokesQueue.setResumeUpdatesStrokeStrategyFactory(factory);
}
KisPostExecutionUndoAdapter *KisUpdateScheduler::lodNPostExecutionUndoAdapter() const
{
return m_d->strokesQueue.lodNPostExecutionUndoAdapter();
}
void KisUpdateScheduler::updateSettings()
{
m_d->updatesQueue.updateSettings();
KisImageConfig config;
m_d->balancingRatio = config.schedulerBalancingRatio();
}
void KisUpdateScheduler::lock()
{
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
}
void KisUpdateScheduler::unlock(bool resetLodLevels)
{
if (resetLodLevels) {
/**
* Legacy strokes may have changed the image while we didn't
* control it. Notify the queue to take it into account.
*/
m_d->strokesQueue.notifyUFOChangedImage();
}
m_d->processingBlocked = false;
processQueues();
}
bool KisUpdateScheduler::isIdle()
{
bool result = false;
if (tryBarrierLock()) {
result = true;
unlock(false);
}
return result;
}
void KisUpdateScheduler::waitForDone()
{
do {
processQueues();
m_d->updaterContext.waitForDone();
} while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
}
bool KisUpdateScheduler::tryBarrierLock()
{
- if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty())
+ if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
return false;
+ }
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
if(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty()) {
m_d->processingBlocked = false;
+ processQueues();
return false;
}
return true;
}
void KisUpdateScheduler::barrierLock()
{
do {
m_d->processingBlocked = false;
processQueues();
m_d->processingBlocked = true;
m_d->updaterContext.waitForDone();
} while(!m_d->updatesQueue.isEmpty() || !m_d->strokesQueue.isEmpty());
}
void KisUpdateScheduler::processQueues()
{
wakeUpWaitingThreads();
if(m_d->processingBlocked) return;
if(m_d->strokesQueue.needsExclusiveAccess()) {
DEBUG_BALANCING_METRICS("STROKES", "X");
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
if(!m_d->strokesQueue.needsExclusiveAccess()) {
tryProcessUpdatesQueue();
}
}
else if(m_d->balancingRatio * m_d->strokesQueue.sizeMetric() > m_d->updatesQueue.sizeMetric()) {
DEBUG_BALANCING_METRICS("STROKES", "N");
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
tryProcessUpdatesQueue();
}
else {
DEBUG_BALANCING_METRICS("UPDATES", "N");
tryProcessUpdatesQueue();
m_d->strokesQueue.processQueue(m_d->updaterContext,
!m_d->updatesQueue.isEmpty());
}
progressUpdate();
}
void KisUpdateScheduler::blockUpdates()
{
m_d->updatesFinishedCondition.initWaiting();
m_d->updatesLockCounter.ref();
while(haveUpdatesRunning()) {
m_d->updatesFinishedCondition.wait();
}
m_d->updatesFinishedCondition.endWaiting();
}
void KisUpdateScheduler::unblockUpdates()
{
m_d->updatesLockCounter.deref();
processQueues();
}
void KisUpdateScheduler::wakeUpWaitingThreads()
{
if(m_d->updatesLockCounter && !haveUpdatesRunning()) {
m_d->updatesFinishedCondition.wakeAll();
}
}
void KisUpdateScheduler::tryProcessUpdatesQueue()
{
QReadLocker locker(&m_d->updatesStartLock);
if(m_d->updatesLockCounter) return;
m_d->updatesQueue.processQueue(m_d->updaterContext);
}
bool KisUpdateScheduler::haveUpdatesRunning()
{
QWriteLocker locker(&m_d->updatesStartLock);
qint32 numMergeJobs, numStrokeJobs;
m_d->updaterContext.getJobsSnapshot(numMergeJobs, numStrokeJobs);
return numMergeJobs;
}
void KisUpdateScheduler::continueUpdate(const QRect &rect)
{
Q_ASSERT(m_d->projectionUpdateListener);
m_d->projectionUpdateListener->notifyProjectionUpdated(rect);
}
void KisUpdateScheduler::doSomeUsefulWork()
{
m_d->updatesQueue.optimize();
}
void KisUpdateScheduler::spareThreadAppeared()
{
processQueues();
}
KisTestableUpdateScheduler::KisTestableUpdateScheduler(KisProjectionUpdateListener *projectionUpdateListener,
qint32 threadCount)
{
Q_UNUSED(threadCount);
updateSettings();
m_d->projectionUpdateListener = projectionUpdateListener;
// The queue will update settings in a constructor itself
// m_d->updatesQueue = new KisTestableSimpleUpdateQueue();
// m_d->strokesQueue = new KisStrokesQueue();
// m_d->updaterContext = new KisTestableUpdaterContext(threadCount);
connectSignals();
}
KisTestableUpdaterContext* KisTestableUpdateScheduler::updaterContext()
{
return dynamic_cast<KisTestableUpdaterContext*>(&m_d->updaterContext);
}
KisTestableSimpleUpdateQueue* KisTestableUpdateScheduler::updateQueue()
{
return dynamic_cast<KisTestableSimpleUpdateQueue*>(&m_d->updatesQueue);
}
diff --git a/libs/image/kis_warptransform_worker.cc b/libs/image/kis_warptransform_worker.cc
index 573547b6d7..279451f43b 100644
--- a/libs/image/kis_warptransform_worker.cc
+++ b/libs/image/kis_warptransform_worker.cc
@@ -1,365 +1,365 @@
/*
* kis_warptransform_worker.cc -- 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 "kis_warptransform_worker.h"
#include "kis_random_sub_accessor.h"
#include "kis_iterator_ng.h"
#include "kis_datamanager.h"
#include <QTransform>
#include <QVector2D>
#include <QPainter>
#include <QVarLengthArray>
#include <KoColorSpace.h>
#include <KoColor.h>
#include <math.h>
#include "kis_grid_interpolation_tools.h"
QPointF KisWarpTransformWorker::affineTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
{
int nbPoints = p.size();
QVarLengthArray<qreal> w(nbPoints);
qreal sumWi = 0;
QPointF pStar(0, 0), qStar(0, 0);
QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
for (int i = 0; i < nbPoints; ++i) {
if (v == p[i])
return q[i];
QVector2D tmp(p[i] - v);
w[i] = 1. / pow(tmp.lengthSquared(), alpha);
pStar += w[i] * p[i];
qStar += w[i] * q[i];
sumWi += w[i];
}
pStar /= sumWi;
qStar /= sumWi;
qreal A_tmp[4] = {0, 0, 0, 0};
for (int i = 0; i < nbPoints; ++i) {
pHat[i] = p[i] - pStar;
qHat[i] = q[i] - qStar;
A_tmp[0] += w[i] * pow(pHat[i].x(), 2);
A_tmp[3] += w[i] * pow(pHat[i].y(), 2);
A_tmp[1] += w[i] * pHat[i].x() * pHat[i].y();
}
A_tmp[2] = A_tmp[1];
qreal det_A_tmp = A_tmp[0] * A_tmp[3] - A_tmp[1] * A_tmp[2];
qreal A_tmp_inv[4];
if (det_A_tmp == 0)
return v;
A_tmp_inv[0] = A_tmp[3] / det_A_tmp;
A_tmp_inv[1] = - A_tmp[1] / det_A_tmp;
A_tmp_inv[2] = A_tmp_inv[1];
A_tmp_inv[3] = A_tmp[0] / det_A_tmp;
QPointF t = v - pStar;
QPointF A_precalc(t.x() * A_tmp_inv[0] + t.y() * A_tmp_inv[1], t.x() * A_tmp_inv[2] + t.y() * A_tmp_inv[3]);
qreal A_j;
QPointF res = qStar;
for (int j = 0; j < nbPoints; ++j) {
A_j = A_precalc.x() * pHat[j].x() + A_precalc.y() * pHat[j].y();
res += w[j] * A_j * qHat[j];
}
return res;
}
QPointF KisWarpTransformWorker::similitudeTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
{
int nbPoints = p.size();
QVarLengthArray<qreal> w(nbPoints);
qreal sumWi = 0;
QPointF pStar(0, 0), qStar(0, 0);
QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
for (int i = 0; i < nbPoints; ++i) {
if (v == p[i])
return q[i];
QVector2D tmp(p[i] - v);
w[i] = 1. / pow(tmp.lengthSquared(), alpha);
pStar += w[i] * p[i];
qStar += w[i] * q[i];
sumWi += w[i];
}
pStar /= sumWi;
qStar /= sumWi;
qreal mu_s = 0;
QPointF res_tmp(0, 0);
qreal qx, qy, px, py;
for (int i = 0; i < nbPoints; ++i) {
pHat[i] = p[i] - pStar;
qHat[i] = q[i] - qStar;
QVector2D tmp(pHat[i]);
mu_s += w[i] * tmp.lengthSquared();
qx = w[i] * qHat[i].x();
qy = w[i] * qHat[i].y();
px = pHat[i].x();
py = pHat[i].y();
res_tmp += QPointF(qx * px + qy * py, qx * py - qy * px);
}
res_tmp /= mu_s;
QPointF v_m_pStar(v - pStar);
QPointF res(res_tmp.x() * v_m_pStar.x() + res_tmp.y() * v_m_pStar.y(), res_tmp.x() * v_m_pStar.y() - res_tmp.y() * v_m_pStar.x());
res += qStar;
return res;
}
QPointF KisWarpTransformWorker::rigidTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha)
{
int nbPoints = p.size();
QVarLengthArray<qreal> w(nbPoints);
qreal sumWi = 0;
QPointF pStar(0, 0), qStar(0, 0);
QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints);
for (int i = 0; i < nbPoints; ++i) {
if (v == p[i])
return q[i];
QVector2D tmp(p[i] - v);
w[i] = 1. / pow(tmp.lengthSquared(), alpha);
pStar += w[i] * p[i];
qStar += w[i] * q[i];
sumWi += w[i];
}
pStar /= sumWi;
qStar /= sumWi;
QVector2D res_tmp(0, 0);
qreal qx, qy, px, py;
for (int i = 0; i < nbPoints; ++i) {
pHat[i] = p[i] - pStar;
qHat[i] = q[i] - qStar;
qx = w[i] * qHat[i].x();
qy = w[i] * qHat[i].y();
px = pHat[i].x();
py = pHat[i].y();
res_tmp += QVector2D(qx * px + qy * py, qx * py - qy * px);
}
QPointF f_arrow(res_tmp.normalized().toPointF());
QVector2D v_m_pStar(v - pStar);
QPointF res(f_arrow.x() * v_m_pStar.x() + f_arrow.y() * v_m_pStar.y(), f_arrow.x() * v_m_pStar.y() - f_arrow.y() * v_m_pStar.x());
res += qStar;
return res;
}
KisWarpTransformWorker::KisWarpTransformWorker(WarpType warpType, KisPaintDeviceSP dev, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress)
: m_dev(dev), m_progress(progress)
{
m_origPoint = origPoint;
m_transfPoint = transfPoint;
m_alpha = alpha;
switch(warpType) {
case AFFINE_TRANSFORM:
m_warpMathFunction = &affineTransformMath;
break;
case SIMILITUDE_TRANSFORM:
m_warpMathFunction = &similitudeTransformMath;
break;
case RIGID_TRANSFORM:
m_warpMathFunction = &rigidTransformMath;
break;
default:
m_warpMathFunction = 0;
break;
}
}
KisWarpTransformWorker::~KisWarpTransformWorker()
{
}
struct KisWarpTransformWorker::FunctionTransformOp
{
FunctionTransformOp(KisWarpTransformWorker::WarpMathFunction function,
const QVector<QPointF> &p,
const QVector<QPointF> &q,
qreal alpha)
: m_function(function),
m_p(p),
m_q(q),
m_alpha(alpha)
{
}
QPointF operator() (const QPointF &pt) const {
return m_function(pt, m_p, m_q, m_alpha);
}
KisWarpTransformWorker::WarpMathFunction m_function;
const QVector<QPointF> &m_p;
const QVector<QPointF> &m_q;
qreal m_alpha;
};
void KisWarpTransformWorker::run()
{
if (!m_warpMathFunction ||
m_origPoint.isEmpty() ||
m_origPoint.size() != m_transfPoint.size()) {
return;
}
KisPaintDeviceSP srcdev = new KisPaintDevice(*m_dev.data());
if (m_origPoint.size() == 1) {
QPointF translate(QPointF(m_dev->x(), m_dev->y()) + m_transfPoint[0] - m_origPoint[0]);
m_dev->moveTo(translate.toPoint());
return;
}
const QRect srcBounds = srcdev->region().boundingRect();
m_dev->clear();
const int pixelPrecision = 8;
FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
GridIterationTools::PaintDevicePolygonOp polygonOp(srcdev, m_dev);
GridIterationTools::processGrid(polygonOp, functionOp,
srcBounds, pixelPrecision);
}
#include "krita_utils.h"
QRect KisWarpTransformWorker::approxChangeRect(const QRect &rc)
{
const qreal margin = 0.05;
FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha);
- QRect resultRect = KritaUtils::approximateRectWithPointTransform(rc, functionOp);
+ QRect resultRect = KisAlgebra2D::approximateRectWithPointTransform(rc, functionOp);
return KisAlgebra2D::blowRect(resultRect, margin);
}
QRect KisWarpTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
{
Q_UNUSED(rc);
return fullBounds;
}
QImage KisWarpTransformWorker::transformQImage(WarpType warpType,
const QVector<QPointF> &origPoint,
const QVector<QPointF> &transfPoint,
qreal alpha,
const QImage& srcImage,
const QPointF &srcQImageOffset,
QPointF *newOffset)
{
KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) {
return QImage();
}
WarpMathFunction warpMathFunction = &rigidTransformMath;
switch (warpType) {
case AFFINE_TRANSFORM:
warpMathFunction = &affineTransformMath;
break;
case SIMILITUDE_TRANSFORM:
warpMathFunction = &similitudeTransformMath;
break;
case RIGID_TRANSFORM:
warpMathFunction = &rigidTransformMath;
break;
default:
KIS_ASSERT_RECOVER(0 && "Unknown warp mode") { return QImage(); }
}
if (!warpMathFunction ||
origPoint.isEmpty() ||
origPoint.size() != transfPoint.size()) {
return srcImage;
}
if (origPoint.size() == 1) {
*newOffset = srcQImageOffset + (transfPoint[0] - origPoint[0]).toPoint();
return srcImage;
}
FunctionTransformOp functionOp(warpMathFunction, origPoint, transfPoint, alpha);
const QRectF srcBounds = QRectF(srcQImageOffset, srcImage.size());
QRectF dstBounds;
{
QPolygonF testPoints;
testPoints << srcBounds.topLeft();
testPoints << srcBounds.topRight();
testPoints << srcBounds.bottomRight();
testPoints << srcBounds.bottomLeft();
testPoints << srcBounds.topLeft();
QPolygonF::iterator it = testPoints.begin() + 1;
while (it != testPoints.end()) {
it = testPoints.insert(it, 0.5 * (*it + *(it - 1)));
it += 2;
}
it = testPoints.begin();
while (it != testPoints.end()) {
*it = functionOp(*it);
++it;
}
dstBounds = testPoints.boundingRect();
}
QPointF dstQImageOffset = dstBounds.topLeft();
*newOffset = dstQImageOffset;
QRect dstBoundsI = dstBounds.toAlignedRect();
QImage dstImage(dstBoundsI.size(), srcImage.format());
dstImage.fill(0);
const int pixelPrecision = 32;
GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcQImageOffset, dstQImageOffset);
GridIterationTools::processGrid(polygonOp, functionOp, srcBounds.toAlignedRect(), pixelPrecision);
return dstImage;
}
diff --git a/libs/image/kis_wrapped_hline_iterator.h b/libs/image/kis_wrapped_hline_iterator.h
index 6a2fa91c37..6cc7fb995f 100644
--- a/libs/image/kis_wrapped_hline_iterator.h
+++ b/libs/image/kis_wrapped_hline_iterator.h
@@ -1,123 +1,125 @@
/*
* 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_WRAPPED_HLINE_ITERATOR_H
#define __KIS_WRAPPED_HLINE_ITERATOR_H
#include "kis_iterator_ng.h"
#include "kis_wrapped_rect.h"
class WrappedHLineIteratorStrategy
{
public:
typedef KisHLineIteratorSP IteratorTypeSP;
WrappedHLineIteratorStrategy()
: m_iteratorRowStart(KisWrappedRect::TOPLEFT),
m_lastRowCoord(-1)
{
}
inline QSize originalRectToColumnsRows(const QRect &rect) {
return rect.size();
}
inline QPoint columnRowToXY(const QPoint &pt) const {
return pt;
}
inline IteratorTypeSP createIterator(KisDataManager *dataManager,
const QRect &rc,
qint32 offsetX, qint32 offsetY,
- bool writable) {
+ bool writable,
+ KisIteratorCompleteListener *listener) {
return new KisHLineIterator2(dataManager,
rc.x(), rc.y(),
rc.width(),
offsetX, offsetY,
- writable);
+ writable,
+ listener);
}
inline void completeInitialization(QVector<IteratorTypeSP> *iterators,
KisWrappedRect *splitRect) {
m_splitRect = splitRect;
m_iterators = iterators;
m_lastRowCoord = m_splitRect->topLeft().bottom();
}
inline IteratorTypeSP leftColumnIterator() const {
return m_iterators->at(m_iteratorRowStart + KisWrappedRect::TOPLEFT);
}
inline IteratorTypeSP rightColumnIterator() const {
return m_iterators->at(m_iteratorRowStart + KisWrappedRect::TOPRIGHT);
}
inline bool trySwitchIteratorStripe() {
bool needSwitching = leftColumnIterator()->y() == m_lastRowCoord;
if (needSwitching) {
if (m_iteratorRowStart == KisWrappedRect::TOPLEFT &&
m_iterators->at(KisWrappedRect::BOTTOMLEFT)) {
m_iteratorRowStart = KisWrappedRect::BOTTOMLEFT;
m_lastRowCoord = m_splitRect->bottomLeft().bottom();
} else /* if (m_iteratorRowStart == KisWrappedRect::BOTTOMLEFT) */ {
m_iteratorRowStart = KisWrappedRect::TOPLEFT;
m_lastRowCoord = m_splitRect->topLeft().bottom();
Q_FOREACH (IteratorTypeSP it, *m_iterators) {
if (it) {
it->resetRowPos();
}
}
}
}
return needSwitching;
}
inline void iteratorsToNextRow() {
leftColumnIterator()->nextRow();
if (rightColumnIterator()) {
rightColumnIterator()->nextRow();
}
}
inline bool trySwitchColumnForced() {
leftColumnIterator()->resetPixelPos();
if (rightColumnIterator()) {
rightColumnIterator()->resetPixelPos();
}
return true;
}
private:
KisWrappedRect *m_splitRect;
QVector<IteratorTypeSP> *m_iterators;
int m_iteratorRowStart; // may be either KisWrappedRect::TOPLEFT or KisWrappedRect::BOTTOMLEFT
int m_lastRowCoord;
};
#include "kis_wrapped_line_iterator_base.h"
typedef KisWrappedLineIteratorBase<WrappedHLineIteratorStrategy, KisHLineIteratorNG> KisWrappedHLineIterator;
#endif /* __KIS_WRAPPED_HLINE_ITERATOR_H */
diff --git a/libs/image/kis_wrapped_line_iterator_base.h b/libs/image/kis_wrapped_line_iterator_base.h
index acf57c9e2f..3e34e54984 100644
--- a/libs/image/kis_wrapped_line_iterator_base.h
+++ b/libs/image/kis_wrapped_line_iterator_base.h
@@ -1,157 +1,159 @@
/*
* 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_WRAPPED_LINE_ITERATOR_BASE_H
#define __KIS_WRAPPED_LINE_ITERATOR_BASE_H
template <class IteratorStrategy, class BaseClass>
class KisWrappedLineIteratorBase : public BaseClass
{
public:
KisWrappedLineIteratorBase(KisDataManager *dataManager,
const KisWrappedRect &splitRect,
qint32 offsetX, qint32 offsetY,
- bool writable)
+ bool writable,
+ KisIteratorCompleteListener *listener)
: m_splitRect(splitRect)
{
Q_ASSERT(m_splitRect.isSplit());
m_iterators.resize(4);
for (int i = 0; i < 4; i++) {
QRect rc = m_splitRect[i];
if (rc.isEmpty()) continue;
m_iterators[i] = m_strategy.createIterator(dataManager,
rc,
offsetX, offsetY,
- writable);
+ writable,
+ listener);
}
m_strategy.completeInitialization(&m_iterators, &m_splitRect);
m_iterationAreaSize =
m_strategy.originalRectToColumnsRows(m_splitRect.originalRect());
m_currentIterator = m_strategy.leftColumnIterator();
}
bool nextPixel() {
int result = m_currentIterator->nextPixel();
if (!result) {
result = trySwitchColumn();
}
m_currentPos.rx()++;
return m_currentPos.rx() < m_iterationAreaSize.width();
}
bool nextPixels(qint32 n) {
int result = m_currentIterator->nextPixels(n);
if (!result) {
result = trySwitchColumn();
}
m_currentPos.rx() += n;
return m_currentPos.rx() < m_iterationAreaSize.width();
}
void nextRow() {
if (!m_strategy.trySwitchIteratorStripe()) {
m_strategy.iteratorsToNextRow();
}
m_currentIterator = m_strategy.leftColumnIterator();
m_currentPos.rx() = 0;
m_currentPos.ry()++;
}
void nextColumn() {
nextRow();
}
const quint8* oldRawData() const {
return m_currentIterator->oldRawData();
}
const quint8* rawDataConst() const {
return m_currentIterator->rawDataConst();
}
quint8* rawData() {
return m_currentIterator->rawData();
}
qint32 nConseqPixels() const {
qint32 iteratorChunk =
m_currentIterator->nConseqPixels();
return qMin(iteratorChunk,
m_iterationAreaSize.width() - m_currentPos.x());
}
qint32 x() const {
return (m_splitRect.originalRect().topLeft() +
m_strategy.columnRowToXY(m_currentPos)).x();
}
qint32 y() const {
return (m_splitRect.originalRect().topLeft() +
m_strategy.columnRowToXY(m_currentPos)).y();
}
void resetPixelPos() {
errKrita << "CRITICAL: resetPixelPos() is not implemented";
}
void resetRowPos() {
errKrita << "CRITICAL: resetRowPos() is not implemented";
}
void resetColumnPos() {
resetRowPos();
}
private:
bool trySwitchColumn() {
int result = true;
if (m_currentIterator == m_strategy.leftColumnIterator() &&
m_strategy.rightColumnIterator()) {
m_currentIterator = m_strategy.rightColumnIterator();
} else if (m_strategy.trySwitchColumnForced()) {
m_currentIterator = m_strategy.leftColumnIterator();
} else {
result = false;
}
return result;
}
private:
KisWrappedRect m_splitRect;
QSize m_iterationAreaSize; // columns x rows
QPoint m_currentPos; // column, row
QVector<typename IteratorStrategy::IteratorTypeSP> m_iterators;
typename IteratorStrategy::IteratorTypeSP m_currentIterator;
IteratorStrategy m_strategy;
};
#endif /* __KIS_WRAPPED_LINE_ITERATOR_BASE_H */
diff --git a/libs/image/kis_wrapped_random_accessor.cpp b/libs/image/kis_wrapped_random_accessor.cpp
index 814bc2822a..7c5efc09da 100644
--- a/libs/image/kis_wrapped_random_accessor.cpp
+++ b/libs/image/kis_wrapped_random_accessor.cpp
@@ -1,76 +1,77 @@
/*
* 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_wrapped_random_accessor.h"
#include "kis_wrapped_rect.h"
KisWrappedRandomAccessor::KisWrappedRandomAccessor(KisTiledDataManager *ktm,
qint32 x, qint32 y,
qint32 offsetX, qint32 offsetY,
bool writable,
+ KisIteratorCompleteListener *completeListener,
const QRect &wrapRect)
- : KisRandomAccessor2(ktm, x, y, offsetX, offsetY, writable),
+ : KisRandomAccessor2(ktm, x, y, offsetX, offsetY, writable, completeListener),
m_wrapRect(wrapRect),
m_currentPos(x, y)
{
}
void KisWrappedRandomAccessor::moveTo(qint32 x, qint32 y)
{
m_currentPos = QPoint(x, y);
x = KisWrappedRect::xToWrappedX(x, m_wrapRect);
y = KisWrappedRect::yToWrappedY(y, m_wrapRect);
KisRandomAccessor2::moveTo(x, y);
}
qint32 KisWrappedRandomAccessor::numContiguousColumns(qint32 x) const
{
x = KisWrappedRect::xToWrappedX(x, m_wrapRect);
qint32 distanceToBorder = m_wrapRect.x() + m_wrapRect.width() - x;
return qMin(distanceToBorder, KisRandomAccessor2::numContiguousColumns(x));
}
qint32 KisWrappedRandomAccessor::numContiguousRows(qint32 y) const
{
y = KisWrappedRect::yToWrappedY(y, m_wrapRect);
qint32 distanceToBorder = m_wrapRect.y() + m_wrapRect.height() - y;
return qMin(distanceToBorder, KisRandomAccessor2::numContiguousRows(y));
}
qint32 KisWrappedRandomAccessor::rowStride(qint32 x, qint32 y) const
{
x = KisWrappedRect::xToWrappedX(x, m_wrapRect);
y = KisWrappedRect::yToWrappedY(y, m_wrapRect);
return KisRandomAccessor2::rowStride(x, y);
}
qint32 KisWrappedRandomAccessor::x() const
{
return m_currentPos.x();
}
qint32 KisWrappedRandomAccessor::y() const
{
return m_currentPos.y();
}
diff --git a/libs/image/kis_wrapped_random_accessor.h b/libs/image/kis_wrapped_random_accessor.h
index 374f2a4e82..670332fc53 100644
--- a/libs/image/kis_wrapped_random_accessor.h
+++ b/libs/image/kis_wrapped_random_accessor.h
@@ -1,47 +1,48 @@
/*
* 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_WRAPPED_RANDOM_ACCESSOR_H
#define __KIS_WRAPPED_RANDOM_ACCESSOR_H
#include "tiles3/kis_random_accessor.h"
class KisWrappedRandomAccessor : public KisRandomAccessor2
{
public:
KisWrappedRandomAccessor(KisTiledDataManager *ktm,
qint32 x, qint32 y,
qint32 offsetX, qint32 offsetY,
bool writable,
+ KisIteratorCompleteListener *completeListener,
const QRect &wrapRect);
void moveTo(qint32 x, qint32 y);
qint32 numContiguousColumns(qint32 x) const;
qint32 numContiguousRows(qint32 y) const;
qint32 rowStride(qint32 x, qint32 y) const;
qint32 x() const;
qint32 y() const;
private:
QRect m_wrapRect;
QPoint m_currentPos;
};
#endif /* __KIS_WRAPPED_RANDOM_ACCESSOR_H */
diff --git a/libs/image/kis_wrapped_vline_iterator.h b/libs/image/kis_wrapped_vline_iterator.h
index 78d65df63c..b0d5c2a60f 100644
--- a/libs/image/kis_wrapped_vline_iterator.h
+++ b/libs/image/kis_wrapped_vline_iterator.h
@@ -1,127 +1,129 @@
/*
* 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_WRAPPED_VLINE_ITERATOR_H
#define __KIS_WRAPPED_VLINE_ITERATOR_H
#include "kis_iterator_ng.h"
#include "kis_wrapped_rect.h"
class WrappedVLineIteratorStrategy
{
private:
static const int TOP_OFFSET = 0;
static const int BOTTOM_OFFSET = 2;
public:
typedef KisVLineIteratorSP IteratorTypeSP;
WrappedVLineIteratorStrategy()
: m_iteratorColumnStart(KisWrappedRect::TOPLEFT),
m_lastColumnCoord(-1)
{
}
inline QSize originalRectToColumnsRows(const QRect &rect) {
return QSize(rect.height(), rect.width());
}
inline QPoint columnRowToXY(const QPoint &pt) const {
return QPoint(pt.y(), pt.x());
}
inline IteratorTypeSP createIterator(KisDataManager *dataManager,
const QRect &rc,
qint32 offsetX, qint32 offsetY,
- bool writable) {
+ bool writable,
+ KisIteratorCompleteListener *completeListener) {
return new KisVLineIterator2(dataManager,
rc.x(), rc.y(),
rc.height(),
offsetX, offsetY,
- writable);
+ writable,
+ completeListener);
}
inline void completeInitialization(QVector<IteratorTypeSP> *iterators,
KisWrappedRect *splitRect) {
m_splitRect = splitRect;
m_iterators = iterators;
m_lastColumnCoord = m_splitRect->topLeft().right();
}
inline IteratorTypeSP leftColumnIterator() const {
return m_iterators->at(m_iteratorColumnStart + TOP_OFFSET);
}
inline IteratorTypeSP rightColumnIterator() const {
return m_iterators->at(m_iteratorColumnStart + BOTTOM_OFFSET);
}
inline bool trySwitchIteratorStripe() {
bool needSwitching = leftColumnIterator()->x() == m_lastColumnCoord;
if (needSwitching) {
if (m_iteratorColumnStart == KisWrappedRect::TOPLEFT &&
m_iterators->at(KisWrappedRect::TOPRIGHT)) {
m_iteratorColumnStart = KisWrappedRect::TOPRIGHT;
m_lastColumnCoord = m_splitRect->topRight().right();
} else /* if (m_iteratorColumnStart == KisWrappedRect::TOPRIGHT) */ {
m_iteratorColumnStart = KisWrappedRect::TOPLEFT;
m_lastColumnCoord = m_splitRect->topLeft().right();
Q_FOREACH (IteratorTypeSP it, *m_iterators) {
if (it) {
it->resetColumnPos();
}
}
}
}
return needSwitching;
}
inline void iteratorsToNextRow() {
leftColumnIterator()->nextColumn();
if (rightColumnIterator()) {
rightColumnIterator()->nextColumn();
}
}
inline bool trySwitchColumnForced() {
leftColumnIterator()->resetPixelPos();
if (rightColumnIterator()) {
rightColumnIterator()->resetPixelPos();
}
return true;
}
private:
KisWrappedRect *m_splitRect;
QVector<IteratorTypeSP> *m_iterators;
int m_iteratorColumnStart; // may be either KisWrappedRect::TOPLEFT or KisWrappedRect::TOPRIGHT
int m_lastColumnCoord;
};
#include "kis_wrapped_line_iterator_base.h"
typedef KisWrappedLineIteratorBase<WrappedVLineIteratorStrategy, KisVLineIteratorNG> KisWrappedVLineIterator;
#endif /* __KIS_WRAPPED_VLINE_ITERATOR_H */
diff --git a/libs/image/krita_utils.cpp b/libs/image/krita_utils.cpp
index 25b1d220e2..bd419d1367 100644
--- a/libs/image/krita_utils.cpp
+++ b/libs/image/krita_utils.cpp
@@ -1,555 +1,444 @@
/*
* 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 <boost/accumulators/accumulators.hpp>
-#include <boost/accumulators/statistics/stats.hpp>
-#include <boost/accumulators/statistics/min.hpp>
-#include <boost/accumulators/statistics/max.hpp>
#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"
namespace KritaUtils
{
QSize optimalPatchSize()
{
KisImageConfig cfg;
return QSize(cfg.updatePatchWidth(),
cfg.updatePatchHeight());
}
QVector<QRect> splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
{
QVector<QRect> patches;
qint32 firstCol = rc.x() / patchSize.width();
qint32 firstRow = rc.y() / patchSize.height();
qint32 lastCol = (rc.x() + rc.width()) / patchSize.width();
qint32 lastRow = (rc.y() + rc.height()) / patchSize.height();
for(qint32 i = firstRow; i <= lastRow; i++) {
for(qint32 j = firstCol; j <= lastCol; j++) {
QRect maxPatchRect(j * patchSize.width(), i * patchSize.height(),
patchSize.width(), patchSize.height());
QRect patchRect = rc & maxPatchRect;
if (!patchRect.isEmpty()) {
patches.append(patchRect);
}
}
}
return patches;
}
QVector<QRect> splitRegionIntoPatches(const QRegion &region, const QSize &patchSize)
{
QVector<QRect> patches;
Q_FOREACH (const QRect rect, region.rects()) {
patches << KritaUtils::splitRectIntoPatches(rect, patchSize);
}
return patches;
}
- template <class Rect, class Point>
- QVector<Point> sampleRectWithPoints(const Rect &rect)
- {
- QVector<Point> points;
-
- Point m1 = 0.5 * (rect.topLeft() + rect.topRight());
- Point m2 = 0.5 * (rect.bottomLeft() + rect.bottomRight());
-
- points << rect.topLeft();
- points << m1;
- points << rect.topRight();
-
- points << 0.5 * (rect.topLeft() + rect.bottomLeft());
- points << 0.5 * (m1 + m2);
- points << 0.5 * (rect.topRight() + rect.bottomRight());
-
- points << rect.bottomLeft();
- points << m2;
- points << rect.bottomRight();
-
- return points;
- }
-
- QVector<QPoint> sampleRectWithPoints(const QRect &rect)
- {
- return sampleRectWithPoints<QRect, QPoint>(rect);
- }
-
- QVector<QPointF> sampleRectWithPoints(const QRectF &rect)
- {
- return sampleRectWithPoints<QRectF, QPointF>(rect);
- }
-
-
- template <class Rect, class Point, bool alignPixels>
- Rect approximateRectFromPointsImpl(const QVector<Point> &points)
- {
- using namespace boost::accumulators;
- accumulator_set<qreal, stats<tag::min, tag::max > > accX;
- accumulator_set<qreal, stats<tag::min, tag::max > > accY;
-
- Q_FOREACH (const Point &pt, points) {
- accX(pt.x());
- accY(pt.y());
- }
-
- Rect resultRect;
-
- if (alignPixels) {
- resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
- std::ceil(max(accX)), std::ceil(max(accY)));
- } else {
- resultRect.setCoords(min(accX), min(accY),
- max(accX), max(accY));
- }
-
- return resultRect;
- }
-
- QRect approximateRectFromPoints(const QVector<QPoint> &points)
- {
- return approximateRectFromPointsImpl<QRect, QPoint, true>(points);
- }
-
- QRectF approximateRectFromPoints(const QVector<QPointF> &points)
- {
- return approximateRectFromPointsImpl<QRectF, QPointF, false>(points);
- }
-
- QRect approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func)
- {
- QVector<QPoint> points = KritaUtils::sampleRectWithPoints(rect);
-
- using namespace boost::accumulators;
- accumulator_set<qreal, stats<tag::min, tag::max > > accX;
- accumulator_set<qreal, stats<tag::min, tag::max > > accY;
-
- Q_FOREACH (const QPoint &pt, points) {
- QPointF dstPt = func(pt);
-
- accX(dstPt.x());
- accY(dstPt.y());
- }
-
- QRect resultRect;
- resultRect.setCoords(std::floor(min(accX)), std::floor(min(accY)),
- std::ceil(max(accX)), std::ceil(max(accY)));
-
- return resultRect;
- }
-
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;
}
- void KRITAIMAGE_EXPORT initAntsPen(QPen *antsPen, QPen *outlinePen,
- int antLength, int antSpace)
- {
- QVector<qreal> antDashPattern;
- antDashPattern << antLength << antSpace;
-
- *antsPen = QPen(Qt::CustomDashLine);
- antsPen->setDashPattern(antDashPattern);
- antsPen->setCosmetic(true);
- antsPen->setColor(Qt::black);
-
- *outlinePen = QPen(Qt::SolidLine);
- outlinePen->setCosmetic(true);
- outlinePen->setColor(Qt::white);
- }
-
QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value)
{
return QString("%1").arg(value, 6, 'f', 1);
}
qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
{
qreal maxDimension = qMax(bounds.width(), bounds.height());
return qMax(portion * maxDimension, minValue);
}
bool tryMergePoints(QPainterPath &path,
const QPointF &startPoint,
const QPointF &endPoint,
qreal &distance,
qreal distanceThreshold,
bool lastSegment)
{
qreal length = (endPoint - startPoint).manhattanLength();
if (lastSegment || length > distanceThreshold) {
if (lastSegment) {
qreal wrappedLength =
(endPoint - QPointF(path.elementAt(0))).manhattanLength();
if (length < distanceThreshold ||
wrappedLength < distanceThreshold) {
return true;
}
}
distance = 0;
return false;
}
distance += length;
if (distance > distanceThreshold) {
path.lineTo(endPoint);
distance = 0;
}
return true;
}
QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
{
QPainterPath newPath;
QPointF startPoint;
qreal distance = 0;
int count = path.elementCount();
for (int i = 0; i < count; i++) {
QPainterPath::Element e = path.elementAt(i);
QPointF endPoint = QPointF(e.x, e.y);
switch (e.type) {
case QPainterPath::MoveToElement:
newPath.moveTo(endPoint);
break;
case QPainterPath::LineToElement:
if (!tryMergePoints(newPath, startPoint, endPoint,
distance, lengthThreshold, i == count - 1)) {
newPath.lineTo(endPoint);
}
break;
case QPainterPath::CurveToElement: {
Q_ASSERT(i + 2 < count);
if (!tryMergePoints(newPath, startPoint, endPoint,
distance, lengthThreshold, i == count - 1)) {
e = path.elementAt(i + 1);
Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
QPointF ctrl1 = QPointF(e.x, e.y);
e = path.elementAt(i + 2);
Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
QPointF ctrl2 = QPointF(e.x, e.y);
newPath.cubicTo(ctrl1, ctrl2, endPoint);
}
i += 2;
}
default:
;
}
startPoint = endPoint;
}
return newPath;
}
QList<QPainterPath> splitDisjointPaths(const QPainterPath &path)
{
QList<QPainterPath> resultList;
QList<QPolygonF> inputPolygons = path.toSubpathPolygons();
Q_FOREACH (const QPolygonF &poly, inputPolygons) {
QPainterPath testPath;
testPath.addPolygon(poly);
if (resultList.isEmpty()) {
resultList.append(testPath);
continue;
}
QPainterPath mergedPath = testPath;
for (auto it = resultList.begin(); it != resultList.end(); /*noop*/) {
if (it->intersects(testPath)) {
mergedPath.addPath(*it);
it = resultList.erase(it);
} else {
++it;
}
}
resultList.append(mergedPath);
}
return resultList;
}
quint8 mergeOpacity(quint8 opacity, quint8 parentOpacity)
{
if (parentOpacity != OPACITY_OPAQUE_U8) {
opacity = (int(opacity) * parentOpacity) / OPACITY_OPAQUE_U8;
}
return opacity;
}
QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags)
{
QBitArray flags = childFlags;
if (!flags.isEmpty() &&
!parentFlags.isEmpty() &&
flags.size() == parentFlags.size()) {
flags &= parentFlags;
} else if (!parentFlags.isEmpty()) {
flags = parentFlags;
}
return flags;
}
bool compareChannelFlags(QBitArray f1, QBitArray f2)
{
if (f1.isNull() && f2.isNull()) return true;
if (f1.isNull()) {
f1.fill(true, f2.size());
}
if (f2.isNull()) {
f2.fill(true, f1.size());
}
return f1 == f2;
}
QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value) {
return value ? i18n("on") : i18n("off");
}
KisNodeSP nearestNodeAfterRemoval(KisNodeSP node)
{
KisNodeSP newNode = node->nextSibling();
if (!newNode) {
newNode = node->prevSibling();
}
if (!newNode) {
newNode = node->parent();
}
return newNode;
}
void renderExactRect(QPainter *p, const QRect &rc)
{
p->drawRect(rc.adjusted(0,0,-1,-1));
}
void renderExactRect(QPainter *p, const QRect &rc, const QPen &pen)
{
QPen oldPen = p->pen();
p->setPen(pen);
renderExactRect(p, rc);
p->setPen(oldPen);
}
QImage convertQImageToGrayA(const QImage &image)
{
QImage dstImage(image.size(), QImage::Format_ARGB32);
// TODO: if someone feel bored, a more optimized version of this would be welcome
const QSize size = image.size();
for(int i = 0; i < size.height(); ++i) {
for(int j = 0; j < size.width(); ++j) {
const QRgb pixel = image.pixel(i,j);
const int gray = qGray(pixel);
dstImage.setPixel(i, j, qRgba(gray, gray, gray, qAlpha(pixel)));
}
}
return dstImage;
}
QColor blendColors(const QColor &c1, const QColor &c2, qreal r1)
{
const qreal r2 = 1.0 - r1;
return QColor::fromRgbF(
c1.redF() * r1 + c2.redF() * r2,
c1.greenF() * r1 + c2.greenF() * r2,
c1.blueF() * r1 + c2.blueF() * r2);
}
void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func) {
KisSequentialConstIterator dstIt(dev, rc);
do {
const quint8 *dstPtr = dstIt.rawDataConst();
func(*dstPtr);
} while (dstIt.nextPixel());
}
void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func) {
KisSequentialIterator dstIt(dev, rc);
do {
quint8 *dstPtr = dstIt.rawData();
*dstPtr = func(*dstPtr);
} while (dstIt.nextPixel());
}
qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion) {
const KoColorSpace *cs = dev->colorSpace();
const qreal linearPortion = std::sqrt(samplePortion);
const qreal ratio = qreal(rect.width()) / rect.height();
const int xStep = qMax(1, qRound(1.0 / linearPortion * ratio));
const int yStep = qMax(1, qRound(1.0 / linearPortion / ratio));
int numTransparentPixels = 0;
int numPixels = 0;
KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(rect.x(), rect.y());
for (int y = rect.y(); y <= rect.bottom(); y += yStep) {
for (int x = rect.x(); x <= rect.right(); x += xStep) {
it->moveTo(x, y);
const quint8 alpha = cs->opacityU8(it->rawDataConst());
if (alpha != OPACITY_OPAQUE_U8) {
numTransparentPixels++;
}
numPixels++;
}
}
return qreal(numTransparentPixels) / numPixels;
}
}
diff --git a/libs/image/krita_utils.h b/libs/image/krita_utils.h
index 6e40349dcc..45e6fab3e0 100644
--- a/libs/image/krita_utils.h
+++ b/libs/image/krita_utils.h
@@ -1,143 +1,101 @@
/*
* 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;
#include <QVector>
#include "kritaimage_export.h"
#include "kis_types.h"
+#include "krita_container_utils.h"
#include <functional>
namespace KritaUtils
{
QSize KRITAIMAGE_EXPORT optimalPatchSize();
QVector<QRect> KRITAIMAGE_EXPORT splitRectIntoPatches(const QRect &rc, const QSize &patchSize);
QVector<QRect> KRITAIMAGE_EXPORT splitRegionIntoPatches(const QRegion &region, const QSize &patchSize);
- QVector<QPoint> KRITAIMAGE_EXPORT sampleRectWithPoints(const QRect &rect);
- QVector<QPointF> KRITAIMAGE_EXPORT sampleRectWithPoints(const QRectF &rect);
-
- QRect KRITAIMAGE_EXPORT approximateRectFromPoints(const QVector<QPoint> &points);
- QRectF KRITAIMAGE_EXPORT approximateRectFromPoints(const QVector<QPointF> &points);
-
- QRect KRITAIMAGE_EXPORT approximateRectWithPointTransform(const QRect &rect, std::function<QPointF(QPointF)> func);
-
QRegion KRITAIMAGE_EXPORT splitTriangles(const QPointF &center,
const QVector<QPointF> &points);
QRegion KRITAIMAGE_EXPORT splitPath(const QPainterPath &path);
- void KRITAIMAGE_EXPORT initAntsPen(QPen *antsPen, QPen *outlinePen,
- int antLength = 4, int antSpace = 4);
-
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);
- template <class T>
- bool compareListsUnordered(const QList<T> &a, const QList<T> &b) {
- if (a.size() != b.size()) return false;
-
- Q_FOREACH(const T &t, a) {
- if (!b.contains(t)) return false;
- }
-
- return true;
- }
-
- template <class C>
- void makeContainerUnique(C &container) {
- std::sort(container.begin(), container.end());
- auto newEnd = std::unique(container.begin(), container.end());
-
- while (newEnd != container.end()) {
- newEnd = container.erase(newEnd);
- }
- }
-
-
- template <class C>
- void filterContainer(C &container, std::function<bool(typename C::reference)> keepIf) {
-
- auto newEnd = std::remove_if(container.begin(), container.end(), std::unary_negate<decltype(keepIf)>(keepIf));
- while (newEnd != container.end()) {
- newEnd = container.erase(newEnd);
- }
- }
-
-
/**
* When drawing a rect Qt uses quite a weird algorithm. It
* draws 4 lines:
* o at X-es: rect.x() and rect.right() + 1
* o at Y-s: rect.y() and rect.bottom() + 1
*
* Which means that bottom and right lines of the rect are painted
* outside the virtual rectangle the rect defines. This methods overcome this issue by
* painting the adjusted rect.
*/
void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc);
/**
* \see renderExactRect(QPainter *p, const QRect &rc)
*/
void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc, const QPen &pen);
QImage KRITAIMAGE_EXPORT convertQImageToGrayA(const QImage &image);
QColor KRITAIMAGE_EXPORT blendColors(const QColor &c1, const QColor &c2, qreal r1);
void KRITAIMAGE_EXPORT applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func);
void KRITAIMAGE_EXPORT filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func);
qreal KRITAIMAGE_EXPORT estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion);
}
#endif /* __KRITA_UTILS_H */
diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt
index 69d13d15d2..8eb9ffd5f2 100644
--- a/libs/image/tests/CMakeLists.txt
+++ b/libs/image/tests/CMakeLists.txt
@@ -1,237 +1,237 @@
# cmake in some versions for some not yet known reasons fails to run automoc
# on random targets (changing target names already has an effect)
# As temporary workaround skipping build of tests on these versions for now
# See https://mail.kde.org/pipermail/kde-buildsystem/2015-June/010819.html
# extend range of affected cmake versions as needed
if(NOT ${CMAKE_VERSION} VERSION_LESS 3.1.3 AND
NOT ${CMAKE_VERSION} VERSION_GREATER 3.2.3)
message(WARNING "Skipping krita/image/tests, CMake in at least versions 3.1.3 - 3.2.3 seems to have a problem with automoc. \n(FRIENDLY REMINDER: PLEASE DON'T BREAK THE TESTS!)")
set (HAVE_FAILING_CMAKE TRUE)
else()
set (HAVE_FAILING_CMAKE FALSE)
endif()
include_directories(
${CMAKE_SOURCE_DIR}/libs/image/metadata
${CMAKE_BINARY_DIR}/libs/image/
${CMAKE_SOURCE_DIR}/libs/image/
${CMAKE_SOURCE_DIR}/libs/image/brushengine
${CMAKE_SOURCE_DIR}/libs/image/tiles3
${CMAKE_SOURCE_DIR}/libs/image/tiles3/swap
${CMAKE_SOURCE_DIR}/sdk/tests
)
include_Directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
if(HAVE_VC)
include_directories(${Vc_INCLUDE_DIR})
endif()
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
macro_add_unittest_definitions()
set(KisRandomGeneratorDemoSources kis_random_generator_demo.cpp kimageframe.cpp)
ki18n_wrap_ui(KisRandomGeneratorDemoSources kis_random_generator_demo.ui)
add_executable(KisRandomGeneratorDemo ${KisRandomGeneratorDemoSources})
target_link_libraries(KisRandomGeneratorDemo kritaimage)
ecm_mark_as_test(KisRandomGeneratorDemo)
ecm_add_tests(
kis_base_node_test.cpp
kis_fast_math_test.cpp
kis_node_test.cpp
kis_node_facade_test.cpp
kis_fixed_paint_device_test.cpp
kis_layer_test.cpp
kis_effect_mask_test.cpp
kis_iterator_test.cpp
kis_painter_test.cpp
kis_selection_test.cpp
kis_count_visitor_test.cpp
kis_projection_test.cpp
kis_properties_configuration_test.cpp
kis_transaction_test.cpp
kis_pixel_selection_test.cpp
kis_group_layer_test.cpp
kis_paint_layer_test.cpp
kis_adjustment_layer_test.cpp
kis_annotation_test.cpp
kis_change_profile_visitor_test.cpp
kis_clone_layer_test.cpp
kis_colorspace_convert_visitor_test.cpp
kis_convolution_painter_test.cpp
kis_crop_processing_visitor_test.cpp
kis_processing_applicator_test.cpp
kis_datamanager_test.cpp
kis_fill_painter_test.cpp
kis_filter_configuration_test.cpp
kis_filter_test.cpp
kis_filter_processing_information_test.cpp
kis_filter_registry_test.cpp
kis_filter_strategy_test.cpp
kis_gradient_painter_test.cpp
kis_image_commands_test.cpp
kis_image_test.cpp
kis_image_signal_router_test.cpp
kis_iterators_ng_test.cpp
kis_iterator_benchmark.cpp
kis_updater_context_test.cpp
kis_simple_update_queue_test.cpp
kis_stroke_test.cpp
kis_simple_stroke_strategy_test.cpp
kis_stroke_strategy_undo_command_based_test.cpp
kis_strokes_queue_test.cpp
kis_macro_test.cpp
kis_mask_test.cpp
kis_math_toolbox_test.cpp
kis_name_server_test.cpp
kis_node_commands_test.cpp
kis_node_graph_listener_test.cpp
kis_node_visitor_test.cpp
kis_paint_information_test.cpp
kis_paintop_test.cpp
kis_pattern_test.cpp
kis_recorded_action_factory_registry_test.cpp
kis_recorded_action_test.cpp
kis_recorded_filter_action_test.cpp
kis_selection_mask_test.cpp
kis_shared_ptr_test.cpp
kis_bsplines_test.cpp
kis_warp_transform_worker_test.cpp
kis_liquify_transform_worker_test.cpp
kis_transparency_mask_test.cpp
kis_types_test.cpp
kis_vec_test.cpp
kis_filter_config_widget_test.cpp
kis_mask_generator_test.cpp
kis_cubic_curve_test.cpp
kis_node_query_path_test.cpp
kis_fixed_point_maths_test.cpp
kis_filter_weights_buffer_test.cpp
kis_filter_weights_applicator_test.cpp
kis_fill_interval_test.cpp
kis_fill_interval_map_test.cpp
kis_scanline_fill_test.cpp
kis_psd_layer_style_test.cpp
kis_layer_style_projection_plane_test.cpp
kis_lod_capable_layer_offset_test.cpp
kis_algebra_2d_test.cpp
kis_marker_painter_test.cpp
kis_lazy_brush_test.cpp
kis_colorize_mask_test.cpp
NAME_PREFIX "krita-image-"
LINK_LIBRARIES kritaimage Qt5::Test)
ecm_add_test(kis_layer_style_filter_environment_test.cpp
TEST_NAME kritaimage-layer_style_filter_environment_test
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test)
ecm_add_test(kis_asl_parser_test.cpp
TEST_NAME kritalibpsd-asl_parser_test
- LINK_LIBRARIES kritapsd kritapigment kritawidgetutils Qt5::Xml Qt5::Test)
+ LINK_LIBRARIES kritapsd kritapigment kritawidgetutils kritacommand Qt5::Xml Qt5::Test)
# ecm_add_test(kis_dom_utils_test.cpp
# TEST_NAME krita-image-DomUtils-Test
# LINK_LIBRARIES kritaimage Qt5::Test)
# kisdoc dep
# kis_transform_worker_test.cpp
# TEST_NAME krita-image-KisTransformWorkerTest
#LINK_LIBRARIES kritaimage Qt5::Test)
# kisdoc
# kis_perspective_transform_worker_test.cpp
# TEST_NAME krita-image-KisPerspectiveTransformWorkerTest
#LINK_LIBRARIES kritaimage Qt5::Test)
# kis_cs_conversion_test.cpp
# TEST_NAME krita-image-KisCsConversionTest
# LINK_LIBRARIES kritaimage Qt5::Test)
# kisdoc
# kis_processings_test.cpp
# TEST_NAME krita-image-KisProcessingsTest
#LINK_LIBRARIES kritaimage Qt5::Test)
# image/tests cannot use stuff that needs kisdocument
# kis_projection_leaf_test.cpp
# TEST_NAME kritaimage-projection_leaf_test
# LINK_LIBRARIES kritaimage Qt5::Test)
if (NOT HAVE_FAILING_CMAKE)
krita_add_broken_unit_test(kis_paint_device_test.cpp
TEST_NAME krita-image-KisPaintDeviceTest
LINK_LIBRARIES kritaimage kritaodf Qt5::Test)
else()
message(WARNING "Skipping KisPaintDeviceTest!!!!!!!!!!!!!!")
endif()
if (NOT HAVE_FAILING_CMAKE)
krita_add_broken_unit_test(kis_filter_mask_test.cpp
TEST_NAME krita-image-KisFilterMaskTest
LINK_LIBRARIES kritaimage Qt5::Test)
else()
message(WARNING "Skipping KisFilterMaskTest!!!!!!!!!!!!!!")
endif()
krita_add_broken_unit_test(kis_transform_mask_test.cpp
TEST_NAME krita-image-KisTransformMaskTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_histogram_test.cpp
TEST_NAME krita-image-KisHistogramTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_walkers_test.cpp
TEST_NAME krita-image-KisWalkersTest
LINK_LIBRARIES kritaimage Qt5::Test)
#krita_add_broken_unit_test(kis_async_merger_test.cpp
# TEST_NAME krita-image-KisAsyncMergerTest
# LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_update_scheduler_test.cpp
TEST_NAME krita-image-KisUpdateSchedulerTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_queues_progress_updater_test.cpp
TEST_NAME krita-image-KisQueuesProgressUpdaterTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_cage_transform_worker_test.cpp
TEST_NAME krita-image-KisCageTransformWorkerTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_meta_data_test.cpp
TEST_NAME krita-image-KisMetaDataTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_random_generator_test.cpp
TEST_NAME krita-image-KisRandomGeneratorTest
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_keyframing_test.cpp
TEST_NAME krita-image-Keyframing-Test
LINK_LIBRARIES kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_image_animation_interface_test.cpp
TEST_NAME krita-image-ImageAnimationInterface-Test
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_onion_skin_compositor_test.cpp
TEST_NAME krita-image-OnionSkinCompositor-Test
LINK_LIBRARIES ${KDE4_KDEUI_LIBS} kritaimage Qt5::Test)
krita_add_broken_unit_test(kis_layer_styles_test.cpp
TEST_NAME krita-image-LayerStylesTest
LINK_LIBRARIES kritaimage Qt5::Test)
diff --git a/libs/image/tests/kis_algebra_2d_test.cpp b/libs/image/tests/kis_algebra_2d_test.cpp
index 43fa64cf1c..d7864d27de 100644
--- a/libs/image/tests/kis_algebra_2d_test.cpp
+++ b/libs/image/tests/kis_algebra_2d_test.cpp
@@ -1,155 +1,190 @@
/*
* 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_algebra_2d_test.h"
#include <QTest>
#include "kis_algebra_2d.h"
#include "kis_debug.h"
namespace KisAlgebra2D {
}
void KisAlgebra2DTest::testHalfPlane()
{
{
QPointF a(10,10);
QPointF b(5,5);
KisAlgebra2D::RightHalfPlane p(a, b);
QVERIFY(p.value(QPointF(7, 5)) > 0);
QVERIFY(p.value(QPointF(3, 5)) < 0);
QVERIFY(p.value(QPointF(3, 3)) == 0);
}
{
QPointF a(10,10);
QPointF b(15,10);
KisAlgebra2D::RightHalfPlane p(a, b);
QCOMPARE(p.value(QPointF(3, 5)), -5.0);
QCOMPARE(p.value(QPointF(500, 15)), 5.0);
QCOMPARE(p.value(QPointF(1000, 10)), 0.0);
QCOMPARE(p.valueSq(QPointF(3, 5)), -25.0);
QCOMPARE(p.valueSq(QPointF(500, 15)), 25.0);
QCOMPARE(p.valueSq(QPointF(1000, 10)), 0.0);
QCOMPARE(p.pos(QPointF(3, 5)), -1);
QCOMPARE(p.pos(QPointF(500, 15)), 1);
QCOMPARE(p.pos(QPointF(1000, 10)), 0);
}
}
void KisAlgebra2DTest::testOuterCircle()
{
QPointF a(10,10);
KisAlgebra2D::OuterCircle p(a, 5);
QVERIFY(p.value(QPointF(3, 5)) > 0);
QVERIFY(p.value(QPointF(7, 7)) < 0);
QVERIFY(p.value(QPointF(10, 5)) == 0);
QCOMPARE(p.value(QPointF(10, 12)), -3.0);
QCOMPARE(p.value(QPointF(10, 15)), 0.0);
QCOMPARE(p.value(QPointF(10, 17)), 2.0);
}
void KisAlgebra2DTest::testQuadraticEquation()
{
int result = 0;
qreal x1 = 0;
qreal x2 = 0;
result = KisAlgebra2D::quadraticEquation(3, -11, 6, &x1, &x2);
QCOMPARE(result, 2);
QCOMPARE(x2, 2.0 / 3.0);
QCOMPARE(x1, 3.0);
result = KisAlgebra2D::quadraticEquation(9, -12, 4, &x1, &x2);
QCOMPARE(result, 1);
QCOMPARE(x1, 2.0 / 3.0);
result = KisAlgebra2D::quadraticEquation(9, -1, 4, &x1, &x2);
QCOMPARE(result, 0);
}
void KisAlgebra2DTest::testIntersections()
{
QVector<QPointF> points;
points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
QPointF(20,10), 5.0);
QCOMPARE(points.size(), 1);
QCOMPARE(points[0], QPointF(15, 10));
points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
QPointF(18,10), 5.0);
QCOMPARE(points.size(), 2);
QCOMPARE(points[0], QPointF(14, 13));
QCOMPARE(points[1], QPointF(14, 7));
points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
QPointF(10,20), 5.0);
QCOMPARE(points.size(), 1);
QCOMPARE(points[0], QPointF(10, 15));
points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
QPointF(10,18), 5.0);
QCOMPARE(points.size(), 2);
QCOMPARE(points[0], QPointF(7, 14));
QCOMPARE(points[1], QPointF(13, 14));
points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
QPointF(17,17), 5.0);
QCOMPARE(points.size(), 2);
QCOMPARE(points[0], QPointF(13, 14));
QCOMPARE(points[1], QPointF(14, 13));
points = KisAlgebra2D::intersectTwoCircles(QPointF(10,10), 5.0,
QPointF(10,100), 5.0);
QCOMPARE(points.size(), 0);
}
void KisAlgebra2DTest::testWeirdIntersections()
{
QVector<QPointF> points;
QPointF c1 = QPointF(5369.14,3537.98);
QPointF c2 = QPointF(5370.24,3536.71);
qreal r1 = 8.5;
qreal r2 = 10;
points = KisAlgebra2D::intersectTwoCircles(c1, r1, c2, r2);
QCOMPARE(points.size(), 2);
//QCOMPARE(points[0], QPointF(15, 10));
}
+void KisAlgebra2DTest::testMatrixDecomposition1()
+{
+ QTransform Sh;
+ Sh.shear(0.2, 0);
+ QTransform R;
+ R.rotate(30);
+ const QTransform t0 =
+ QTransform::fromScale(0.5, -0.6) *
+ R * Sh *
+ QTransform::fromTranslate(100, 200);
+
+ KisAlgebra2D::DecomposedMatix matrix(t0);
+
+ QCOMPARE(matrix.isValid(), true);
+ QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4));
+}
+
+void KisAlgebra2DTest::testMatrixDecomposition2()
+{
+ QPolygonF poly;
+ poly << QPointF(-0.3,-0.3);
+ poly << QPointF(1,0);
+ poly << QPointF(0.8,0.8);
+ poly << QPointF(0,1);
+
+ QTransform t0;
+ bool valid = QTransform::squareToQuad(poly, t0);
+ QVERIFY(valid);
+
+ KisAlgebra2D::DecomposedMatix matrix(t0);
+
+ QCOMPARE(matrix.isValid(), true);
+ QVERIFY(KisAlgebra2D::fuzzyMatrixCompare(matrix.transform(), t0, 1e-4));
+}
+
QTEST_MAIN(KisAlgebra2DTest)
diff --git a/libs/image/tests/kis_algebra_2d_test.h b/libs/image/tests/kis_algebra_2d_test.h
index 14f7cd82d8..6edb0ff330 100644
--- a/libs/image/tests/kis_algebra_2d_test.h
+++ b/libs/image/tests/kis_algebra_2d_test.h
@@ -1,36 +1,39 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_ALGEBRA_2D_TEST_H
#define __KIS_ALGEBRA_2D_TEST_H
#include <QtTest/QtTest>
class KisAlgebra2DTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testHalfPlane();
void testOuterCircle();
void testQuadraticEquation();
void testIntersections();
void testWeirdIntersections();
+
+ void testMatrixDecomposition1();
+ void testMatrixDecomposition2();
};
#endif /* __KIS_ALGEBRA_2D_TEST_H */
diff --git a/libs/image/tests/kis_properties_configuration_test.cpp b/libs/image/tests/kis_properties_configuration_test.cpp
index 6c870a8fb0..87ca007a4e 100644
--- a/libs/image/tests/kis_properties_configuration_test.cpp
+++ b/libs/image/tests/kis_properties_configuration_test.cpp
@@ -1,96 +1,111 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_properties_configuration_test.h"
#include <QTest>
#include "kis_properties_configuration.h"
KisPropertiesConfigurationTest::KisPropertiesConfigurationTest() :
v1(10), v2("hello"), v3(1242.0), v4(true)
{
QList<QPointF> pts; pts.push_back(QPointF(0.2, 0.3)); pts.push_back(QPointF(0.5, 0.7));
v5.setPoints(pts);
}
void KisPropertiesConfigurationTest::testSerialization()
{
KisPropertiesConfigurationSP config = createConfig();
QString xml = config->toXML();
KisPropertiesConfigurationSP decodedConfig = new KisPropertiesConfiguration();
decodedConfig->fromXML(xml);
testConfig(decodedConfig);
}
void KisPropertiesConfigurationTest::testSetGet()
{
KisPropertiesConfigurationSP config = createConfig();
testConfig(config);
}
void KisPropertiesConfigurationTest::testDefaultValues()
{
KisPropertiesConfigurationSP config = new KisPropertiesConfiguration();
QVERIFY(config->getInt("bouh", v1) == v1);
QVERIFY(config->getString("bouh", v2) == v2);
QVERIFY(config->getDouble("bouh", v3) == v3);
QVERIFY(config->getBool("bouh", v4) == v4);
QVERIFY(config->getCubicCurve("bouh", v5) == v5);
}
KisPropertiesConfigurationSP KisPropertiesConfigurationTest::createConfig()
{
KisPropertiesConfigurationSP config = new KisPropertiesConfiguration();
config->setProperty("v1", v1);
config->setProperty("v2", v2);
config->setProperty("v3", v3);
config->setProperty("v4", v4);
config->setProperty("v5", qVariantFromValue(v5));
return config;
}
void KisPropertiesConfigurationTest::testConfig(KisPropertiesConfigurationSP config)
{
QVERIFY(config->getInt("v1", 0) == v1);
QVERIFY(config->getString("v2", QString()) == v2);
QVERIFY(config->getDouble("v3", 0.0) == v3);
QVERIFY(config->getBool("v4", !v4) == v4);
QVERIFY(config->getCubicCurve("v5") == v5);
}
void KisPropertiesConfigurationTest::testNotSavedValues()
{
KisPropertiesConfigurationSP config = createConfig();
config->setPropertyNotSaved("v3");
testConfig(config);
QString s = config->toXML();
config = new KisPropertiesConfiguration();
config->fromXML(s);
QVERIFY(config->getInt("v1", 0) == v1);
QVERIFY(config->getString("v2", QString()) == v2);
QVERIFY(config->hasProperty("v3") == false);
QVERIFY(config->getBool("v4", !v4) == v4);
QVERIFY(config->getCubicCurve("v5") == v5);
}
+void KisPropertiesConfigurationTest::testCopy()
+{
+ KisPropertiesConfigurationSP p1 = createConfig();
+ p1->setProperty("v6", "bla");
+ p1->setPropertyNotSaved("v6");
+ KisPropertiesConfiguration config = KisPropertiesConfiguration(*p1.data());
+ QVERIFY(config.getInt("v1", 0) == v1);
+ QVERIFY(config.getString("v2", QString()) == v2);
+ QVERIFY(config.getDouble("v3", 0.0) == v3);
+ QVERIFY(config.getBool("v4", !v4) == v4);
+ QVERIFY(config.getCubicCurve("v5") == v5);
+ QVERIFY(config.hasProperty("v6") == false);
+
+}
+
QTEST_MAIN(KisPropertiesConfigurationTest)
diff --git a/libs/image/tests/kis_properties_configuration_test.h b/libs/image/tests/kis_properties_configuration_test.h
index c9b26d4551..7528c8cebe 100644
--- a/libs/image/tests/kis_properties_configuration_test.h
+++ b/libs/image/tests/kis_properties_configuration_test.h
@@ -1,49 +1,50 @@
/*
* 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 KIS_SERIALIZABLE_CONFIGURATION_TEST_H
#define KIS_SERIALIZABLE_CONFIGURATION_TEST_H
#include <QtTest>
#include "kis_cubic_curve.h"
#include "kis_properties_configuration.h"
class KisPropertiesConfigurationTest : public QObject
{
Q_OBJECT
public:
KisPropertiesConfigurationTest();
private Q_SLOTS:
void testSetGet();
void testSerialization();
void testDefaultValues();
void testNotSavedValues();
+ void testCopy();
private:
KisPropertiesConfigurationSP createConfig();
void testConfig(KisPropertiesConfigurationSP config);
private:
int v1;
QString v2;
double v3;
bool v4;
KisCubicCurve v5;
};
#endif
diff --git a/libs/image/tests/kis_warp_transform_worker_test.cpp b/libs/image/tests/kis_warp_transform_worker_test.cpp
index 01256a3264..94a57da1e6 100644
--- a/libs/image/tests/kis_warp_transform_worker_test.cpp
+++ b/libs/image/tests/kis_warp_transform_worker_test.cpp
@@ -1,352 +1,352 @@
/*
* 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_warp_transform_worker_test.h"
#include <QTest>
#include "testutil.h"
#include "kis_warptransform_worker.h"
#include <KoProgressUpdater.h>
struct WarpTransforWorkerData {
WarpTransforWorkerData() {
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
updater = pu.startSubtask();
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
// QImage image(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
QImage image(TestUtil::fetchDataFileLazy("test_transform_quality_second.png"));
dev = new KisPaintDevice(cs);
dev->convertFromQImage(image, 0);
alpha = 1.0;
bounds = dev->exactBounds();
origPoints << bounds.topLeft();
origPoints << bounds.topRight();
origPoints << bounds.bottomRight();
origPoints << bounds.bottomLeft();
origPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight());
origPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(-20, 0);
transfPoints << bounds.topLeft();
transfPoints << bounds.bottomLeft() + 0.6 * (bounds.topRight() - bounds.bottomLeft());
transfPoints << bounds.topLeft() + 0.8 * (bounds.bottomRight() - bounds.topLeft());
transfPoints << bounds.bottomLeft() + QPointF(200, 0);
transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(40,20);
transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(-20, 0) + QPointF(-40,20);
}
KisPaintDeviceSP dev;
QVector<QPointF> origPoints;
QVector<QPointF> transfPoints;
qreal alpha;
KoUpdaterPtr updater;
QRectF bounds;
};
void KisWarpTransformWorkerTest::test()
{
WarpTransforWorkerData d;
KisWarpTransformWorker worker(KisWarpTransformWorker::RIGID_TRANSFORM,
d.dev,
d.origPoints,
d.transfPoints,
d.alpha,
d.updater);
QBENCHMARK_ONCE {
worker.run();
}
QImage result = d.dev->convertToQImage(0);
TestUtil::checkQImage(result, "warp_transform_test", "simple", "tr");
}
void KisWarpTransformWorkerTest::testQImage()
{
TestUtil::TestProgressBar bar;
KoProgressUpdater pu(&bar);
KoUpdaterPtr updater = pu.startSubtask();
// QImage image(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
QImage image(TestUtil::fetchDataFileLazy("test_transform_quality_second.png"));
image = image.convertToFormat(QImage::Format_ARGB32);
dbgKrita << ppVar(image.format());
QVector<QPointF> origPoints;
QVector<QPointF> transfPoints;
qreal alpha = 1.0;
QRectF bounds(image.rect());
origPoints << bounds.topLeft();
origPoints << bounds.topRight();
origPoints << bounds.bottomRight();
origPoints << bounds.bottomLeft();
origPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight());
origPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(-20, 0);
transfPoints << bounds.topLeft();
transfPoints << bounds.bottomLeft() + 0.6 * (bounds.topRight() - bounds.bottomLeft());
transfPoints << bounds.topLeft() + 0.8 * (bounds.bottomRight() - bounds.topLeft());
transfPoints << bounds.bottomLeft() + QPointF(200, 0);
transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(40,20);
transfPoints << 0.5 * (bounds.bottomLeft() + bounds.bottomRight()) + QPointF(-20, 0) + QPointF(-40,20);
QImage result;
QPointF newOffset;
QBENCHMARK_ONCE {
result = KisWarpTransformWorker::transformQImage(
KisWarpTransformWorker::RIGID_TRANSFORM,
origPoints, transfPoints, alpha,
image, QPointF(), &newOffset);
}
dbgKrita << ppVar(newOffset);
TestUtil::checkQImage(result, "warp_transform_test", "qimage", "tr");
}
#include "kis_four_point_interpolator_forward.h"
void KisWarpTransformWorkerTest::testForwardInterpolator()
{
QPolygonF src;
src << QPointF(0, 0);
src << QPointF(100, 0);
src << QPointF(100, 100);
src << QPointF(0, 100);
QPolygonF dst;
dst << QPointF(0, 0);
dst << QPointF(100, 10);
dst << QPointF(100, 120);
dst << QPointF(0, 100);
KisFourPointInterpolatorForward interp(src, dst);
QCOMPARE(interp.map(QPointF(0,50)), QPointF(0,50));
QCOMPARE(interp.map(QPointF(50,0)), QPointF(50,5));
QCOMPARE(interp.map(QPointF(100,0)), QPointF(100,10));
QCOMPARE(interp.map(QPointF(100,50)), QPointF(100,65));
QCOMPARE(interp.map(QPointF(100,100)), QPointF(100,120));
QCOMPARE(interp.map(QPointF(50,100)), QPointF(50,110));
QCOMPARE(interp.map(QPointF(50,50)), QPointF(50,57.5));
}
#include "kis_four_point_interpolator_backward.h"
void KisWarpTransformWorkerTest::testBackwardInterpolatorXShear()
{
QPolygonF src;
src << QPointF(0, 0);
src << QPointF(100, 0);
src << QPointF(100, 100);
src << QPointF(0, 100);
QPolygonF dst;
dst << QPointF(0, 0);
dst << QPointF(100, 0);
dst << QPointF(120, 100);
dst << QPointF(10, 100);
KisFourPointInterpolatorBackward interp(src, dst);
QCOMPARE(interp.map(QPointF(10,100)), QPointF(0,100));
QCOMPARE(interp.map(QPointF(5,50)), QPointF(0,50));
QCOMPARE(interp.map(QPointF(110,50)), QPointF(100,50));
QCOMPARE(interp.map(QPointF(57.5,50)), QPointF(50,50));
}
void KisWarpTransformWorkerTest::testBackwardInterpolatorYShear()
{
QPolygonF src;
src << QPointF(0, 0);
src << QPointF(100, 0);
src << QPointF(100, 100);
src << QPointF(0, 100);
QPolygonF dst;
dst << QPointF(0, 0);
dst << QPointF(100, 10);
dst << QPointF(100, 120);
dst << QPointF(0, 100);
KisFourPointInterpolatorBackward interp(src, dst);
QCOMPARE(interp.map(QPointF(100,10)), QPointF(100,0));
QCOMPARE(interp.map(QPointF(50,5)), QPointF(50,0));
QCOMPARE(interp.map(QPointF(50,110)), QPointF(50,100));
QCOMPARE(interp.map(QPointF(50,57.5)), QPointF(50,50));
}
void KisWarpTransformWorkerTest::testBackwardInterpolatorXYShear()
{
QPolygonF src;
src << QPointF(0, 0);
src << QPointF(100, 0);
src << QPointF(100, 100);
src << QPointF(0, 100);
QPolygonF dst;
dst << QPointF(0, 0);
dst << QPointF(100, 10);
dst << QPointF(140, 120);
dst << QPointF(20, 100);
KisFourPointInterpolatorBackward interp(src, dst);
QCOMPARE(interp.map(QPointF(100,10)), QPointF(100,0));
QCOMPARE(interp.map(QPointF(50,5)), QPointF(50,0));
QCOMPARE(interp.map(QPointF(80,110)), QPointF(50,100));
QCOMPARE(interp.map(QPointF(120,65)), QPointF(100,50));
QCOMPARE(interp.map(QPointF(10,50)), QPointF(0,50));
}
void KisWarpTransformWorkerTest::testBackwardInterpolatorRoundTrip()
{
QPolygonF src;
src << QPointF(0, 0);
src << QPointF(100, 0);
src << QPointF(100, 100);
src << QPointF(0, 100);
QPolygonF dst;
dst << QPointF(100, 100);
dst << QPointF(20, 140);
dst << QPointF(10, 80);
dst << QPointF(15, 5);
KisFourPointInterpolatorForward f(src, dst);
KisFourPointInterpolatorBackward b(src, dst);
for (int y = 0; y <= 100; y += 1) {
for (int x = 0; x <= 100; x += 1) {
QPointF pt(x, y);
QPointF fwdPt = f.map(pt);
QPointF bwdPt = b.map(fwdPt);
//dbgKrita << "R:" << ppVar(pt) << ppVar(fwdPt) << ppVar(bwdPt) << (bwdPt - pt);
QVERIFY((bwdPt - pt).manhattanLength() < 1e-3);
}
}
}
#include "kis_grid_interpolation_tools.h"
void KisWarpTransformWorkerTest::testGridSize()
{
QCOMPARE(GridIterationTools::calcGridDimension(1, 7, 4), 3);
QCOMPARE(GridIterationTools::calcGridDimension(1, 8, 4), 3);
QCOMPARE(GridIterationTools::calcGridDimension(1, 9, 4), 4);
QCOMPARE(GridIterationTools::calcGridDimension(0, 7, 4), 3);
QCOMPARE(GridIterationTools::calcGridDimension(1, 8, 4), 3);
QCOMPARE(GridIterationTools::calcGridDimension(4, 9, 4), 3);
QCOMPARE(GridIterationTools::calcGridDimension(0, 9, 4), 4);
QCOMPARE(GridIterationTools::calcGridDimension(-1, 9, 4), 5);
QCOMPARE(GridIterationTools::calcGridDimension(0, 300, 8), 39);
}
void KisWarpTransformWorkerTest::testBackwardInterpolatorExtrapolation()
{
QPolygonF src;
src << QPointF(0, 0);
src << QPointF(100, 0);
src << QPointF(100, 100);
src << QPointF(0, 100);
QPolygonF dst(src);
std::rotate(dst.begin(), dst.begin() + 1, dst.end());
KisFourPointInterpolatorBackward interp(src, dst);
// standard checks
QCOMPARE(interp.map(QPointF(0,0)), QPointF(0,100));
QCOMPARE(interp.map(QPointF(100,0)), QPointF(0,0));
QCOMPARE(interp.map(QPointF(100,100)), QPointF(100,0));
QCOMPARE(interp.map(QPointF(0,100)), QPointF(100,100));
// extrapolate!
QCOMPARE(interp.map(QPointF(-10,0)), QPointF(0,110));
QCOMPARE(interp.map(QPointF(0,-10)), QPointF(-10,100));
QCOMPARE(interp.map(QPointF(-10,-10)), QPointF(-10,110));
QCOMPARE(interp.map(QPointF(110,0)), QPointF(0,-10));
QCOMPARE(interp.map(QPointF(100,-10)), QPointF(-10,0));
QCOMPARE(interp.map(QPointF(110,-10)), QPointF(-10,-10));
QCOMPARE(interp.map(QPointF(110,100)), QPointF(100, -10));
QCOMPARE(interp.map(QPointF(100,110)), QPointF(110, 0));
QCOMPARE(interp.map(QPointF(110,110)), QPointF(110,-10));
QCOMPARE(interp.map(QPointF(-10,100)), QPointF(100, 110));
QCOMPARE(interp.map(QPointF(0,110)), QPointF(110, 100));
QCOMPARE(interp.map(QPointF(-10,110)), QPointF(110,110));
}
#include "krita_utils.h"
void KisWarpTransformWorkerTest::testNeedChangeRects()
{
WarpTransforWorkerData d;
KisWarpTransformWorker worker(KisWarpTransformWorker::RIGID_TRANSFORM,
d.dev,
d.origPoints,
d.transfPoints,
d.alpha,
d.updater);
- QCOMPARE(KritaUtils::sampleRectWithPoints(d.bounds.toAlignedRect()).size(), 9);
+ QCOMPARE(KisAlgebra2D::sampleRectWithPoints(d.bounds.toAlignedRect()).size(), 9);
QCOMPARE(worker.approxChangeRect(d.bounds.toAlignedRect()), QRect(-44,-44, 982,986));
}
QTEST_MAIN(KisWarpTransformWorkerTest)
diff --git a/libs/image/tiles3/kis_base_iterator.h b/libs/image/tiles3/kis_base_iterator.h
index 88d357154e..068c655b67 100644
--- a/libs/image/tiles3/kis_base_iterator.h
+++ b/libs/image/tiles3/kis_base_iterator.h
@@ -1,69 +1,79 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_BASE_ITERATOR_H_
#define _KIS_BASE_ITERATOR_H_
#include "kis_datamanager.h"
#include "kis_tiled_data_manager.h"
#include "kis_tile.h"
#include "kis_types.h"
#include "kis_shared.h"
+#include "kis_iterator_complete_listener.h"
class KisBaseIterator {
protected:
- KisBaseIterator(KisTiledDataManager * _dataManager, bool _writable) {
+ KisBaseIterator(KisTiledDataManager * _dataManager, bool _writable, KisIteratorCompleteListener *listener) {
m_dataManager = _dataManager;
m_pixelSize = m_dataManager->pixelSize();
m_writable = _writable;
+ m_completeListener = listener;
}
+ ~KisBaseIterator() {
+ if (m_writable && m_completeListener) {
+ m_completeListener->notifyWritableIteratorCompleted();
+ }
+ }
+
KisTiledDataManager *m_dataManager;
qint32 m_pixelSize; // bytes per pixel
bool m_writable;
inline void lockTile(KisTileSP &tile) {
if (m_writable)
tile->lockForWrite();
else
tile->lockForRead();
}
inline void lockOldTile(KisTileSP &tile) {
// Doesn't depend on current access type
tile->lockForRead();
}
inline void unlockTile(KisTileSP &tile) {
tile->unlock();
}
inline quint32 xToCol(quint32 x) const {
return m_dataManager ? m_dataManager->xToCol(x) : 0;
}
inline quint32 yToRow(quint32 y) const {
return m_dataManager ? m_dataManager->yToRow(y) : 0;
}
inline qint32 calcXInTile(qint32 x, qint32 col) const {
return x - col * KisTileData::WIDTH;
}
inline qint32 calcYInTile(qint32 y, qint32 row) const {
return y - row * KisTileData::HEIGHT;
}
+private:
+ KisIteratorCompleteListener *m_completeListener;
};
#endif
diff --git a/libs/image/tiles3/kis_hline_iterator.cpp b/libs/image/tiles3/kis_hline_iterator.cpp
index 5af90637da..478e907520 100644
--- a/libs/image/tiles3/kis_hline_iterator.cpp
+++ b/libs/image/tiles3/kis_hline_iterator.cpp
@@ -1,233 +1,233 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_hline_iterator.h"
-KisHLineIterator2::KisHLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY, bool writable)
- : KisBaseIterator(dataManager, writable),
+KisHLineIterator2::KisHLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *competionListener)
+ : KisBaseIterator(dataManager, writable, competionListener),
m_offsetX(offsetX),
m_offsetY(offsetY)
{
x -= m_offsetX;
y -= m_offsetY;
Q_ASSERT(dataManager != 0);
Q_ASSERT(w > 0); // for us, to warn us when abusing the iterators
if (w < 1) w = 1; // for release mode, to make sure there's always at least one pixel read.
m_x = x;
m_y = y;
m_left = x;
m_right = x + w - 1;
m_top = y;
m_havePixels = (w == 0) ? false : true;
if (m_left > m_right) {
m_havePixels = false;
return;
}
m_leftCol = xToCol(m_left);
m_rightCol = xToCol(m_right);
m_row = yToRow(m_y);
m_yInTile = calcYInTile(m_y, m_row);
m_leftInLeftmostTile = m_left - m_leftCol * KisTileData::WIDTH;
m_tilesCacheSize = m_rightCol - m_leftCol + 1;
m_tilesCache.resize(m_tilesCacheSize);
m_tileWidth = m_pixelSize * KisTileData::HEIGHT;
// let's prealocate first row
for (quint32 i = 0; i < m_tilesCacheSize; i++){
fetchTileDataForCache(m_tilesCache[i], m_leftCol + i, m_row);
}
m_index = 0;
switchToTile(m_leftInLeftmostTile);
}
void KisHLineIterator2::resetPixelPos()
{
m_x = m_left;
m_index = 0;
switchToTile(m_leftInLeftmostTile);
m_havePixels = true;
}
void KisHLineIterator2::resetRowPos()
{
m_y = m_top;
m_row = yToRow(m_y);
m_yInTile = calcYInTile(m_y, m_row);
preallocateTiles();
resetPixelPos();
}
bool KisHLineIterator2::nextPixel()
{
// We won't increment m_x here as integer can overflow here
if (m_x >= m_right) {
//return !m_isDoneFlag;
return m_havePixels = false;
} else {
++m_x;
m_data += m_pixelSize;
if (m_x <= m_rightmostInTile)
m_oldData += m_pixelSize;
else {
// Switching to the beginning of the next tile
++m_index;
switchToTile(0);
}
}
return m_havePixels;
}
void KisHLineIterator2::nextRow()
{
m_x = m_left;
++m_y;
if (++m_yInTile < KisTileData::HEIGHT) {
/* do nothing, usual case */
} else {
++m_row;
m_yInTile = 0;
preallocateTiles();
}
m_index = 0;
switchToTile(m_leftInLeftmostTile);
m_havePixels = true;
}
qint32 KisHLineIterator2::nConseqPixels() const
{
return qMin(m_rightmostInTile, m_right) - m_x + 1;
}
bool KisHLineIterator2::nextPixels(qint32 n)
{
Q_ASSERT_X(!(m_x > 0 && (m_x + n) < 0), "hlineIt+=", "Integer overflow");
qint32 previousCol = xToCol(m_x);
// We won't increment m_x here first as integer can overflow
if (m_x >= m_right || (m_x += n) > m_right) {
m_havePixels = false;
} else {
qint32 col = xToCol(m_x);
// if we are in the same column in tiles
if (col == previousCol) {
m_data += n * m_pixelSize;
} else {
qint32 xInTile = calcXInTile(m_x, col);
m_index += col - previousCol;
switchToTile(xInTile);
}
}
return m_havePixels;
}
KisHLineIterator2::~KisHLineIterator2()
{
for (uint i = 0; i < m_tilesCacheSize; i++) {
unlockTile(m_tilesCache[i].tile);
unlockTile(m_tilesCache[i].oldtile);
}
}
quint8* KisHLineIterator2::rawData()
{
return m_data;
}
const quint8* KisHLineIterator2::oldRawData() const
{
return m_oldData;
}
const quint8* KisHLineIterator2::rawDataConst() const
{
return m_data;
}
void KisHLineIterator2::switchToTile(qint32 xInTile)
{
// The caller must ensure that we are not out of bounds
Q_ASSERT(m_index < m_tilesCacheSize);
m_data = m_tilesCache[m_index].data;
m_oldData = m_tilesCache[m_index].oldData;
int offset_row = m_pixelSize * (m_yInTile * KisTileData::WIDTH);
m_data += offset_row;
m_rightmostInTile = (m_leftCol + m_index + 1) * KisTileData::WIDTH - 1;
int offset_col = m_pixelSize * xInTile;
m_data += offset_col;
m_oldData += offset_row + offset_col;
}
void KisHLineIterator2::fetchTileDataForCache(KisTileInfo& kti, qint32 col, qint32 row)
{
kti.tile = m_dataManager->getTile(col, row, m_writable);
lockTile(kti.tile);
kti.data = kti.tile->data();
// set old data
kti.oldtile = m_dataManager->getOldTile(col, row);
lockOldTile(kti.oldtile);
kti.oldData = kti.oldtile->data();
}
void KisHLineIterator2::preallocateTiles()
{
for (quint32 i = 0; i < m_tilesCacheSize; ++i){
unlockTile(m_tilesCache[i].tile);
unlockTile(m_tilesCache[i].oldtile);
fetchTileDataForCache(m_tilesCache[i], m_leftCol + i, m_row);
}
}
qint32 KisHLineIterator2::x() const
{
return m_x + m_offsetX;
}
qint32 KisHLineIterator2::y() const
{
return m_y + m_offsetY;
}
diff --git a/libs/image/tiles3/kis_hline_iterator.h b/libs/image/tiles3/kis_hline_iterator.h
index 034d1ac3b5..1b9a951dcf 100644
--- a/libs/image/tiles3/kis_hline_iterator.h
+++ b/libs/image/tiles3/kis_hline_iterator.h
@@ -1,89 +1,89 @@
/*
* 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.
*/
#ifndef _KIS_HLINE_ITERATOR_H_
#define _KIS_HLINE_ITERATOR_H_
#include "kis_base_iterator.h"
#include "kritaimage_export.h"
#include "kis_iterator_ng.h"
class KRITAIMAGE_EXPORT KisHLineIterator2 : public KisHLineIteratorNG, public KisBaseIterator {
KisHLineIterator2(const KisHLineIterator2&);
KisHLineIterator2& operator=(const KisHLineIterator2&);
public:
struct KisTileInfo {
KisTileSP tile;
KisTileSP oldtile;
quint8* data;
quint8* oldData;
};
public:
- KisHLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY, bool writable);
+ KisHLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 w, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *listener);
~KisHLineIterator2();
virtual bool nextPixel();
virtual void nextRow();
virtual const quint8* oldRawData() const;
virtual const quint8* rawDataConst() const;
virtual quint8* rawData();
virtual qint32 nConseqPixels() const;
virtual bool nextPixels(qint32 n);
virtual qint32 x() const;
virtual qint32 y() const;
virtual void resetPixelPos();
virtual void resetRowPos();
private:
qint32 m_offsetX;
qint32 m_offsetY;
qint32 m_x; // current x position
qint32 m_y; // current y position
qint32 m_row; // current row in tilemgr
quint32 m_index; // current col in tilemgr
quint32 m_tileWidth;
quint8 *m_data;
quint8 *m_oldData;
bool m_havePixels;
qint32 m_right;
qint32 m_left;
qint32 m_top;
qint32 m_leftCol;
qint32 m_rightCol;
qint32 m_rightmostInTile; // limited by the current tile border only
qint32 m_leftInLeftmostTile;
qint32 m_yInTile;
QVector<KisTileInfo> m_tilesCache;
quint32 m_tilesCacheSize;
private:
void switchToTile(qint32 xInTile);
void fetchTileDataForCache(KisTileInfo& kti, qint32 col, qint32 row);
void preallocateTiles();
};
#endif
diff --git a/libs/image/tiles3/kis_memento_manager.cc b/libs/image/tiles3/kis_memento_manager.cc
index 0af0bb22e7..6566fb0135 100644
--- a/libs/image/tiles3/kis_memento_manager.cc
+++ b/libs/image/tiles3/kis_memento_manager.cc
@@ -1,422 +1,425 @@
/*
* 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.
*/
#include <QtGlobal>
#include "kis_memento_manager.h"
#include "kis_memento.h"
//#define DEBUG_MM
#ifdef DEBUG_MM
#define DEBUG_LOG_TILE_ACTION(action, tile, col, row) \
printf("### MementoManager (0x%X): %s " \
"\ttile:\t0x%X (%d, %d) ###\n", (quintptr)this, action, \
(quintptr)tile, col, row)
#define DEBUG_LOG_SIMPLE_ACTION(action) \
printf("### MementoManager (0x%X): %s\n", (quintptr)this, action)
#define DEBUG_DUMP_MESSAGE(action) do { \
printf("\n### MementoManager (0x%X): %s \t\t##########\n", \
(quintptr)this, action); \
debugPrintInfo(); \
printf("##################################################################\n\n"); \
} while(0)
#else
#define DEBUG_LOG_TILE_ACTION(action, tile, col, row)
#define DEBUG_LOG_SIMPLE_ACTION(action)
#define DEBUG_DUMP_MESSAGE(action)
#endif
/**
* The class is supposed to store the changes of the paint device
* it is associated with. The history of changes is presented in form
* of transactions (revisions). If you purge the history of one
* transaction (revision) with purgeHistory() we won't be able to undo
* the changes made by this transactions.
*
* The Memento Manager can be in two states:
* - Named Transaction is in progress - it means the caller
* has explicitly requested creation of a new transaction.
* The handle for the transaction is stored on a side of
* the caller. And the history will be automatically purged
* when the handler dies.
* - Anonymous Transaction is in progress - the caller isn't
* bothered about transactions at all. We pretend as we do
* not support any versioning and do not have any historical
* information. The history of such transactions is not purged
* automatically, but it is free'd when younger named transaction
* is purged.
*/
#define blockRegistration() (m_registrationBlocked = true)
#define unblockRegistration() (m_registrationBlocked = false)
#define registrationBlocked() (m_registrationBlocked)
#define namedTransactionInProgress() ((bool)m_currentMemento)
KisMementoManager::KisMementoManager()
: m_index(0),
m_headsHashTable(0),
m_registrationBlocked(false)
{
/**
* Tile change/delete registration is enabled for all
* devices by default. It can't be delayed.
*/
}
KisMementoManager::KisMementoManager(const KisMementoManager& rhs)
: m_index(rhs.m_index, 0),
m_revisions(rhs.m_revisions),
m_cancelledRevisions(rhs.m_cancelledRevisions),
m_headsHashTable(rhs.m_headsHashTable, 0),
m_currentMemento(rhs.m_currentMemento),
m_registrationBlocked(rhs.m_registrationBlocked)
{
Q_ASSERT_X(!m_registrationBlocked,
"KisMementoManager", "(impossible happened) "
"The device has been copied while registration was blocked");
}
KisMementoManager::~KisMementoManager()
{
// Nothing to be done here. Happily...
// Everything is done by QList and KisSharedPtr...
DEBUG_LOG_SIMPLE_ACTION("died\n");
}
/**
* NOTE: We don't assume that the registerTileChange/Delete
* can be called once a commit only. Reverse can happen when we
* do sequential clears of the device. In such a case the tiles
* will be removed and added several times during a commit.
*
* TODO: There is an 'uncomfortable' state for the tile possible
* 1) Imagine we have a clear device
* 2) Then we painted something in a tile
* 3) It registered itself using registerTileChange()
* 4) Then we called clear() and getMemento() [==commit()]
* 5) The tile will be registered as deleted and successfully
* committed to a revision. That means the states of the memento
* manager at stages 1 and 5 do not coinside.
* This will not lead to any memory leaks or bugs seen, it just
* not good from a theoretical perspective.
*/
void KisMementoManager::registerTileChange(KisTile *tile)
{
if (registrationBlocked()) return;
DEBUG_LOG_TILE_ACTION("reg. [C]", tile, tile->col(), tile->row());
KisMementoItemSP mi = m_index.getExistedTile(tile->col(), tile->row());
if(!mi) {
mi = new KisMementoItem();
mi->changeTile(tile);
m_index.addTile(mi);
if(namedTransactionInProgress())
m_currentMemento->updateExtent(mi->col(), mi->row());
}
else {
mi->reset();
mi->changeTile(tile);
}
}
void KisMementoManager::registerTileDeleted(KisTile *tile)
{
if (registrationBlocked()) return;
DEBUG_LOG_TILE_ACTION("reg. [D]", tile, tile->col(), tile->row());
KisMementoItemSP mi = m_index.getExistedTile(tile->col(), tile->row());
if(!mi) {
mi = new KisMementoItem();
mi->deleteTile(tile, m_headsHashTable.defaultTileData());
m_index.addTile(mi);
if(namedTransactionInProgress())
m_currentMemento->updateExtent(mi->col(), mi->row());
}
else {
mi->reset();
mi->deleteTile(tile, m_headsHashTable.defaultTileData());
}
}
void KisMementoManager::commit()
{
if (m_index.isEmpty()) {
if(namedTransactionInProgress()) {
//warnTiles << "Named Transaction is empty";
/**
* We still need to continue commit, because
* a named transaction may be reverted by the user
*/
}
else {
m_currentMemento = 0;
return;
}
}
KisMementoItemList revisionList;
KisMementoItemSP mi;
KisMementoItemSP parentMI;
bool newTile;
KisMementoItemHashTableIterator iter(&m_index);
while ((mi = iter.tile())) {
parentMI = m_headsHashTable.getTileLazy(mi->col(), mi->row(), newTile);
mi->setParent(parentMI);
mi->commit();
revisionList.append(mi);
m_headsHashTable.deleteTile(mi->col(), mi->row());
iter.moveCurrentToHashTable(&m_headsHashTable);
//++iter; // previous line does this for us
}
KisHistoryItem hItem;
hItem.itemList = revisionList;
hItem.memento = m_currentMemento.data();
m_revisions.append(hItem);
m_currentMemento = 0;
Q_ASSERT(m_index.isEmpty());
DEBUG_DUMP_MESSAGE("COMMIT_DONE");
// Waking up pooler to prepare copies for us
KisTileDataStore::instance()->kickPooler();
}
KisTileSP KisMementoManager::getCommitedTile(qint32 col, qint32 row)
{
/**
* Our getOldTile mechanism is supposed to return current
* tile, if the history is disabled. So we return zero if
* no named transaction is in progress.
*/
if(!namedTransactionInProgress())
return KisTileSP();
KisMementoItemSP mi = m_headsHashTable.getReadOnlyTileLazy(col, row);
Q_ASSERT(mi);
return mi->tile(0);
}
KisMementoSP KisMementoManager::getMemento()
{
/**
* We do not allow nested transactions
*/
Q_ASSERT(!namedTransactionInProgress());
// Clear redo() information
m_cancelledRevisions.clear();
commit();
m_currentMemento = new KisMemento(this);
DEBUG_LOG_SIMPLE_ACTION("GET_MEMENTO_DONE");
return m_currentMemento;
}
KisMementoSP KisMementoManager::currentMemento() {
return m_currentMemento;
}
#define forEachReversed(iter, list) \
for(iter=list.end(); iter-- != list.begin();)
void KisMementoManager::rollback(KisTileHashTable *ht)
{
commit();
if (! m_revisions.size()) return;
KisHistoryItem changeList = m_revisions.takeLast();
KisMementoItemSP mi;
KisMementoItemSP parentMI;
KisMementoItemList::iterator iter;
blockRegistration();
forEachReversed(iter, changeList.itemList) {
mi=*iter;
parentMI = mi->parent();
if (mi->type() == KisMementoItem::CHANGED)
ht->deleteTile(mi->col(), mi->row());
if (parentMI->type() == KisMementoItem::CHANGED)
ht->addTile(parentMI->tile(this));
m_headsHashTable.deleteTile(parentMI->col(), parentMI->row());
m_headsHashTable.addTile(parentMI);
// This is not necessary
//mi->setParent(0);
}
/**
* NOTE: tricky hack alert.
* We have just deleted some tiles from the original hash table.
* And they accurately reported to us about their death. Should
* have reported... But we have prevented their registration with
* explicitly blocking the process. So all the dead tiles are
* going to /dev/null :)
*
* PS: It could cause some race condition... But we insist on
* serialization of rollback()/rollforward() requests. There is
* not much sense in calling rollback() concurrently.
*/
unblockRegistration();
// We have just emulated a commit so:
m_currentMemento = 0;
Q_ASSERT(!namedTransactionInProgress());
m_cancelledRevisions.prepend(changeList);
DEBUG_DUMP_MESSAGE("UNDONE");
+
+ // Waking up pooler to prepare copies for us
+ KisTileDataStore::instance()->kickPooler();
}
void KisMementoManager::rollforward(KisTileHashTable *ht)
{
Q_ASSERT(m_index.isEmpty());
if (!m_cancelledRevisions.size()) return;
KisHistoryItem changeList = m_cancelledRevisions.takeFirst();
KisMementoItemSP mi;
blockRegistration();
Q_FOREACH (mi, changeList.itemList) {
if (mi->parent()->type() == KisMementoItem::CHANGED)
ht->deleteTile(mi->col(), mi->row());
if (mi->type() == KisMementoItem::CHANGED)
ht->addTile(mi->tile(this));
m_index.addTile(mi);
}
// see comment in rollback()
m_currentMemento = changeList.memento;
commit();
unblockRegistration();
DEBUG_DUMP_MESSAGE("REDONE");
}
void KisMementoManager::purgeHistory(KisMementoSP oldestMemento)
{
if (m_currentMemento == oldestMemento) {
commit();
}
qint32 revisionIndex = findRevisionByMemento(oldestMemento);
if (revisionIndex < 0) return;
for(; revisionIndex > 0; revisionIndex--) {
resetRevisionHistory(m_revisions.first().itemList);
m_revisions.removeFirst();
}
Q_ASSERT(m_revisions.first().memento == oldestMemento);
resetRevisionHistory(m_revisions.first().itemList);
DEBUG_DUMP_MESSAGE("PURGE_HISTORY");
}
qint32 KisMementoManager::findRevisionByMemento(KisMementoSP memento) const
{
qint32 index = -1;
for(qint32 i = 0; i < m_revisions.size(); i++) {
if (m_revisions[i].memento == memento) {
index = i;
break;
}
}
return index;
}
void KisMementoManager::resetRevisionHistory(KisMementoItemList list)
{
KisMementoItemSP parentMI;
KisMementoItemSP mi;
Q_FOREACH (mi, list) {
parentMI = mi->parent();
if(!parentMI) continue;
while (parentMI->parent()) {
parentMI = parentMI->parent();
}
mi->setParent(parentMI);
}
}
void KisMementoManager::setDefaultTileData(KisTileData *defaultTileData)
{
m_headsHashTable.setDefaultTileData(defaultTileData);
m_index.setDefaultTileData(defaultTileData);
}
void KisMementoManager::debugPrintInfo()
{
printf("KisMementoManager stats:\n");
printf("Index list\n");
KisMementoItemSP mi;
KisMementoItemHashTableIterator iter(&m_index);
while ((mi = iter.tile())) {
mi->debugPrintInfo();
++iter;
}
printf("Revisions list:\n");
qint32 i = 0;
Q_FOREACH (const KisHistoryItem &changeList, m_revisions) {
printf("--- revision #%d ---\n", i++);
Q_FOREACH (mi, changeList.itemList) {
mi->debugPrintInfo();
}
}
printf("\nCancelled revisions list:\n");
i = 0;
Q_FOREACH (const KisHistoryItem &changeList, m_cancelledRevisions) {
printf("--- revision #%d ---\n", m_revisions.size() + i++);
Q_FOREACH (mi, changeList.itemList) {
mi->debugPrintInfo();
}
}
printf("----------------\n");
m_headsHashTable.debugPrintInfo();
}
diff --git a/libs/image/tiles3/kis_random_accessor.cc b/libs/image/tiles3/kis_random_accessor.cc
index 3c8a4fb7d5..838b397e0a 100644
--- a/libs/image/tiles3/kis_random_accessor.cc
+++ b/libs/image/tiles3/kis_random_accessor.cc
@@ -1,156 +1,161 @@
/*
* 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.
*/
#include "kis_random_accessor.h"
#include <kis_debug.h>
const quint32 KisRandomAccessor2::CACHESIZE = 4; // Define the number of tiles we keep in cache
-KisRandomAccessor2::KisRandomAccessor2(KisTiledDataManager *ktm, qint32 x, qint32 y, qint32 offsetX, qint32 offsetY, bool writable) :
+KisRandomAccessor2::KisRandomAccessor2(KisTiledDataManager *ktm, qint32 x, qint32 y, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *completeListener) :
m_ktm(ktm),
m_tilesCache(new KisTileInfo*[CACHESIZE]),
m_tilesCacheSize(0),
m_pixelSize(m_ktm->pixelSize()),
m_writable(writable),
m_offsetX(offsetX),
- m_offsetY(offsetY)
+ m_offsetY(offsetY),
+ m_completeListener(completeListener)
{
Q_ASSERT(ktm != 0);
moveTo(x, y);
}
KisRandomAccessor2::~KisRandomAccessor2()
{
for (uint i = 0; i < m_tilesCacheSize; i++) {
unlockTile(m_tilesCache[i]->tile);
unlockTile(m_tilesCache[i]->oldtile);
delete m_tilesCache[i];
}
delete [] m_tilesCache;
+
+ if (m_writable && m_completeListener) {
+ m_completeListener->notifyWritableIteratorCompleted();
+ }
}
void KisRandomAccessor2::moveTo(qint32 x, qint32 y)
{
m_lastX = x;
m_lastY = y;
x -= m_offsetX;
y -= m_offsetY;
// Look in the cache if the tile if the data is available
for (uint i = 0; i < m_tilesCacheSize; i++) {
if (x >= m_tilesCache[i]->area_x1 && x <= m_tilesCache[i]->area_x2 &&
y >= m_tilesCache[i]->area_y1 && y <= m_tilesCache[i]->area_y2) {
KisTileInfo* kti = m_tilesCache[i];
quint32 offset = x - kti->area_x1 + (y - kti->area_y1) * KisTileData::WIDTH;
offset *= m_pixelSize;
m_data = kti->data + offset;
m_oldData = kti->oldData + offset;
if (i > 0) {
memmove(m_tilesCache + 1, m_tilesCache, i * sizeof(KisTileInfo*));
m_tilesCache[0] = kti;
}
return;
}
}
// The tile wasn't in cache
if (m_tilesCacheSize == KisRandomAccessor2::CACHESIZE) { // Remove last element of cache
unlockTile(m_tilesCache[CACHESIZE-1]->tile);
unlockTile(m_tilesCache[CACHESIZE-1]->oldtile);
delete m_tilesCache[CACHESIZE-1];
} else {
m_tilesCacheSize++;
}
quint32 col = xToCol(x);
quint32 row = yToRow(y);
KisTileInfo* kti = fetchTileData(col, row);
quint32 offset = x - kti->area_x1 + (y - kti->area_y1) * KisTileData::WIDTH;
offset *= m_pixelSize;
m_data = kti->data + offset;
m_oldData = kti->oldData + offset;
memmove(m_tilesCache + 1, m_tilesCache, (KisRandomAccessor2::CACHESIZE - 1) * sizeof(KisTileInfo*));
m_tilesCache[0] = kti;
}
quint8* KisRandomAccessor2::rawData()
{
return m_data;
}
const quint8* KisRandomAccessor2::oldRawData() const
{
#ifdef DEBUG
if (!m_ktm->hasCurrentMemento()) warnTiles << "Accessing oldRawData() when no transaction is in progress.";
#endif
return m_oldData;
}
const quint8* KisRandomAccessor2::rawDataConst() const
{
return m_data;
}
KisRandomAccessor2::KisTileInfo* KisRandomAccessor2::fetchTileData(qint32 col, qint32 row)
{
KisTileInfo* kti = new KisTileInfo;
kti->tile = m_ktm->getTile(col, row, m_writable);
lockTile(kti->tile);
kti->data = kti->tile->data();
kti->area_x1 = col * KisTileData::HEIGHT;
kti->area_y1 = row * KisTileData::WIDTH;
kti->area_x2 = kti->area_x1 + KisTileData::HEIGHT - 1;
kti->area_y2 = kti->area_y1 + KisTileData::WIDTH - 1;
// set old data
kti->oldtile = m_ktm->getOldTile(col, row);
lockOldTile(kti->oldtile);
kti->oldData = kti->oldtile->data();
return kti;
}
qint32 KisRandomAccessor2::numContiguousColumns(qint32 x) const
{
return m_ktm->numContiguousColumns(x - m_offsetX, 0, 0);
}
qint32 KisRandomAccessor2::numContiguousRows(qint32 y) const
{
return m_ktm->numContiguousRows(y - m_offsetY, 0, 0);
}
qint32 KisRandomAccessor2::rowStride(qint32 x, qint32 y) const
{
return m_ktm->rowStride(x - m_offsetX, y - m_offsetY);
}
qint32 KisRandomAccessor2::x() const
{
return m_lastX;
}
qint32 KisRandomAccessor2::y() const
{
return m_lastY;
}
diff --git a/libs/image/tiles3/kis_random_accessor.h b/libs/image/tiles3/kis_random_accessor.h
index e41158544d..336a5c0f63 100644
--- a/libs/image/tiles3/kis_random_accessor.h
+++ b/libs/image/tiles3/kis_random_accessor.h
@@ -1,99 +1,101 @@
/*
* 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_TILED_RANDOM_ACCESSOR_H
#define KIS_TILED_RANDOM_ACCESSOR_H
#include <QRect>
#include <kis_shared.h>
#include "kis_tiled_data_manager.h"
#include "kis_random_accessor_ng.h"
+#include "kis_iterator_complete_listener.h"
class KisRandomAccessor2 : public KisRandomAccessorNG
{
struct KisTileInfo {
KisTileSP tile;
KisTileSP oldtile;
quint8* data;
const quint8* oldData;
qint32 area_x1, area_y1, area_x2, area_y2;
};
public:
- KisRandomAccessor2(KisTiledDataManager *ktm, qint32 x, qint32 y, qint32 offsetX, qint32 offsetY, bool writable);
+ KisRandomAccessor2(KisTiledDataManager *ktm, qint32 x, qint32 y, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *completeListener);
KisRandomAccessor2(const KisTiledRandomAccessor& lhs);
~KisRandomAccessor2();
private:
inline void lockTile(KisTileSP &tile) {
if (m_writable)
tile->lockForWrite();
else
tile->lockForRead();
}
inline void lockOldTile(KisTileSP &tile) {
// Doesn't depend on access type
tile->lockForRead();
}
inline void unlockTile(KisTileSP &tile) {
tile->unlock();
}
inline quint32 xToCol(quint32 x) const {
return m_ktm ? m_ktm->xToCol(x) : 0;
}
inline quint32 yToRow(quint32 y) const {
return m_ktm ? m_ktm->yToRow(y) : 0;
}
KisTileInfo* fetchTileData(qint32 col, qint32 row);
public:
/// Move to a given x,y position, fetch tiles and data
void moveTo(qint32 x, qint32 y);
quint8* rawData();
const quint8* oldRawData() const;
const quint8* rawDataConst() const;
qint32 numContiguousColumns(qint32 x) const;
qint32 numContiguousRows(qint32 y) const;
qint32 rowStride(qint32 x, qint32 y) const;
qint32 x() const;
qint32 y() const;
private:
KisTiledDataManager *m_ktm;
KisTileInfo** m_tilesCache;
quint32 m_tilesCacheSize;
qint32 m_pixelSize;
quint8* m_data;
const quint8* m_oldData;
bool m_writable;
int m_lastX, m_lastY;
qint32 m_offsetX, m_offsetY;
+ KisIteratorCompleteListener *m_completeListener;
static const quint32 CACHESIZE; // Define the number of tiles we keep in cache
};
#endif
diff --git a/libs/image/tiles3/kis_vline_iterator.cpp b/libs/image/tiles3/kis_vline_iterator.cpp
index 52a4d49a9a..acfacc97f9 100644
--- a/libs/image/tiles3/kis_vline_iterator.cpp
+++ b/libs/image/tiles3/kis_vline_iterator.cpp
@@ -1,235 +1,235 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_vline_iterator.h"
#include <iostream>
-KisVLineIterator2::KisVLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 h, qint32 offsetX, qint32 offsetY, bool writable)
- : KisBaseIterator(dataManager, writable),
+KisVLineIterator2::KisVLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 h, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *completeListener)
+ : KisBaseIterator(dataManager, writable, completeListener),
m_offsetX(offsetX),
m_offsetY(offsetY)
{
x -= m_offsetX;
y -= m_offsetY;
Q_ASSERT(dataManager != 0);
Q_ASSERT(h > 0); // for us, to warn us when abusing the iterators
if (h < 1) h = 1; // for release mode, to make sure there's always at least one pixel read.
m_lineStride = m_pixelSize * KisTileData::WIDTH;
m_x = x;
m_y = y;
m_top = y;
m_bottom = y + h - 1;
m_left = m_x;
m_havePixels = (h == 0) ? false : true;
if (m_top > m_bottom) {
m_havePixels = false;
return;
}
m_topRow = yToRow(m_top);
m_bottomRow = yToRow(m_bottom);
m_column = xToCol(m_x);
m_xInTile = calcXInTile(m_x, m_column);
m_topInTopmostTile = m_top - m_topRow * KisTileData::WIDTH;
m_tilesCacheSize = m_bottomRow - m_topRow + 1;
m_tilesCache.resize(m_tilesCacheSize);
m_tileSize = m_lineStride * KisTileData::HEIGHT;
// let's prealocate first row
for (int i = 0; i < m_tilesCacheSize; i++){
fetchTileDataForCache(m_tilesCache[i], m_column, m_topRow + i);
}
m_index = 0;
switchToTile(m_topInTopmostTile);
}
void KisVLineIterator2::resetPixelPos()
{
m_y = m_top;
m_index = 0;
switchToTile(m_topInTopmostTile);
m_havePixels = true;
}
void KisVLineIterator2::resetColumnPos()
{
m_x = m_left;
m_column = xToCol(m_x);
m_xInTile = calcXInTile(m_x, m_column);
preallocateTiles();
resetPixelPos();
}
bool KisVLineIterator2::nextPixel()
{
// We won't increment m_x here as integer can overflow here
if (m_y >= m_bottom) {
//return !m_isDoneFlag;
return m_havePixels = false;
} else {
++m_y;
m_data += m_lineStride;
if (m_data < m_dataBottom)
m_oldData += m_lineStride;
else {
// Switching to the beginning of the next tile
++m_index;
switchToTile(0);
}
}
return m_havePixels;
}
void KisVLineIterator2::nextColumn()
{
m_y = m_top;
++m_x;
if (++m_xInTile < KisTileData::HEIGHT) {
/* do nothing, usual case */
} else {
++m_column;
m_xInTile = 0;
preallocateTiles();
}
m_index = 0;
switchToTile(m_topInTopmostTile);
m_havePixels = true;
}
qint32 KisVLineIterator2::nConseqPixels() const
{
return 1;
}
bool KisVLineIterator2::nextPixels(qint32 n)
{
Q_ASSERT_X(!(m_y > 0 && (m_y + n) < 0), "vlineIt+=", "Integer overflow");
qint32 previousRow = yToRow(m_y);
// We won't increment m_x here first as integer can overflow
if (m_y >= m_bottom || (m_y += n) > m_bottom) {
m_havePixels = false;
} else {
qint32 row = yToRow(m_y);
// if we are in the same column in tiles
if (row == previousRow) {
m_data += n * m_pixelSize;
} else {
qint32 yInTile = calcYInTile(m_y, row);
m_index += row - previousRow;
switchToTile(yInTile);
}
}
return m_havePixels;
}
KisVLineIterator2::~KisVLineIterator2()
{
for (int i = 0; i < m_tilesCacheSize; i++) {
unlockTile(m_tilesCache[i].tile);
unlockTile(m_tilesCache[i].oldtile);
}
}
quint8* KisVLineIterator2::rawData()
{
return m_data;
}
const quint8* KisVLineIterator2::oldRawData() const
{
return m_oldData;
}
const quint8* KisVLineIterator2::rawDataConst() const
{
return m_data;
}
void KisVLineIterator2::switchToTile(qint32 yInTile)
{
// The caller must ensure that we are not out of bounds
Q_ASSERT(m_index < m_tilesCacheSize);
Q_ASSERT(m_index >= 0);
int offset_row = m_pixelSize * m_xInTile;
m_data = m_tilesCache[m_index].data;
m_oldData = m_tilesCache[m_index].oldData;
m_data += offset_row;
m_dataBottom = m_data + m_tileSize;
int offset_col = m_pixelSize * yInTile * KisTileData::WIDTH;
m_data += offset_col;
m_oldData += offset_row + offset_col;
}
void KisVLineIterator2::fetchTileDataForCache(KisTileInfo& kti, qint32 col, qint32 row)
{
kti.tile = m_dataManager->getTile(col, row, m_writable);
lockTile(kti.tile);
kti.data = kti.tile->data();
// set old data
kti.oldtile = m_dataManager->getOldTile(col, row);
lockOldTile(kti.oldtile);
kti.oldData = kti.oldtile->data();
}
void KisVLineIterator2::preallocateTiles()
{
for (int i = 0; i < m_tilesCacheSize; ++i){
unlockTile(m_tilesCache[i].tile);
unlockTile(m_tilesCache[i].oldtile);
fetchTileDataForCache(m_tilesCache[i], m_column, m_topRow + i );
}
}
qint32 KisVLineIterator2::x() const
{
return m_x + m_offsetX;
}
qint32 KisVLineIterator2::y() const
{
return m_y + m_offsetY;
}
diff --git a/libs/image/tiles3/kis_vline_iterator.h b/libs/image/tiles3/kis_vline_iterator.h
index 875dffb579..68ae69ddc4 100644
--- a/libs/image/tiles3/kis_vline_iterator.h
+++ b/libs/image/tiles3/kis_vline_iterator.h
@@ -1,90 +1,90 @@
/*
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_VLINE_ITERATOR_H_
#define _KIS_VLINE_ITERATOR_H_
#include "kis_base_iterator.h"
#include "kritaimage_export.h"
#include "kis_iterator_ng.h"
class KRITAIMAGE_EXPORT KisVLineIterator2 : public KisVLineIteratorNG, KisBaseIterator {
KisVLineIterator2(const KisVLineIterator2&);
KisVLineIterator2& operator=(const KisVLineIterator2&);
public:
struct KisTileInfo {
KisTileSP tile;
KisTileSP oldtile;
quint8* data;
quint8* oldData;
};
public:
- KisVLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 h, qint32 offsetX, qint32 offsetY, bool writable);
+ KisVLineIterator2(KisDataManager *dataManager, qint32 x, qint32 y, qint32 h, qint32 offsetX, qint32 offsetY, bool writable, KisIteratorCompleteListener *completeListener);
~KisVLineIterator2();
virtual void resetPixelPos();
virtual void resetColumnPos();
virtual bool nextPixel();
virtual void nextColumn();
virtual const quint8* rawDataConst() const;
virtual const quint8* oldRawData() const;
virtual quint8* rawData();
virtual qint32 nConseqPixels() const;
virtual bool nextPixels(qint32 n);
virtual qint32 x() const;
virtual qint32 y() const;
private:
qint32 m_offsetX;
qint32 m_offsetY;
qint32 m_x; // current x position
qint32 m_y; // current y position
qint32 m_column; // current column in tilemgr
qint32 m_index; // current row in tilemgr
qint32 m_tileSize;
quint8 *m_data;
quint8 *m_dataBottom;
quint8 *m_oldData;
bool m_havePixels;
qint32 m_top;
qint32 m_bottom;
qint32 m_left;
qint32 m_topRow;
qint32 m_bottomRow;
qint32 m_topInTopmostTile;
qint32 m_xInTile;
qint32 m_lineStride;
QVector<KisTileInfo> m_tilesCache;
qint32 m_tilesCacheSize;
private:
void switchToTile(qint32 xInTile);
void fetchTileDataForCache(KisTileInfo& kti, qint32 col, qint32 row);
void preallocateTiles();
};
#endif
diff --git a/libs/image/tiles3/swap/kis_chunk_allocator.cpp b/libs/image/tiles3/swap/kis_chunk_allocator.cpp
index 26998335de..ce79dfbdbb 100644
--- a/libs/image/tiles3/swap/kis_chunk_allocator.cpp
+++ b/libs/image/tiles3/swap/kis_chunk_allocator.cpp
@@ -1,213 +1,213 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_debug.h"
#include "kis_chunk_allocator.h"
#define GAP_SIZE(low, high) ((high) - (low) > 0 ? (high) - (low) - 1 : 0)
#define HAS_NEXT(list,iter) ((iter)!=(list).end())
#define HAS_PREVIOUS(list,iter) ((iter)!=(list).begin())
#define PEEK_NEXT(iter) (*(iter))
#define PEEK_PREVIOUS(iter) (*((iter)-1))
#define WRAP_PREVIOUS_CHUNK_DATA(iter) (KisChunk((iter)-1))
KisChunkAllocator::KisChunkAllocator(quint64 slabSize, quint64 storeSize)
{
m_storeMaxSize = storeSize;
m_storeSlabSize = slabSize;
m_iterator = m_list.begin();
m_storeSize = m_storeSlabSize;
INIT_FAIL_COUNTER();
}
KisChunkAllocator::~KisChunkAllocator()
{
}
KisChunk KisChunkAllocator::getChunk(quint64 size)
{
KisChunkDataListIterator startPosition = m_iterator;
START_COUNTING();
forever {
if(tryInsertChunk(m_list, m_iterator, size))
return WRAP_PREVIOUS_CHUNK_DATA(m_iterator);
if(m_iterator == m_list.end())
break;
m_iterator++;
REGISTER_STEP();
}
REGISTER_FAIL();
m_iterator = m_list.begin();
forever {
if(tryInsertChunk(m_list, m_iterator, size))
return WRAP_PREVIOUS_CHUNK_DATA(m_iterator);
if(m_iterator == m_list.end() || m_iterator == startPosition)
break;
m_iterator++;
REGISTER_STEP();
}
REGISTER_FAIL();
m_iterator = m_list.end();
while ((m_storeSize += m_storeSlabSize) <= m_storeMaxSize) {
if(tryInsertChunk(m_list, m_iterator, size))
return WRAP_PREVIOUS_CHUNK_DATA(m_iterator);
}
qFatal("KisChunkAllocator: out of swap space");
// just let gcc be happy! :)
return KisChunk(m_list.end());
}
bool KisChunkAllocator::tryInsertChunk(KisChunkDataList &list,
KisChunkDataListIterator &iterator,
quint64 size)
{
bool result = false;
quint64 highBound = m_storeSize;
quint64 lowBound = 0;
quint64 shift = 0;
if(HAS_NEXT(list, iterator))
highBound = PEEK_NEXT(iterator).m_begin;
if(HAS_PREVIOUS(list, iterator)) {
lowBound = PEEK_PREVIOUS(iterator).m_end;
shift = 1;
}
if(GAP_SIZE(lowBound, highBound) >= size) {
list.insert(iterator, KisChunkData(lowBound + shift, size));
result = true;
}
return result;
}
void KisChunkAllocator::freeChunk(KisChunk chunk)
{
if(m_iterator != m_list.end() && m_iterator == chunk.position()) {
m_iterator = m_list.erase(m_iterator);
return;
}
Q_ASSERT(chunk.position()->m_begin == chunk.begin());
m_list.erase(chunk.position());
}
/**************************************************************/
/******* Debugging features ********/
/**************************************************************/
void KisChunkAllocator::debugChunks()
{
quint64 idx = 0;
KisChunkDataListIterator i;
for(i = m_list.begin(); i != m_list.end(); ++i) {
qDebug("chunk #%lld: [%lld %lld]", idx++, i->m_begin, i->m_end);
}
}
bool KisChunkAllocator::sanityCheck(bool pleaseCrash)
{
bool failed = false;
KisChunkDataListIterator i;
for(i = m_list.begin(); i != m_list.end(); ++i) {
if(HAS_PREVIOUS(m_list, i)) {
if(PEEK_PREVIOUS(i).m_end >= i->m_begin) {
qWarning("Chunks overlapped: [%lld %lld], [%lld %lld]", PEEK_PREVIOUS(i).m_begin, PEEK_PREVIOUS(i).m_end, i->m_begin, i->m_end);
failed = true;
break;
}
}
}
i = m_list.end();
if(HAS_PREVIOUS(m_list, i)) {
if(PEEK_PREVIOUS(i).m_end >= m_storeSize) {
warnKrita << "Last chunk exceeds the store size!";
failed = true;
}
}
if(failed && pleaseCrash)
qFatal("KisChunkAllocator: sanity check failed!");
return !failed;
}
qreal KisChunkAllocator::debugFragmentation(bool toStderr)
{
KisChunkDataListIterator i;
quint64 totalSize = 0;
quint64 allocated = 0;
quint64 free = 0;
qreal fragmentation = 0;
for(i = m_list.begin(); i != m_list.end(); ++i) {
allocated += i->m_end - i->m_begin + 1;
if(HAS_PREVIOUS(m_list, i))
free += GAP_SIZE(PEEK_PREVIOUS(i).m_end, i->m_begin);
else
free += i->m_begin;
}
i = m_list.end();
if(HAS_PREVIOUS(m_list, i))
totalSize = PEEK_PREVIOUS(i).m_end + 1;
if(totalSize)
fragmentation = qreal(free) / totalSize;
if(toStderr) {
- dbgKrita << "Hard store limit:\t" << m_storeMaxSize;
- dbgKrita << "Slab size:\t\t" << m_storeSlabSize;
- dbgKrita << "Num slabs:\t\t" << m_storeSize / m_storeSlabSize;
- dbgKrita << "Store size:\t\t" << m_storeSize;
- dbgKrita << "Total used:\t\t" << totalSize;
- dbgKrita << "Allocated:\t\t" << allocated;
- dbgKrita << "Free:\t\t\t" << free;
- dbgKrita << "Fragmentation:\t\t" << fragmentation;
+ qDebug() << "Hard store limit:\t" << m_storeMaxSize;
+ qDebug() << "Slab size:\t\t" << m_storeSlabSize;
+ qDebug() << "Num slabs:\t\t" << m_storeSize / m_storeSlabSize;
+ qDebug() << "Store size:\t\t" << m_storeSize;
+ qDebug() << "Total used:\t\t" << totalSize;
+ qDebug() << "Allocated:\t\t" << allocated;
+ qDebug() << "Free:\t\t\t" << free;
+ qDebug() << "Fragmentation:\t\t" << fragmentation;
DEBUG_FAIL_COUNTER();
}
Q_ASSERT(totalSize == allocated + free);
return fragmentation;
}
diff --git a/libs/image/tiles3/swap/kis_chunk_allocator.h b/libs/image/tiles3/swap/kis_chunk_allocator.h
index efa2fb4669..934a6543be 100644
--- a/libs/image/tiles3/swap/kis_chunk_allocator.h
+++ b/libs/image/tiles3/swap/kis_chunk_allocator.h
@@ -1,162 +1,162 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_CHUNK_LIST_H
#define __KIS_CHUNK_LIST_H
#include <QLinkedList>
#define MiB (1ULL << 20)
#define DEFAULT_STORE_SIZE (4096*MiB)
#define DEFAULT_SLAB_SIZE (64*MiB)
//#define DEBUG_SLAB_FAILS
#ifdef DEBUG_SLAB_FAILS
#define WINDOW_SIZE 2000
#define DECLARE_FAIL_COUNTER() quint64 __failCount
#define INIT_FAIL_COUNTER() __failCount = 0
#define START_COUNTING() quint64 __numSteps = 0
#define REGISTER_STEP() if(++__numSteps > WINDOW_SIZE) {__numSteps=0; __failCount++;}
#define REGISTER_FAIL() __failCount++
-#define DEBUG_FAIL_COUNTER() dbgKrita << "Slab fail count:\t" << __failCount
+#define DEBUG_FAIL_COUNTER() qDebug() << "Slab fail count:\t" << __failCount
#else
#define DECLARE_FAIL_COUNTER()
#define INIT_FAIL_COUNTER()
#define START_COUNTING()
#define REGISTER_STEP()
#define REGISTER_FAIL()
#define DEBUG_FAIL_COUNTER()
#endif /* DEBUG_SLAB_FAILS */
class KisChunkData;
typedef QLinkedList<KisChunkData> KisChunkDataList;
typedef KisChunkDataList::iterator KisChunkDataListIterator;
class KisChunkData
{
public:
KisChunkData(quint64 begin, quint64 size)
{
setChunk(begin, size);
}
inline void setChunk(quint64 begin, quint64 size) {
m_begin = begin;
m_end = begin + size - 1;
}
inline quint64 size() const {
return m_end - m_begin +1;
}
bool operator== (const KisChunkData& other) const
{
Q_ASSERT(m_begin!=other.m_begin || m_end==other.m_end);
/**
* Chunks cannot overlap, so it is enough to check
* the beginning of the interval only
*/
return m_begin == other.m_begin;
}
quint64 m_begin;
quint64 m_end;
};
class KisChunk
{
public:
KisChunk() {}
KisChunk(KisChunkDataListIterator iterator)
: m_iterator(iterator)
{
}
inline quint64 begin() const {
return m_iterator->m_begin;
}
inline quint64 end() const {
return m_iterator->m_end;
}
inline quint64 size() const {
return m_iterator->size();
}
inline KisChunkDataListIterator position() {
return m_iterator;
}
inline const KisChunkData& data() {
return *m_iterator;
}
private:
KisChunkDataListIterator m_iterator;
};
class KisChunkAllocator
{
public:
KisChunkAllocator(quint64 slabSize = DEFAULT_SLAB_SIZE,
quint64 storeSize = DEFAULT_STORE_SIZE);
~KisChunkAllocator();
inline quint64 numChunks() const {
return m_list.size();
}
KisChunk getChunk(quint64 size);
void freeChunk(KisChunk chunk);
void debugChunks();
bool sanityCheck(bool pleaseCrash = true);
qreal debugFragmentation(bool toStderr = true);
private:
bool tryInsertChunk(KisChunkDataList &list,
KisChunkDataListIterator &iterator,
quint64 size);
private:
quint64 m_storeMaxSize;
quint64 m_storeSlabSize;
KisChunkDataList m_list;
KisChunkDataListIterator m_iterator;
quint64 m_storeSize;
DECLARE_FAIL_COUNTER()
};
#endif /* __KIS_CHUNK_ALLOCATOR_H */
diff --git a/libs/kundo2/CMakeLists.txt b/libs/kundo2/CMakeLists.txt
deleted file mode 100644
index f787022e55..0000000000
--- a/libs/kundo2/CMakeLists.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-set(kritaundo2_LIB_SRCS
- kundo2stack.cpp
- kundo2group.cpp
- kundo2view.cpp
- kundo2model.cpp
- kundo2magicstring.cpp
- kundo2commandextradata.cpp
-)
-
-add_library(kritaundo2 SHARED ${kritaundo2_LIB_SRCS})
-generate_export_header(kritaundo2 BASE_NAME kritaundo2)
-
-target_link_libraries(kritaundo2
- PUBLIC
- kritawidgetutils
- KF5::I18n
- KF5::ConfigGui
- Qt5::Core
- Qt5::Widgets
-)
-
-set_target_properties(kritaundo2 PROPERTIES
- VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
-)
-install(TARGETS kritaundo2 ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/kundo2/kundo2commandextradata.h b/libs/kundo2/kundo2commandextradata.h
deleted file mode 100644
index c6840f9d75..0000000000
--- a/libs/kundo2/kundo2commandextradata.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2015 Dmitry Kazakov <dimula73@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef __KUNDO2COMMANDEXTRADATA_H
-#define __KUNDO2COMMANDEXTRADATA_H
-
-#include "kritaundo2_export.h"
-
-
-class KRITAUNDO2_EXPORT KUndo2CommandExtraData
-{
-public:
- virtual ~KUndo2CommandExtraData();
-};
-
-#endif /* __KUNDO2COMMANDEXTRADATA_H */
diff --git a/libs/kundo2/kundo2group.h b/libs/kundo2/kundo2group.h
deleted file mode 100644
index 6d940e9b9e..0000000000
--- a/libs/kundo2/kundo2group.h
+++ /dev/null
@@ -1,104 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the QtGui module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef KUNDO2GROUP_H
-#define KUNDO2GROUP_H
-
-#include <QObject>
-#include <QString>
-
-#include "kritaundo2_export.h"
-
-class KUndo2GroupPrivate;
-class KUndo2QStack;
-class QAction;
-
-#ifndef QT_NO_UNDOGROUP
-
-class KRITAUNDO2_EXPORT KUndo2Group : public QObject
-{
- Q_OBJECT
- Q_DECLARE_PRIVATE(KUndo2Group)
-
-public:
- explicit KUndo2Group(QObject *parent = 0);
- ~KUndo2Group();
-
- void addStack(KUndo2QStack *stack);
- void removeStack(KUndo2QStack *stack);
- QList<KUndo2QStack*> stacks() const;
- KUndo2QStack *activeStack() const;
-
-#ifndef QT_NO_ACTION
- QAction *createUndoAction(QObject *parent) const;
- QAction *createRedoAction(QObject *parent) const;
-#endif // QT_NO_ACTION
- bool canUndo() const;
- bool canRedo() const;
- QString undoText() const;
- QString redoText() const;
- bool isClean() const;
-
-public Q_SLOTS:
- void undo();
- void redo();
- void setActiveStack(KUndo2QStack *stack);
-
-Q_SIGNALS:
- void activeStackChanged(KUndo2QStack *stack);
- void indexChanged(int idx);
- void cleanChanged(bool clean);
- void canUndoChanged(bool canUndo);
- void canRedoChanged(bool canRedo);
- void undoTextChanged(const QString &undoActionText);
- void redoTextChanged(const QString &redoActionText);
-
-private:
- // from QUndoGroupPrivate
- KUndo2QStack *m_active;
- QList<KUndo2QStack*> m_stack_list;
-
- Q_DISABLE_COPY(KUndo2Group)
-};
-
-#endif // QT_NO_UNDOGROUP
-
-#endif // KUNDO2GROUP_H
diff --git a/libs/kundo2/kundo2magicstring.h b/libs/kundo2/kundo2magicstring.h
deleted file mode 100644
index ffe7001dc9..0000000000
--- a/libs/kundo2/kundo2magicstring.h
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * 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 "kritaundo2_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
- * nominative</a>) and the message in undo/redo actions (which is
- * usually a <a href="http://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 KRITAUNDO2_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 speacial 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/kundo2/kundo2stack.h b/libs/kundo2/kundo2stack.h
deleted file mode 100644
index 74258a8062..0000000000
--- a/libs/kundo2/kundo2stack.h
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
- * Copyright (c) 2014 Mohit Goyal <mohit.bits2011@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 program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-/****************************************************************************
-**
-** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the QtGui module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#ifndef KUNDO2STACK_H
-#define KUNDO2STACK_H
-
-#include <QObject>
-#include <QString>
-#include <QList>
-#include <QAction>
-#include <QTime>
-#include <QVector>
-
-
-#include "kritaundo2_export.h"
-
-class QAction;
-class KUndo2CommandPrivate;
-class KUndo2Group;
-class KActionCollection;
-
-#ifndef QT_NO_UNDOCOMMAND
-
-#include "kundo2magicstring.h"
-#include "kundo2commandextradata.h"
-
-
-/**
- * WARNING: In general, don't derive undo commands from QObject. And
- * if you really need it, don't use QObject lifetime tracking
- * for the commands: KUndo2Command has its own, *nonvirtual*
- * hierarchy, and don't make it a parent or a child of any
- * QObject. Otherwise two different parents will try to track
- * the lifetime of your command and, most probably, you'll
- * get a crash.
- *
- * As a general rule: an undo command should be derived
- * from QObject only for the sake of signal/slots capabilities.
- * Nothing else.
- */
-class KRITAUNDO2_EXPORT KUndo2Command
-{
- KUndo2CommandPrivate *d;
- int timedID;
-
-public:
- explicit KUndo2Command(KUndo2Command *parent = 0);
- explicit KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent = 0);
- virtual ~KUndo2Command();
-
- virtual void undo();
- virtual void redo();
-
- QString actionText() const;
- KUndo2MagicString text() const;
- void setText(const KUndo2MagicString &text);
-
- virtual int id() const;
- virtual int timedId();
- virtual void setTimedID(int timedID);
- virtual bool mergeWith(const KUndo2Command *other);
- virtual bool timedMergeWith(KUndo2Command *other);
-
- int childCount() const;
- const KUndo2Command *child(int index) const;
-
- bool hasParent();
- virtual void setTime();
- virtual QTime time();
- virtual void setEndTime();
- virtual QTime endTime();
-
- virtual QVector<KUndo2Command*> mergeCommandsVector();
- virtual bool isMerged();
- virtual void undoMergedCommands();
- virtual void redoMergedCommands();
-
- /**
- * \return user-defined object associated with the command
- *
- * \see setExtraData()
- */
- KUndo2CommandExtraData* extraData() const;
-
- /**
- * The user can assign an arbitrary object associated with the
- * command. The \p data object is owned by the command. If you assign
- * the object twice, the first one will be destroyed.
- */
- void setExtraData(KUndo2CommandExtraData *data);
-
-private:
- Q_DISABLE_COPY(KUndo2Command)
- friend class KUndo2QStack;
-
-
- bool m_hasParent;
- int m_timedID;
-
- QTime m_timeOfCreation;
- QTime m_endOfCommand;
- QVector<KUndo2Command*> m_mergeCommandsVector;
-};
-
-#endif // QT_NO_UNDOCOMMAND
-
-#ifndef QT_NO_UNDOSTACK
-
-class KRITAUNDO2_EXPORT KUndo2QStack : public QObject
-{
- Q_OBJECT
-// Q_DECLARE_PRIVATE(KUndo2QStack)
- Q_PROPERTY(bool active READ isActive WRITE setActive)
- Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit)
-
-public:
- explicit KUndo2QStack(QObject *parent = 0);
- virtual ~KUndo2QStack();
- void clear();
-
- void push(KUndo2Command *cmd);
-
- bool canUndo() const;
- bool canRedo() const;
- QString undoText() const;
- QString redoText() const;
-
- int count() const;
- int index() const;
- QString actionText(int idx) const;
- QString text(int idx) const;
-
-#ifndef QT_NO_ACTION
- QAction *createUndoAction(QObject *parent) const;
- QAction *createRedoAction(QObject *parent) const;
-#endif // QT_NO_ACTION
-
- bool isActive() const;
- bool isClean() const;
- int cleanIndex() const;
-
- void beginMacro(const KUndo2MagicString &text);
- void endMacro();
-
- void setUndoLimit(int limit);
- int undoLimit() const;
-
- const KUndo2Command *command(int index) const;
-
- void setUseCumulativeUndoRedo(bool value);
- bool useCumulativeUndoRedo();
- void setTimeT1(double value);
- double timeT1();
- void setTimeT2(double value);
- double timeT2();
- int strokesN();
- void setStrokesN(int value);
-
-
-public Q_SLOTS:
- void setClean();
- virtual void setIndex(int idx);
- virtual void undo();
- virtual void redo();
- void setActive(bool active = true);
-
- void purgeRedoState();
-
-Q_SIGNALS:
- void indexChanged(int idx);
- void cleanChanged(bool clean);
- void canUndoChanged(bool canUndo);
- void canRedoChanged(bool canRedo);
- void undoTextChanged(const QString &undoActionText);
- void redoTextChanged(const QString &redoActionText);
-
-protected:
- virtual void notifySetIndexChangedOneCommand();
-
-private:
- // from QUndoStackPrivate
- QList<KUndo2Command*> m_command_list;
- QList<KUndo2Command*> m_macro_stack;
- int m_index;
- int m_clean_index;
- KUndo2Group *m_group;
- int m_undo_limit;
- bool m_useCumulativeUndoRedo;
- double m_timeT1;
- double m_timeT2;
- int m_strokesN;
- int m_lastMergedSetCount;
- int m_lastMergedIndex;
-
- // also from QUndoStackPrivate
- void setIndex(int idx, bool clean);
- bool checkUndoLimit();
-
- Q_DISABLE_COPY(KUndo2QStack)
- friend class KUndo2Group;
-};
-
-class KRITAUNDO2_EXPORT KUndo2Stack : public KUndo2QStack
-{
-public:
- explicit KUndo2Stack(QObject *parent = 0);
-
- // functions from KUndoStack
- QAction* createRedoAction(KActionCollection* actionCollection, const QString& actionName = QString());
- QAction* createUndoAction(KActionCollection* actionCollection, const QString& actionName = QString());
-};
-
-#endif // QT_NO_UNDOSTACK
-
-#endif // KUNDO2STACK_H
diff --git a/libs/kundo2/kundo2view.h b/libs/kundo2/kundo2view.h
deleted file mode 100644
index 1e234034da..0000000000
--- a/libs/kundo2/kundo2view.h
+++ /dev/null
@@ -1,111 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-/****************************************************************************
-**
-** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
-** All rights reserved.
-** Contact: Nokia Corporation (qt-info@nokia.com)
-**
-** This file is part of the QtGui module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** No Commercial Usage
-** This file contains pre-release code and may not be distributed.
-** You may use this file in accordance with the terms and conditions
-** contained in the Technology Preview License Agreement accompanying
-** this package.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 2.1 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 2.1 requirements
-** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
-**
-** In addition, as a special exception, Nokia gives you certain additional
-** rights. These rights are described in the Nokia Qt LGPL Exception
-** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
-**
-** If you have questions regarding the use of this file, please contact
-** Nokia at qt-info@nokia.com.
-**
-**
-**
-**
-**
-**
-**
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-#ifndef KUNDO2VIEW_H
-#define KUNDO2VIEW_H
-
-#include <QListView>
-#include <QString>
-
-#include "kritaundo2_export.h"
-
-#ifndef QT_NO_UNDOVIEW
-
-class KUndo2ViewPrivate;
-class KUndo2QStack;
-class KUndo2Group;
-class QIcon;
-
-class KRITAUNDO2_EXPORT KUndo2View : public QListView
-{
- Q_OBJECT
- Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel)
- Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon)
-
-public:
- explicit KUndo2View(QWidget *parent = 0);
- explicit KUndo2View(KUndo2QStack *stack, QWidget *parent = 0);
-#ifndef QT_NO_UNDOGROUP
- explicit KUndo2View(KUndo2Group *group, QWidget *parent = 0);
-#endif
- ~KUndo2View();
-
- KUndo2QStack *stack() const;
-#ifndef QT_NO_UNDOGROUP
- KUndo2Group *group() const;
-#endif
-
- void setEmptyLabel(const QString &label);
- QString emptyLabel() const;
-
- void setCleanIcon(const QIcon &icon);
- QIcon cleanIcon() const;
-
-public Q_SLOTS:
- void setStack(KUndo2QStack *stack);
-#ifndef QT_NO_UNDOGROUP
- void setGroup(KUndo2Group *group);
-#endif
-
-private:
- KUndo2ViewPrivate* const d;
- Q_DISABLE_COPY(KUndo2View)
-};
-
-#endif // QT_NO_UNDOVIEW
-#endif // KUNDO2VIEW_H
diff --git a/libs/libkis/Action.cpp b/libs/libkis/Action.cpp
new file mode 100644
index 0000000000..00df6f3667
--- /dev/null
+++ b/libs/libkis/Action.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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 "Action.h"
+
+
+struct Action::Private {
+ Private() {}
+ QAction *action {0};
+};
+
+Action::Action(QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->action = new KisAction(this);
+ d->action->setProperty("menu", "tools/scripts");
+ connect(d->action, SIGNAL(triggered(bool)), SIGNAL(triggered(bool)));
+}
+
+Action::Action(const QString &name, QAction *action, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->action = action;
+ d->action->setObjectName(name);
+ d->action->setProperty("menu", "tools/scripts");
+ connect(d->action, SIGNAL(triggered(bool)), SIGNAL(triggered(bool)));
+}
+
+Action::~Action()
+{
+ delete d;
+}
+
+
+bool Action::operator==(const Action &other) const
+{
+ return (d->action == other.d->action);
+}
+
+bool Action::operator!=(const Action &other) const
+{
+ return !(operator==(other));
+}
+
+
+QString Action::text() const
+{
+ if (!d->action) return "";
+ return d->action->text();
+}
+
+void Action::settext(QString text)
+{
+ if (!d->action) return;
+ d->action->setText(text);
+}
+
+QString Action::name() const
+{
+ if (!d->action) return "";
+ return d->action->objectName();
+}
+
+void Action::setName(QString name)
+{
+ if (!d->action) return;
+ d->action->setObjectName(name);
+}
+
+bool Action::isCheckable() const
+{
+ if (!d->action) return false;
+ return d->action->isCheckable();
+}
+
+void Action::setCheckable(bool value)
+{
+ if (!d->action) return;
+ d->action->setCheckable(value);
+}
+
+bool Action::isChecked() const
+{
+ if (!d->action) return false;
+ return d->action->isChecked();
+}
+
+void Action::setChecked(bool value)
+{
+ if (!d->action) return;
+ d->action->setChecked(value);
+}
+
+QString Action::shortcut() const
+{
+ if (!d->action) return QString();
+ return d->action->shortcut().toString();
+}
+
+void Action::setShortcut(QString value)
+{
+ if (!d->action) return;
+ d->action->setShortcut(QKeySequence::fromString(value));
+}
+
+bool Action::isVisible() const
+{
+ if (!d->action) return false;
+ return d->action->property("menu").toString() == "tools/scripts";
+}
+
+void Action::setVisible(bool value)
+{
+ if (!d->action) return;
+ if (value) {
+ d->action->setProperty("menu", "tools/scripts");
+ }
+ else {
+ d->action->setProperty("menu", "");
+ }
+}
+
+bool Action::isEnabled() const
+{
+ if (!d->action) return false;
+ return d->action->isEnabled();
+}
+
+void Action::setEnabled(bool value)
+{
+ if (!d->action) return;
+ d->action->setEnabled(value);
+}
+
+void Action::setToolTip(QString tooltip)
+{
+ if (!d->action) return;
+ d->action->setToolTip(tooltip);
+}
+
+void Action::trigger()
+{
+ d->action->trigger();
+}
diff --git a/libs/libkis/Action.h b/libs/libkis/Action.h
new file mode 100644
index 0000000000..1f3dcd0a85
--- /dev/null
+++ b/libs/libkis/Action.h
@@ -0,0 +1,148 @@
+/*
+ * 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_ACTION_H
+#define LIBKIS_ACTION_H
+
+#include <kis_action.h>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+/**
+ * Action encapsulates a KisAction. By default, actions are put in the Tools/Scripts menu.
+ */
+class KRITALIBKIS_EXPORT Action : public QObject
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief Action Create a new action object
+ * @param parent the parent if it's in a QObject hierarchy
+ */
+ explicit Action(QObject *parent = 0);
+
+ /**
+ * @brief Action Create a new action object
+ * @param name the name of the action
+ * @param action the QAction it wraps
+ * @param parent the parent if it's in a QObject hierarchy
+ */
+ Action(const QString &name, QAction *action, QObject *parent = 0);
+ virtual ~Action();
+
+ bool operator==(const Action &other) const;
+ bool operator!=(const Action &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * @return the user-visible text of the action.
+ */
+ QString text() const;
+
+ /**
+ * set the user-visible text of the action to @param text.
+ */
+ void settext(QString text);
+
+ /**
+ * @return the internal name of the action.
+ */
+ QString name() const;
+
+ /**
+ * set the name of the action to @param name. This is not the user-visible name, but the internal one
+ */
+ void setName(QString name);
+
+ /**
+ * @return true if the action is checkable, false if it isn't*
+ */
+ bool isCheckable() const;
+
+ /**
+ * Set the action action checkable if @param value is true, unchecked if it's false
+ */
+ void setCheckable(bool value);
+
+ /**
+ * @return true if the action is checked, false if it isn't
+ */
+ bool isChecked() const;
+
+ /**
+ * Set the action checked if @param value is true, unchecked if it's false
+ */
+ void setChecked(bool value);
+
+ /**
+ * Return the action's shortcut as a string
+ */
+ QString shortcut() const;
+
+ /**
+ * set the action's shortcut to the given string.
+ * @code
+ * action.setShortcut("CTRL+SHIFT+S")
+ * @endcode
+ */
+ void setShortcut(QString value);
+
+ bool isVisible() const;
+ /**
+ * @brief setVisible determines whether the action will be visible in the scripting menu.
+ * @param value true if the action is to be shown in the menu, false otherwise
+ */
+ void setVisible(bool value);
+
+ /**
+ * @return true if the action is enabled, false if not
+ */
+ bool isEnabled() const;
+
+ /**
+ * Set the action enabled or disabled according to @param value
+ */
+ void setEnabled(bool value);
+
+ /**
+ * Set the tooltip to the given @param tooltip
+ */
+ void setToolTip(QString tooltip);
+
+ /**
+ * Trigger this action
+ */
+ void trigger();
+
+Q_SIGNALS:
+
+ /**
+ * Emitted whenever the action is triggered.
+ */
+ void triggered(bool);
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+
+#endif // LIBKIS_ACTION_H
diff --git a/libs/libkis/CMakeLists.txt b/libs/libkis/CMakeLists.txt
new file mode 100644
index 0000000000..8ef7be672b
--- /dev/null
+++ b/libs/libkis/CMakeLists.txt
@@ -0,0 +1,34 @@
+set(kritalibkis_LIB_SRCS
+ Action.cpp
+ Canvas.cpp
+ Channel.cpp
+ DockWidget.cpp
+ DockWidgetFactoryBase.cpp
+ Document.cpp
+ Filter.cpp
+ InfoObject.cpp
+ Krita.cpp
+ Node.cpp
+ Notifier.cpp
+ PresetChooser
+ Resource.cpp
+ Selection.cpp
+ View.cpp
+ Extension.cpp
+ Window.cpp
+)
+
+add_library(kritalibkis SHARED ${kritalibkis_LIB_SRCS} )
+generate_export_header(kritalibkis)
+
+target_link_libraries(kritalibkis kritaui kritaimage kritaversion)
+target_link_libraries(kritalibkis LINK_INTERFACE_LIBRARIES kritaimage kritaui)
+
+set_target_properties(kritalibkis PROPERTIES
+ VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
+)
+
+install(TARGETS kritalibkis ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+
+add_subdirectory(tests)
diff --git a/libs/libkis/Canvas.cpp b/libs/libkis/Canvas.cpp
new file mode 100644
index 0000000000..378d38bf2d
--- /dev/null
+++ b/libs/libkis/Canvas.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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);
+}
+
+
+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;
+}
+
+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/Canvas.h b/libs/libkis/Canvas.h
new file mode 100644
index 0000000000..22e20f57a4
--- /dev/null
+++ b/libs/libkis/Canvas.h
@@ -0,0 +1,119 @@
+/*
+ * 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_CANVAS_H
+#define LIBKIS_CANVAS_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+class KoCanvasBase;
+
+/**
+ * Canvas wraps the canvas inside a view on an image/document.
+ * It is responsible for the view parameters of the document:
+ * zoom, rotation, mirror, wraparound and instant preview.
+ */
+class KRITALIBKIS_EXPORT Canvas : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Canvas(KoCanvasBase *canvas, QObject *parent = 0);
+ virtual ~Canvas();
+
+ bool operator==(const Canvas &other) const;
+ bool operator!=(const Canvas &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * @return the current zoomlevel. 1.0 is 100%.
+ */
+ qreal zoomLevel() const;
+
+ /**
+ * @brief setZoomLevel set the zoomlevel to the given @param value. 1.0 is 100%.
+ */
+ void setZoomLevel(qreal value);
+
+ /**
+ * @brief resetZoom set the zoomlevel to 100%
+ */
+ void resetZoom();
+
+ /**
+ * @return the rotation of the canvas in degrees.
+ */
+ qreal rotation() const;
+
+ /**
+ * @brief setRotation set the rotation of the canvas to the given @param angle in degrees.
+ */
+ void setRotation(qreal angle);
+
+ /**
+ * @brief resetRotation reset the canvas rotation.
+ */
+ void resetRotation();
+
+ /**
+ * @return return true if the canvas is mirrored, false otherwise.
+ */
+ bool mirror() const;
+
+ /**
+ * @brief setMirror turn the canvas mirroring on or off depending on @param value
+ */
+ void setMirror(bool value);
+
+ /**
+ * @return true if the canvas is in wraparound mode, false if not. Only when OpenGL is enabled,
+ * is wraparound mode available.
+ */
+ bool wrapAroundMode() const;
+
+ /**
+ * @brief setWrapAroundMode set wraparound mode to @param enable
+ */
+ void setWrapAroundMode(bool enable);
+
+ /**
+ * @return true if the canvas is in Instant Preview mode, false if not. Only when OpenGL is enabled,
+ * is Instant Preview mode available.
+ */
+ bool levelOfDetailMode() const;
+
+ /**
+ * @brief setLevelOfDetailMode sets Instant Preview to @param enable
+ */
+ void setLevelOfDetailMode(bool enable);
+
+ /**
+ * @return the view that holds this canvas
+ */
+ View *view() const;
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_CANVAS_H
diff --git a/libs/libkis/Channel.cpp b/libs/libkis/Channel.cpp
new file mode 100644
index 0000000000..93518e85c8
--- /dev/null
+++ b/libs/libkis/Channel.cpp
@@ -0,0 +1,232 @@
+/*
+ * 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 "Channel.h"
+
+#include <QByteArray>
+#include <QDataStream>
+
+#include <KoColorModelStandardIds.h>
+#include <KoConfig.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoColorSpace.h>
+#include <kis_sequential_iterator.h>
+#include <kis_layer.h>
+
+#ifdef HAVE_OPENEXR
+#include <half.h>
+#endif
+
+struct Channel::Private {
+ Private() {}
+
+ KisNodeSP node;
+ KoChannelInfo *channel;
+
+};
+
+Channel::Channel(KisNodeSP node, KoChannelInfo *channel, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->node = node;
+ d->channel = channel;
+}
+
+Channel::~Channel()
+{
+ delete d;
+}
+
+
+bool Channel::operator==(const Channel &other) const
+{
+ return (d->node == other.d->node
+ && d->channel == other.d->channel);
+}
+
+bool Channel::operator!=(const Channel &other) const
+{
+ return !(operator==(other));
+}
+
+
+bool Channel::visible() const
+{
+ if (!d->node || !d->channel) return false;
+ if (!d->node->inherits("KisLayer")) return false;
+ for (uint i = 0; i < d->node->colorSpace()->channelCount(); ++i) {
+ if (d->node->colorSpace()->channels()[i] == d->channel) {
+ KisLayerSP layer = qobject_cast<KisLayer*>(d->node.data());
+ QBitArray flags = layer->channelFlags();
+ return flags.testBit(i);
+ }
+ }
+ return false;
+}
+
+void Channel::setVisible(bool value)
+{
+ if (!d->node || !d->channel) return;
+ if (!d->node->inherits("KisLayer")) return;
+
+ for (uint i = 0; i < d->node->colorSpace()->channelCount(); ++i) {
+ if (d->node->colorSpace()->channels()[i] == d->channel) {
+ QBitArray flags = d->node->colorSpace()->channelFlags(true, true);
+ flags.setBit(i, value);
+ KisLayerSP layer = qobject_cast<KisLayer*>(d->node.data());
+ layer->setChannelFlags(flags);
+ break;
+ }
+ }
+
+}
+
+QString Channel::name() const
+{
+ return d->channel->name();
+}
+
+int Channel::position() const
+{
+ return d->channel->pos();
+}
+
+int Channel::channelSize() const
+{
+ return d->channel->size();
+}
+
+QRect Channel::bounds() const
+{
+ if (!d->node || !d->channel) return QRect();
+
+ QRect rect = d->node->exactBounds();
+
+ KisPaintDeviceSP dev;
+ if (d->node->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) {
+ dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
+ }
+ else if (d->node->colorSpace()->colorDepthId() == Integer16BitsColorDepthID) {
+ dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha16());
+ }
+#ifdef HAVE_OPENEXR
+ else if (d->node->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
+ dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha16f());
+ }
+#endif
+ else if (d->node->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
+ dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha32f());
+ }
+
+ KisSequentialConstIterator srcIt(d->node->projection(), rect);
+ KisSequentialIterator dstIt(dev, rect);
+
+ do {
+ const quint8 *srcPtr = srcIt.rawDataConst();
+ memcpy(dstIt.rawData(), srcPtr + d->channel->pos(), d->channel->size());
+
+ } while(srcIt.nextPixel() && dstIt.nextPixel());
+
+ if (dev) {
+ return dev->exactBounds();
+ }
+
+ return QRect();
+}
+
+QByteArray Channel::pixelData(const QRect &rect) const
+{
+ QByteArray ba;
+
+ if (!d->node || !d->channel) return ba;
+
+ QDataStream stream(&ba, QIODevice::WriteOnly);
+ KisSequentialConstIterator srcIt(d->node->projection(), rect);
+
+ if (d->node->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) {
+ do {
+ stream << (quint8) *srcIt.rawDataConst();
+ } while(srcIt.nextPixel());
+ }
+ else if (d->node->colorSpace()->colorDepthId() == Integer16BitsColorDepthID) {
+ do {
+ stream << (quint16) *srcIt.rawDataConst();
+ } while(srcIt.nextPixel());
+ }
+#ifdef HAVE_OPENEXR
+ else if (d->node->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
+ do {
+ half h = (half)*srcIt.rawDataConst();
+ stream << (float)h;
+ } while(srcIt.nextPixel());
+ }
+#endif
+ else if (d->node->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
+ do {
+ stream << (float) *srcIt.rawDataConst();
+ } while(srcIt.nextPixel());
+
+ }
+
+ return ba;
+}
+
+void Channel::setPixelData(QByteArray value, const QRect &rect)
+{
+ if (!d->node || !d->channel || d->node->paintDevice() == 0) return;
+
+ QDataStream stream(&value, QIODevice::ReadOnly);
+ KisSequentialIterator dstIt(d->node->paintDevice(), rect);
+
+ if (d->node->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) {
+ do {
+ quint8 v;
+ stream >> v;
+ *dstIt.rawData() = v ;
+ } while(dstIt.nextPixel());
+ }
+ else if (d->node->colorSpace()->colorDepthId() == Integer16BitsColorDepthID) {
+ do {
+ quint16 v;
+ stream >> v;
+ *dstIt.rawData() = v ;
+ } while(dstIt.nextPixel());
+ }
+#ifdef HAVE_OPENEXR
+ else if (d->node->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
+ do {
+ float f;
+ stream >> f;
+ half v = f;
+ *dstIt.rawData() = v ;
+ } while(dstIt.nextPixel());
+
+ }
+#endif
+ else if (d->node->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
+ do {
+ float v;
+ stream >> v;
+ *dstIt.rawData() = v ;
+ } while(dstIt.nextPixel());
+ }
+}
+
+
+
+
diff --git a/libs/libkis/Channel.h b/libs/libkis/Channel.h
new file mode 100644
index 0000000000..b5384c4c15
--- /dev/null
+++ b/libs/libkis/Channel.h
@@ -0,0 +1,103 @@
+/*
+ * 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_CHANNEL_H
+#define LIBKIS_CHANNEL_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+#include <KoChannelInfo.h>
+#include <kis_node.h>
+
+/**
+ * A Channel represents a singel channel in a Node. Krita does not
+ * use channels to store local selections: these are strictly the
+ * color and alpha channels.
+ */
+class KRITALIBKIS_EXPORT Channel : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Channel(KisNodeSP node, KoChannelInfo *channel, QObject *parent = 0);
+ virtual ~Channel();
+
+ bool operator==(const Channel &other) const;
+ bool operator!=(const Channel &other) const;
+
+ /**
+ * @brief visible checks whether this channel is visible in the node
+ * @return the status of this channel
+ */
+ bool visible() const;
+
+ /**
+ * @brief setvisible set the visibility of the channel to the given value.
+ */
+ void setVisible(bool value);
+
+ /**
+ * @return the name of the channel
+ */
+ QString name() const;
+
+ /**
+ * @returns the position of the first byte of the channel in the pixel
+ */
+ int position() const;
+
+ /**
+ * @return the number of bytes this channel takes
+ */
+ int channelSize() const;
+
+ /**
+ * @return the exact bounds of the channel. This can be smaller than the bounds of the Node this channel is part of.
+ */
+ QRect bounds() const;
+
+ /**
+ * Read the values of the channel into the a byte array for each pixel in the rect from the Node this channel is part of, and returns it.
+ *
+ * Note that if Krita is built with OpenEXR and the Node has the 16 bits floating point channel depth type, Krita returns
+ * 32 bits float for every channel; the libkis scripting API does not support half.
+ */
+ QByteArray pixelData(const QRect &rect) const;
+
+ /**
+ * @brief setPixelData writes the given data to the relevant channel in the Node. This is only possible for Nodes
+ * that have a paintDevice, so nothing will happen when trying to write to e.g. a group layer.
+ *
+ * Note that if Krita is built with OpenEXR and the Node has the 16 bits floating point channel depth type, Krita expects
+ * to be given a 4 byte, 32 bits float for every channel; the libkis scripting API does not support half.
+ *
+ * @param value a byte array with exactly enough bytes.
+ * @param rect the rectangle to write the bytes into
+ */
+ void setPixelData(QByteArray value, const QRect &rect);
+
+private:
+
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_CHANNEL_H
diff --git a/libs/libkis/DockWidget.cpp b/libs/libkis/DockWidget.cpp
new file mode 100644
index 0000000000..f7cc48d949
--- /dev/null
+++ b/libs/libkis/DockWidget.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "DockWidget.h"
+#include <QDebug>
+
+#include <KoCanvasBase.h>
+
+#include "Canvas.h"
+
+struct DockWidget::Private {
+ Private() {}
+
+ Canvas *canvas {0};
+};
+
+DockWidget::DockWidget()
+ : QDockWidget()
+ , d(new Private)
+{
+}
+
+DockWidget::~DockWidget()
+{
+ delete d;
+}
+
+Canvas* DockWidget::canvas() const
+{
+ return d->canvas;
+}
+
+void DockWidget::setCanvas(KoCanvasBase* canvas)
+{
+ delete d->canvas;
+ d->canvas = new Canvas(canvas);
+ canvasChanged(d->canvas);
+}
+
+void DockWidget::unsetCanvas()
+{
+ delete d->canvas;
+ d->canvas = 0;
+}
diff --git a/libs/libkis/DockWidget.h b/libs/libkis/DockWidget.h
new file mode 100644
index 0000000000..e038268151
--- /dev/null
+++ b/libs/libkis/DockWidget.h
@@ -0,0 +1,87 @@
+/*
+ * 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_DOCKWIDGET_H
+#define LIBKIS_DOCKWIDGET_H
+
+#include <QDockWidget>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+#include <KoCanvasObserverBase.h>
+
+class KoCanvasBase;
+
+/**
+ * DockWidget is the base class for custom Dockers. Dockers are created by a
+ * factory class which needs to be registered by calling Application.addDockWidgetFactory:
+ *
+ * @code
+ * class HelloDocker(DockWidget):
+ * def __init__(self):
+ * super().__init__()
+ * label = QLabel("Hello", self)
+ * self.setWidget(label)
+ * self.label = label
+ *
+ * def canvasChanged(self, canvas):
+ * self.label.setText("Hellodocker: canvas changed");
+ *
+ * Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker))
+ *
+ * @endcode
+ *
+ * One docker per window will be created, not one docker per canvas or view. When the user
+ * switches between views/canvases, canvasChanged will be called. You can override that
+ * method to reset your docker's internal state, if necessary.
+ */
+class KRITALIBKIS_EXPORT DockWidget : public QDockWidget, public KoCanvasObserverBase
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(DockWidget)
+
+public:
+ explicit DockWidget();
+ virtual ~DockWidget();
+
+protected Q_SLOTS: // Krita API
+
+ virtual void setCanvas(KoCanvasBase* canvas);
+ virtual void unsetCanvas();
+
+protected Q_SLOTS: // PyKrita API
+
+ /**
+ * @@return the canvas object that this docker is currently associated with
+ */
+ Canvas* canvas() const;
+
+ /**
+ * @brief canvasChanged is called whenever the current canvas is changed
+ * in the mainwindow this dockwidget instance is shown in.
+ * @param canvas The new canvas.
+ */
+ virtual void canvasChanged(Canvas *canvas) = 0;
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_DOCKWIDGET_H
diff --git a/libs/libkis/DockWidgetFactoryBase.cpp b/libs/libkis/DockWidgetFactoryBase.cpp
new file mode 100644
index 0000000000..2f6df382ff
--- /dev/null
+++ b/libs/libkis/DockWidgetFactoryBase.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015 Cyrille Berger <cberger@cberger.net>
+ *
+ * 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 "DockWidgetFactoryBase.h"
+#include <QDebug>
+
+DockWidgetFactoryBase::DockWidgetFactoryBase(const QString& _id, KoDockFactoryBase::DockPosition _dockPosition, bool _isCollapsable, bool _defaultCollapsed)
+ : m_id(_id),
+ m_dockPosition(_dockPosition),
+ m_isCollapsable(_isCollapsable),
+ m_defaultCollapsed(_defaultCollapsed)
+{
+
+}
+
+DockWidgetFactoryBase::~DockWidgetFactoryBase()
+{
+}
+
+bool DockWidgetFactoryBase::defaultCollapsed() const
+{
+ return m_defaultCollapsed;
+}
+
+KoDockFactoryBase::DockPosition DockWidgetFactoryBase::defaultDockPosition() const
+{
+ return m_dockPosition;
+}
+
+QString DockWidgetFactoryBase::id() const
+{
+ return m_id;
+}
+
+bool DockWidgetFactoryBase::isCollapsable() const
+{
+ return m_isCollapsable;
+}
diff --git a/libs/libkis/DockWidgetFactoryBase.h b/libs/libkis/DockWidgetFactoryBase.h
new file mode 100644
index 0000000000..0eb2920933
--- /dev/null
+++ b/libs/libkis/DockWidgetFactoryBase.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015 Cyrille Berger <cberger@cberger.net>
+ *
+ * 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_DOCKWIDGETFACTORY_H
+#define LIBKIS_DOCKWIDGETFACTORY_H
+
+#include <QString>
+#include <KoDockFactoryBase.h>
+
+#include "kritalibkis_export.h"
+
+/**
+ * @brief The DockWidgetFactoryBase class is the base class for plugins that want
+ * to add a dock widget to every window. You do not need to implement this class
+ * yourself, but create a DockWidget implementation and then add the DockWidgetFactory
+ * to the Krita instance like this:
+ *
+ * @code
+ * class HelloDocker(DockWidget):
+ * def __init__(self):
+ * super().__init__()
+ * label = QLabel("Hello", self)
+ * self.setWidget(label)
+ * self.label = label
+ *
+ * def canvasChanged(self, canvas):
+ * self.label.setText("Hellodocker: canvas changed");
+ *
+ * Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker))
+ *
+ * @endcode
+ */
+class KRITALIBKIS_EXPORT DockWidgetFactoryBase : public KoDockFactoryBase
+{
+public:
+ DockWidgetFactoryBase(const QString& _id, DockPosition _dockPosition, bool _isCollapsable = true, bool _defaultCollapsed = false);
+ virtual ~DockWidgetFactoryBase();
+ virtual QString id() const;
+ virtual DockPosition defaultDockPosition() const;
+ virtual bool isCollapsable() const;
+ virtual bool defaultCollapsed() const;
+private:
+ QString m_id;
+ DockPosition m_dockPosition;
+ bool m_isCollapsable, m_defaultCollapsed;
+};
+
+#endif
diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp
new file mode 100644
index 0000000000..5525ece21e
--- /dev/null
+++ b/libs/libkis/Document.cpp
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2016 Boudewijn Rempt <boud@valdyas.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "Document.h"
+#include <QPointer>
+#include <QUrl>
+#include <QDomDocument>
+
+#include <KoColorSpaceConstants.h>
+#include <KoXmlReader.h>
+#include <KisDocument.h>
+#include <kis_colorspace_convert_visitor.h>
+#include <kis_image.h>
+#include <KisPart.h>
+#include <kis_paint_device.h>
+#include <KisMainWindow.h>
+#include <kis_node_manager.h>
+#include <kis_node_selection_adapter.h>
+#include <KisViewManager.h>
+#include <kis_file_layer.h>
+#include <kis_adjustment_layer.h>
+#include <kis_mask.h>
+#include <kis_clone_layer.h>
+#include <kis_group_layer.h>
+#include <kis_filter_mask.h>
+#include <kis_transform_mask.h>
+#include <kis_transparency_mask.h>
+#include <kis_selection_mask.h>
+#include <kis_effect_mask.h>
+#include <kis_paint_layer.h>
+#include <kis_generator_layer.h>
+#include <kis_shape_layer.h>
+#include <kis_filter_configuration.h>
+#include <kis_selection.h>
+
+#include <KoColorSpace.h>
+#include <KoColorProfile.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoColorConversionTransformation.h>
+#include <KoDocumentInfo.h>
+
+#include <InfoObject.h>
+#include <Node.h>
+#include <Selection.h>
+
+struct Document::Private {
+ Private() {}
+ QPointer<KisDocument> document;
+};
+
+Document::Document(KisDocument *document, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->document = document;
+}
+
+Document::~Document()
+{
+ delete d;
+}
+
+bool Document::operator==(const Document &other) const
+{
+ return (d->document == other.d->document);
+}
+
+bool Document::operator!=(const Document &other) const
+{
+ return !(operator==(other));
+}
+
+bool Document::batchmode() const
+{
+ if (!d->document) return false;
+ return d->document->fileBatchMode();
+}
+
+void Document::setBatchmode(bool value)
+{
+ if (!d->document) return;
+ d->document->setFileBatchMode(value);
+}
+
+Node *Document::activeNode() const
+{
+ QList<KisNodeSP> activeNodes;
+ Q_FOREACH(QPointer<KisView> view, KisPart::instance()->views()) {
+ if (view && view->document() == d->document) {
+ activeNodes << view->currentNode();
+ }
+ }
+ if (activeNodes.size() > 0) {
+ return new Node(d->document->image(), activeNodes.first());
+ }
+ return new Node(d->document->image(), d->document->image()->root()->firstChild());
+}
+
+void Document::setActiveNode(Node* value)
+{
+ if (!value->node()) return;
+ KisMainWindow *mainWin = KisPart::instance()->currentMainwindow();
+ if (!mainWin) return;
+ KisViewManager *viewManager = mainWin->viewManager();
+ if (!viewManager) return;
+ if (viewManager->document() != d->document) return;
+ KisNodeManager *nodeManager = viewManager->nodeManager();
+ if (!nodeManager) return;
+ KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter();
+ if (!selectionAdapter) return;
+ selectionAdapter->setActiveNode(value->node());
+
+}
+
+QList<Node *> Document::topLevelNodes() const
+{
+ if (!d->document) return QList<Node *>();
+ Node n(d->document->image(), d->document->image()->rootLayer());
+ return n.childNodes();
+}
+
+
+Node *Document::nodeByName(const QString &name) const
+{
+ if (!d->document) return 0;
+ KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name);
+ return new Node(d->document->image(), node);
+}
+
+
+QString Document::colorDepth() const
+{
+ if (!d->document) return "";
+ return d->document->image()->colorSpace()->colorDepthId().id();
+}
+
+QString Document::colorModel() const
+{
+ if (!d->document) return "";
+ return d->document->image()->colorSpace()->colorModelId().id();
+}
+
+QString Document::colorProfile() const
+{
+ if (!d->document) return "";
+ return d->document->image()->colorSpace()->profile()->name();
+}
+
+bool Document::setColorProfile(const QString &value)
+{
+ if (!d->document) return false;
+ if (!d->document->image()) return false;
+ const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value);
+ if (!profile) return false;
+ bool retval = d->document->image()->assignImageProfile(profile);
+ d->document->image()->setModified();
+ d->document->image()->initialRefreshGraph();
+ return retval;
+}
+
+bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile)
+{
+ if (!d->document) return false;
+ if (!d->document->image()) return false;
+ const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile);
+ if (!colorSpace) return false;
+
+ d->document->image()->convertImageColorSpace(colorSpace,
+ KoColorConversionTransformation::IntentPerceptual,
+ KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization);
+
+ d->document->image()->setModified();
+ d->document->image()->initialRefreshGraph();
+ return true;
+}
+
+
+QString Document::documentInfo() const
+{
+ QDomDocument doc = KisDocument::createDomDocument("document-info"
+ /*DTD name*/, "document-info" /*tag name*/, "1.1");
+ doc = d->document->documentInfo()->save(doc);
+ return doc.toString();
+}
+
+void Document::setDocumentInfo(const QString &document)
+{
+ KoXmlDocument doc = KoXmlDocument(true);
+ QString errorMsg;
+ int errorLine, errorColumn;
+ doc.setContent(document, &errorMsg, &errorLine, &errorColumn);
+ d->document->documentInfo()->load(doc);
+}
+
+QString Document::fileName() const
+{
+ if (!d->document) return QString::null;
+ return d->document->url().toLocalFile();
+}
+
+void Document::setFileName(QString value)
+{
+ if (!d->document) return;
+ d->document->setUrl(QUrl::fromLocalFile(value));
+}
+
+
+int Document::height() const
+{
+ if (!d->document) return 0;
+ KisImageSP image = d->document->image();
+ if (!image) return 0;
+ return image->height();
+}
+
+void Document::setHeight(int value)
+{
+ if (!d->document) return;
+ if (!d->document->image()) return;
+ resizeImage(d->document->image()->width(), value);
+}
+
+
+QString Document::name() const
+{
+ if (!d->document) return "";
+ return d->document->documentInfo()->aboutInfo("title");
+}
+
+void Document::setName(QString value)
+{
+ if (!d->document) return;
+ d->document->documentInfo()->setAboutInfo("title", value);
+}
+
+
+int Document::resolution() const
+{
+ if (!d->document) return 0;
+ KisImageSP image = d->document->image();
+ if (!image) return 0;
+
+ return qRound(d->document->image()->xRes() * 72);
+}
+
+void Document::setResolution(int value)
+{
+ if (!d->document) return;
+ KisImageSP image = d->document->image();
+ if (!image) return;
+
+ d->document->image()->setResolution(value / 72.0, value / 72.0);
+}
+
+
+Node *Document::rootNode() const
+{
+ if (!d->document) return 0;
+ KisImageSP image = d->document->image();
+ if (!image) return 0;
+
+ return new Node(image, image->root());
+}
+
+Selection *Document::selection() const
+{
+ if (!d->document) return 0;
+ if (!d->document->image()) return 0;
+ if (!d->document->image()->globalSelection()) return 0;
+ return new Selection(d->document->image()->globalSelection());
+}
+
+void Document::setSelection(Selection* value)
+{
+ if (!d->document) return;
+ if (!d->document->image()) return;
+ if (value) {
+ d->document->image()->setGlobalSelection(value->selection());
+ }
+ else {
+ d->document->image()->setGlobalSelection(0);
+ }
+}
+
+
+int Document::width() const
+{
+ if (!d->document) return 0;
+ KisImageSP image = d->document->image();
+ if (!image) return 0;
+ return image->width();
+}
+
+void Document::setWidth(int value)
+{
+ if (!d->document) return;
+ if (!d->document->image()) return;
+ resizeImage(value, d->document->image()->height());
+}
+
+double Document::xRes() const
+{
+ if (!d->document) return 0.0;
+ if (!d->document->image()) return 0.0;
+ return d->document->image()->xRes();
+}
+
+void Document::setXRes(double xRes) const
+{
+ if (!d->document) return;
+ if (!d->document->image()) return;
+ d->document->image()->setResolution(xRes, d->document->image()->yRes());
+}
+
+double Document::yRes() const
+{
+ if (!d->document) return 0.0;
+ if (!d->document->image()) return 0.0;
+ return d->document->image()->yRes();
+}
+
+void Document::setYRes(double yRes) const
+{
+ if (!d->document) return;
+ if (!d->document->image()) return;
+ d->document->image()->setResolution(d->document->image()->xRes(), yRes);
+}
+
+
+QByteArray Document::pixelData(int x, int y, int w, int h) const
+{
+ QByteArray ba;
+
+ if (!d->document) return ba;
+ KisImageSP image = d->document->image();
+ if (!image) return ba;
+
+ KisPaintDeviceSP dev = image->projection();
+ ba.resize(w * h * dev->pixelSize());
+ dev->readBytes(reinterpret_cast<quint8*>(ba.data()), x, y, w, h);
+ return ba;
+}
+
+bool Document::close()
+{
+ bool retval = d->document->closeUrl(false);
+ Q_FOREACH(KisView *view, KisPart::instance()->views()) {
+ if (view->document() == d->document) {
+ view->close();
+ view->deleteLater();
+ }
+ }
+
+ d->document->deleteLater();
+ d->document = 0;
+ return retval;
+}
+
+void Document::crop(int x, int y, int w, int h)
+{
+ if (!d->document) return;
+ KisImageSP image = d->document->image();
+ if (!image) return;
+ QRect rc(x, y, w, h);
+ image->cropImage(rc);
+}
+
+bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration)
+{
+ if (!d->document) return false;
+ return d->document->exportDocument(QUrl::fromLocalFile(filename), exportConfiguration.configuration());
+}
+
+void Document::flatten()
+{
+ if (!d->document) return;
+ if (!d->document->image()) return;
+ d->document->image()->flatten();
+}
+
+void Document::resizeImage(int w, int h)
+{
+ if (!d->document) return;
+ KisImageSP image = d->document->image();
+ if (!image) return;
+ QRect rc = image->bounds();
+ rc.setWidth(w);
+ rc.setHeight(h);
+ image->resizeImage(rc);
+}
+
+bool Document::save()
+{
+ if (!d->document) return false;
+ return d->document->save();
+}
+
+bool Document::saveAs(const QString &filename)
+{
+ if (!d->document) return false;
+ return d->document->saveAs(QUrl::fromLocalFile(filename));
+}
+
+Node* Document::createNode(const QString &name, const QString &nodeType)
+{
+ if (!d->document) return 0;
+ if (!d->document->image()) return 0;
+ KisImageSP image = d->document->image();
+
+ Node *node = 0;
+
+ if (nodeType == "paintlayer") {
+ node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8));
+ }
+ else if (nodeType == "grouplayer") {
+ node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8));
+ }
+ else if (nodeType == "filelayer") {
+ node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8));
+ }
+ else if (nodeType == "filterlayer") {
+ node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0));
+ }
+ else if (nodeType == "filllayer") {
+ node = new Node(image, new KisGeneratorLayer(image, name, 0, 0));
+ }
+ else if (nodeType == "clonelayer") {
+ node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8));
+ }
+ else if (nodeType == "vectorlayer") {
+ node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8));
+ }
+ else if (nodeType == "transparencymask") {
+ node = new Node(image, new KisTransparencyMask());
+ }
+ else if (nodeType == "filtermask") {
+ node = new Node(image, new KisFilterMask());
+ }
+ else if (nodeType == "transformmask") {
+ node = new Node(image, new KisTransformMask());
+ }
+ else if (nodeType == "selectionmask") {
+ node = new Node(image, new KisSelectionMask(image));
+ }
+ return node;
+}
+
+QImage Document::projection(int x, int y, int w, int h) const
+{
+ if (!d->document || !d->document->image()) return QImage();
+ return d->document->image()->convertToQImage(x, y, w, h, 0);
+}
+
+QImage Document::thumbnail(int w, int h) const
+{
+ if (!d->document || !d->document->image()) return QImage();
+ return d->document->generatePreview(QSize(w, h)).toImage();
+}
+
+
+void Document::lock()
+{
+ if (!d->document || !d->document->image()) return;
+ d->document->image()->barrierLock();
+}
+
+void Document::unlock()
+{
+ if (!d->document || !d->document->image()) return;
+ d->document->image()->unlock();
+}
+
+void Document::waitForDone()
+{
+ if (!d->document || !d->document->image()) return;
+ d->document->image()->waitForDone();
+}
+
+bool Document::tryBarrierLock()
+{
+ if (!d->document || !d->document->image()) return false;
+ return d->document->image()->tryBarrierLock();
+}
+
+bool Document::isIdle()
+{
+ if (!d->document || !d->document->image()) return false;
+ return d->document->image()->isIdle();
+}
+
+void Document::refreshProjection()
+{
+ if (!d->document || !d->document->image()) return;
+ d->document->image()->refreshGraph();
+}
+
+QPointer<KisDocument> Document::document() const
+{
+ return d->document;
+}
diff --git a/libs/libkis/Document.h b/libs/libkis/Document.h
new file mode 100644
index 0000000000..061bd3a2bf
--- /dev/null
+++ b/libs/libkis/Document.h
@@ -0,0 +1,478 @@
+/*
+ * 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_DOCUMENT_H
+#define LIBKIS_DOCUMENT_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+class KisDocument;
+
+/**
+ * The Document class encapsulates a Krita Document/Image. A Krita document is an Image with
+ * a filename. Libkis does not differentiate between a document and an image, like Krita does
+ * internally.
+ */
+class KRITALIBKIS_EXPORT Document : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Document)
+
+public:
+ explicit Document(KisDocument *document, QObject *parent = 0);
+ virtual ~Document();
+
+ bool operator==(const Document &other) const;
+ bool operator!=(const Document &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * Batchmode means that no actions on the document should show dialogs or popups.
+ * @return true if the document is in batchmode.
+ */
+ bool batchmode() const;
+
+ /**
+ * Set batchmode to @param value. If batchmode is true, then there should be no popups
+ * or dialogs shown to the user.
+ */
+ void setBatchmode(bool value);
+
+ /**
+ * @brief activeNode retrieve the node that is currently active in the currently active window
+ * @return the active node. If there is no active window, the first child node is returned.
+ */
+ Node* activeNode() const;
+
+ /**
+ * @brief setActiveNode make the given node active in the currently active view and window
+ * @param value the node to make active.
+ */
+ void setActiveNode(Node* value);
+
+ /**
+ * @brief toplevelNodes return a list with all top level nodes in the image graph
+ */
+ QList<Node*> topLevelNodes() const;
+
+ /**
+ * @brief nodeByName searches the node tree for a node with the given name and returns it
+ * @param name the name of the node
+ * @return the first node with the given name or 0 if no node is found
+ */
+ Node *nodeByName(const QString &name) const;
+
+ /**
+ * colorDepth A string describing the color depth of the image:
+ * <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 documentInfo creates and XML document representing document and author information.
+ * @return a string containing a valid XML document with the right information about the document
+ * and author. The DTD can be found here:
+ *
+ * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/
+ *
+ * @code
+ * <?xml version="1.0" encoding="UTF-8"?>
+ * <!DOCTYPE document-info PUBLIC '-//KDE//DTD document-info 1.1//EN' 'http://www.calligra.org/DTD/document-info-1.1.dtd'>
+ * <document-info xmlns="http://www.calligra.org/DTD/document-info">
+ * <about>
+ * <title>My Document</title>
+ * <description></description>
+ * <subject></subject>
+ * <abstract><![CDATA[]]></abstract>
+ * <keyword></keyword>
+ * <initial-creator>Unknown</initial-creator>
+ * <editing-cycles>1</editing-cycles>
+ * <editing-time>35</editing-time>
+ * <date>2017-02-27T20:15:09</date>
+ * <creation-date>2017-02-27T20:14:33</creation-date>
+ * <language></language>
+ * </about>
+ * <author>
+ * <full-name>Boudewijn Rempt</full-name>
+ * <initial></initial>
+ * <author-title></author-title>
+ * <email></email>
+ * <telephone></telephone>
+ * <telephone-work></telephone-work>
+ * <fax></fax>
+ * <country></country>
+ * <postal-code></postal-code>
+ * <city></city>
+ * <street></street>
+ * <position></position>
+ * <company></company>
+ * </author>
+ * </document-info>
+ * @endcode
+ *
+ */
+ QString documentInfo() const;
+
+ /**
+ * @brief setDocumentInfo set the Document information to the information contained in document
+ * @param document A string containing a valid XML document that conforms to the document-info DTD
+ * that can be found here:
+ *
+ * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/
+ */
+ void setDocumentInfo(const QString &document);
+
+ /**
+ * @return the full path to the document, if it has been set.
+ */
+ QString fileName() const;
+
+ /**
+ * @brief setFileName set the full path of the document to @param value
+ */
+ void setFileName(QString value);
+
+ /**
+ * @return the height of the image in pixels
+ */
+ int height() const;
+
+ /**
+ * @brief setHeight resize the document to @param value height. This is a canvas resize, not a scale.
+ */
+ void setHeight(int value);
+
+ /**
+ * @return the name of the document. This is the title field in the @see documentInfo
+ */
+ QString name() const;
+
+ /**
+ * @brief setName sets the name of the document to @param value. This is the title field in the @see documentInfo
+ */
+ void setName(QString value);
+
+ /**
+ * @return the resolution in pixels per inch
+ */
+ int resolution() const;
+ /**
+ * @brief setResolution set the resolution of the image; this does not scale the image
+ * @param value the resolution in pixels per inch
+ */
+ void setResolution(int value);
+
+ /**
+ * @brief rootNode the root node is the invisible group layer that contains the entire node
+ * hierarchy.
+ * @return the root of the image
+ */
+ Node* rootNode() const;
+
+ /**
+ * @brief selection Create a Selection object around the global selection, if there is one.
+ * @return the global selection or None if there is no global selection.
+ */
+ Selection* selection() const;
+
+ /**
+ * @brief setSelection set or replace the global selection
+ * @param value a valid selection object.
+ */
+ void setSelection(Selection* value);
+
+ /**
+ * @return the width of the image in pixels.
+ */
+ int width() const;
+
+ /**
+ * @brief setWidth resize the document to @param value width. This is a canvas resize, not a scale.
+ */
+ void setWidth(int value);
+
+ /**
+ * @return xRes the horizontal resolution of the image in pixels per pt (there are 72 pts to an inch)
+ */
+ double xRes() const;
+
+ /**
+ * @brief setXRes set the horizontal resolution of the image to xRes in pixels per pt. (there are 72 pts to an inch)
+ */
+ void setXRes(double xRes) const;
+
+ /**
+ * @return yRes the vertical resolution of the image in pixels per pt (there are 72 pts to an inch)
+ */
+ double yRes() const;
+
+ /**
+ * @brief setYRes set the vertical resolution of the image to yRes in pixels per pt. (there are 72 pts to an inch)
+ */
+ void setYRes(double yRes) const;
+
+ /**
+ * @brief pixelData reads the given rectangle from the image projection and returns it as a byte
+ * array. The pixel data starts top-left, and is ordered row-first.
+ *
+ * The byte array can be interpreted as follows: 8 bits images have one byte per channel,
+ * and as many bytes as there are channels. 16 bits integer images have two bytes per channel,
+ * representing an unsigned short. 16 bits float images have two bytes per channel, representing
+ * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a
+ * float.
+ *
+ * You can read outside the image boundaries; those pixels will be transparent black.
+ *
+ * The order of channels is:
+ *
+ * <ul>
+ * <li>Integer RGBA: Blue, Green, Red, Alpha
+ * <li>Float RGBA: Red, Green, Blue, Alpha
+ * <li>LabA: L, a, b, Alpha
+ * <li>CMYKA: Cyan, Magenta, Yellow, Key, Alpha
+ * <li>XYZA: X, Y, Z, A
+ * <li>YCbCrA: Y, Cb, Cr, Alpha
+ * </ul>
+ *
+ * The byte array is a copy of the original image data. In Python, you can use bytes, bytearray
+ * and the struct module to interpret the data and construct, for instance, a Pillow Image object.
+ *
+ * @param x x position from where to start reading
+ * @param y y position from where to start reading
+ * @param w row length to read
+ * @param h number of rows to read
+ * @return a QByteArray with the pixel data. The byte array may be empty.
+ */
+ QByteArray pixelData(int x, int y, int w, int h) const;
+
+ /**
+ * @brief close Close the document: remove it from Krita's internal list of documents and
+ * close all views. If the document is modified, you should save it first. There will be
+ * no prompt for saving.
+ * @return true if the document is closed.
+ */
+ bool close();
+
+ /**
+ * @brief crop the image to rectangle described by @param x, @param y,
+ * @param w and @param h
+ */
+ void crop(int x, int y, int w, int h);
+
+ /**
+ * @brief exportImage export the image, without changing its URL to the given path.
+ * @param filename the full path to which the image is to be saved
+ * @param exportConfiguration a configuration object appropriate to the file format
+ * @return true if the export succeeded, false if it failed.
+ */
+ bool exportImage(const QString &filename, const InfoObject &exportConfiguration);
+
+ /**
+ * @brief flatten all layers in the image
+ */
+ void flatten();
+
+ /**
+ * @brief resizeImage resize the image to the given width and height.
+ * @param w the new width
+ * @param h the new height
+ */
+ void resizeImage(int w, int h);
+
+ /**
+ * @brief save the image to its currently set path. The modified flag of the
+ * document will be reset
+ * @return true if saving succeeded, false otherwise.
+ */
+ bool save();
+
+ /**
+ * @brief saveAs save the document under the @param filename. The document's
+ * filename will be reset to @param filename.
+ * @param filename the new filename (full path) for the document
+ * @return true if saving succeeded, false otherwise.
+ */
+ bool saveAs(const QString &filename);
+
+ /**
+ * @brief createNode create a new node of the given type. The node is not added
+ * to the node hierarchy; you need to do that by finding the right parent node,
+ * getting its list of child nodes and adding the node in the right place, then
+ * calling Node::SetChildNodes
+ *
+ * @param name The name of the node
+ *
+ * @param nodeType The type of the node. Valid types are:
+ * <ul>
+ * <li>paintlayer
+ * <li>grouplayer
+ * <li>filelayer
+ * <li>filterlayer
+ * <li>filllayer
+ * <li>clonelayer
+ * <li>vectorlayer
+ * <li>transparencymask
+ * <li>filtermask
+ * <li>transformmask
+ * <li>selectionmask
+ * </ul>
+ *
+ * When relevant, the new Node will have the colorspace of the image by default;
+ * that can be changed with Node::setColorSpace.
+ *
+ * The settings and selections for relevant layer and mask types can also be set
+ * after the Node has been created.
+ *
+ * @return the new Node.
+ */
+ Node* createNode(const QString &name, const QString &nodeType);
+
+ /**
+ * @brief projection creates a QImage from the rendered image or
+ * a cutout rectangle.
+ */
+ QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const;
+
+ /**
+ * @brief thumbnail create a thumbnail of the given dimensions.
+ *
+ * If the requested size is too big a null QImage is created.
+ *
+ * @return a QImage representing the layer contents.
+ */
+ QImage thumbnail(int w, int h) const;
+
+
+ /**
+ * Why this should be used, When it should be used, How it should be used,
+ * and warnings about when not.
+ */
+ void lock();
+
+ /**
+ * Why this should be used, When it should be used, How it should be used,
+ * and warnings about when not.
+ */
+ void unlock();
+
+ /**
+ * Why this should be used, When it should be used, How it should be used,
+ * and warnings about when not.
+ */
+ void waitForDone();
+
+ /**
+ * Why this should be used, When it should be used, How it should be used,
+ * and warnings about when not.
+ */
+ bool tryBarrierLock();
+
+ /**
+ * Why this should be used, When it should be used, How it should be used,
+ * and warnings about when not.
+ */
+ bool isIdle();
+
+ /**
+ * Starts a synchronous recomposition of the projection: everything will
+ * wait until the image is fully recomputed.
+ */
+ void refreshProjection();
+
+private:
+
+ friend class Krita;
+ friend class Window;
+ friend class Filter;
+ QPointer<KisDocument> document() const;
+
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_DOCUMENT_H
diff --git a/libs/libkis/Extension.cpp b/libs/libkis/Extension.cpp
new file mode 100644
index 0000000000..d195692b3b
--- /dev/null
+++ b/libs/libkis/Extension.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2015 Cyrille Berger <cberger@cberger.net>
+ * 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 "Extension.h"
+
+#include <QDebug>
+
+Extension::Extension(QObject* parent)
+ : QObject(parent)
+{
+}
+
+Extension::~Extension()
+{
+}
diff --git a/libs/libkis/Extension.h b/libs/libkis/Extension.h
new file mode 100644
index 0000000000..1bb43ea4b5
--- /dev/null
+++ b/libs/libkis/Extension.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015 Cyrille Berger <cberger@cberger.net>
+ *
+ * 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_EXTENSION_H
+#define LIBKIS_EXTENSION_H
+
+#include "kritalibkis_export.h"
+
+#include <QObject>
+
+/**
+ * An Extension is the base for classes that extend Krita. An Extension
+ * is loaded on startup, when the setup() method will be executed.
+ *
+ * The extension instance should be added to the Krita Application object
+ * using Krita.instance().addViewExtension or Application.addViewExtension
+ * or Scripter.addViewExtension.
+ *
+ * Example:
+ *
+ * @code
+ * import sys
+ * from PyQt5.QtGui import *
+ * from PyQt5.QtWidgets import *
+ * from krita import *
+ * class HelloExtension(Extension):
+ *
+ * def __init__(self, parent):
+ * super().__init__(parent)
+ *
+ * def hello(self):
+ * QMessageBox.information(QWidget(), "Test", "Hello! This is Krita " + Application.version())
+ *
+ * def setup(self):
+ * qDebug("Hello Setup")
+ * action = Krita.instance().createAction("hello")
+ * action.triggered.connect(self.hello)
+ *
+ * Scripter.addExtension(HelloExtension(Krita.instance()))
+ *
+ * @endcode
+ */
+class KRITALIBKIS_EXPORT Extension : public QObject
+{
+ Q_OBJECT
+public:
+
+ /**
+ * Create a new extension. The extension will be
+ * owned by @param parent.
+ */
+ explicit Extension(QObject *parent = 0);
+ virtual ~Extension();
+
+ /**
+ * Override this function to setup your Extension. You can use it to add
+ * Actions to the action collection or integrate in any other way with
+ * the application.
+ */
+ virtual void setup() = 0;
+};
+
+#endif
diff --git a/libs/libkis/Filter.cpp b/libs/libkis/Filter.cpp
new file mode 100644
index 0000000000..6df2c286ca
--- /dev/null
+++ b/libs/libkis/Filter.cpp
@@ -0,0 +1,168 @@
+/*
+ * 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 "Filter.h"
+
+#include <KoCanvasResourceManager.h>
+
+#include <kis_canvas_resource_provider.h>
+#include <kis_filter.h>
+#include <kis_filter.h>
+#include <kis_properties_configuration.h>
+#include <kis_filter_configuration.h>
+#include <kis_filter_manager.h>
+#include <kis_filter_registry.h>
+#include <KisPart.h>
+#include <KisView.h>
+
+#include <strokes/kis_filter_stroke_strategy.h>
+#include <krita_utils.h>
+
+#include "Krita.h"
+#include "Document.h"
+#include "InfoObject.h"
+#include "Node.h"
+
+struct Filter::Private {
+ Private() {}
+ QString name;
+ InfoObject *configuration {0};
+};
+
+Filter::Filter()
+ : QObject(0)
+ , d(new Private)
+{
+}
+
+Filter::~Filter()
+{
+ delete d->configuration;
+ delete d;
+}
+
+bool Filter::operator==(const Filter &other) const
+{
+ return (d->name == other.d->name
+ && d->configuration == other.d->configuration);
+}
+
+bool Filter::operator!=(const Filter &other) const
+{
+ return !(operator==(other));
+}
+
+
+QString Filter::name() const
+{
+ return d->name;
+}
+
+void Filter::setName(const QString &name)
+{
+ d->name = name;
+ delete d->configuration;
+
+ KisFilterSP filter = KisFilterRegistry::instance()->value(d->name);
+ d->configuration = new InfoObject(filter->defaultConfiguration());
+}
+
+InfoObject* Filter::configuration() const
+{
+ return d->configuration;
+}
+
+void Filter::setConfiguration(InfoObject* value)
+{
+ d->configuration = value;
+}
+
+bool Filter::apply(Node *node, int x, int y, int w, int h)
+{
+ if (node->locked()) return false;
+
+ KisFilterSP filter = KisFilterRegistry::instance()->value(d->name);
+ if (!filter) return false;
+
+ KisPaintDeviceSP dev = node->paintDevice();
+ if (!dev) return false;
+
+ QRect applyRect = QRect(x, y, w, h);
+ KisFilterConfigurationSP config = static_cast<KisFilterConfiguration*>(d->configuration->configuration().data());
+ filter->process(dev, applyRect, config);
+ return true;
+}
+
+bool Filter::startFilter(Node *node, int x, int y, int w, int h)
+{
+ if (node->locked()) return false;
+
+ KisFilterSP filter = KisFilterRegistry::instance()->value(d->name);
+ if (!filter) return false;
+
+ KisImageWSP image = node->image();
+ if (!image) return false;
+
+ KisFilterConfigurationSP filterConfig = static_cast<KisFilterConfiguration*>(d->configuration->configuration().data());
+
+ image->waitForDone();
+ QRect initialApplyRect = QRect(x, y, w, h);
+
+ QRect applyRect = initialApplyRect;
+
+ KisPaintDeviceSP paintDevice = node->paintDevice();
+ if (paintDevice && filter->needsTransparentPixels(filterConfig.data(), paintDevice->colorSpace())) {
+ applyRect |= image->bounds();
+ }
+
+ KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, node->node());
+
+ Document *document = Krita::instance()->activeDocument();
+ if (document && KisPart::instance()->viewCount(document->document()) > 0) {
+ Q_FOREACH (QPointer<KisView> view, KisPart::instance()->views()) {
+ if (view && view->document() == document->document()) {
+ resources = new KisResourcesSnapshot(image, node->node(), view->resourceProvider()->resourceManager());
+ break;
+ }
+ }
+ }
+ delete document;
+
+ KisStrokeId currentStrokeId = image->startStroke(new KisFilterStrokeStrategy(filter,
+ KisFilterConfigurationSP(filterConfig),
+ resources));
+
+ QRect processRect = filter->changedRect(applyRect, filterConfig.data(), 0);
+ processRect &= image->bounds();
+
+ if (filter->supportsThreading()) {
+ QSize size = KritaUtils::optimalPatchSize();
+ QVector<QRect> rects = KritaUtils::splitRectIntoPatches(processRect, size);
+ Q_FOREACH (const QRect &rc, rects) {
+ image->addJob(currentStrokeId, new KisFilterStrokeStrategy::Data(rc, true));
+ }
+ } else {
+ image->addJob(currentStrokeId, new KisFilterStrokeStrategy::Data(processRect, false));
+ }
+
+ image->endStroke(currentStrokeId);
+
+ return true;
+}
+
+
+
diff --git a/libs/libkis/Filter.h b/libs/libkis/Filter.h
new file mode 100644
index 0000000000..1aa75e41cc
--- /dev/null
+++ b/libs/libkis/Filter.h
@@ -0,0 +1,108 @@
+/*
+ * 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_FILTER_H
+#define LIBKIS_FILTER_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+/**
+ * Filter: represents a filter and its configuration. A filter is identified by
+ * an internal name. The configuration for each filter is defined as an InfoObject:
+ * a map of name and value pairs.
+ *
+ * Currently available filters are:
+ *
+ * 'autocontrast', 'blur', 'bottom edge detections', 'brightnesscontrast', 'burn', 'colorbalance', 'colortoalpha', 'colortransfer',
+ * 'desaturate', 'dodge', 'emboss', 'emboss all directions', 'emboss horizontal and vertical', 'emboss horizontal only',
+ * 'emboss laplascian', 'emboss vertical only', 'gaussian blur', 'gaussiannoisereducer', 'gradientmap', 'halftone', 'hsvadjustment',
+ * 'indexcolors', 'invert', 'left edge detections', 'lens blur', 'levels', 'maximize', 'mean removal', 'minimize', 'motion blur',
+ * 'noise', 'normalize', 'oilpaint', 'perchannel', 'phongbumpmap', 'pixelize', 'posterize', 'raindrops', 'randompick',
+ * 'right edge detections', 'roundcorners', 'sharpen', 'smalltiles', 'sobel', 'threshold', 'top edge detections', 'unsharp',
+ * 'wave', 'waveletnoisereducer']
+ */
+class KRITALIBKIS_EXPORT Filter : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Filter)
+
+public:
+ /**
+ * @brief Filter: create an empty filter object. Until a name is set, the filter cannot
+ * be applied.
+ */
+ explicit Filter();
+ virtual ~Filter();
+
+ bool operator==(const Filter &other) const;
+ bool operator!=(const Filter &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * @brief name the internal name of this filter.
+ * @return the name.
+ */
+ QString name() const;
+
+ /**
+ * @brief setName set the filter's name to the given name.
+ */
+ void setName(const QString &name);
+
+ /**
+ * @return the configuration object for the filter
+ */
+ InfoObject* configuration() const;
+
+ /**
+ * @brief setConfiguration set the configuration object for the filter
+ */
+ void setConfiguration(InfoObject* value);
+
+ /**
+ * @brief Apply the filter to the given node.
+ * @param node the node to apply the filter to
+ * @params x, y, w, h: describe the rectangle the filter should be apply.
+ * This is always in image pixel coordinates and not relative to the x, y
+ * of the node.
+ * @return true if the filter was applied succesfully, or
+ * false if the filter could not be applied because the node is locked or
+ * does not have an editable paint device.
+ */
+ bool apply(Node *node, int x, int y, int w, int h);
+
+ /**
+ * @brief startFilter starts the given filter on the given node.
+ *
+ * @param node the node to apply the filter to
+ * @params x, y, w, h: describe the rectangle the filter should be apply.
+ * This is always in image pixel coordinates and not relative to the x, y
+ * of the node.
+ */
+ bool startFilter(Node *node, int x, int y, int w, int h);
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_FILTER_H
diff --git a/libs/libkis/InfoObject.cpp b/libs/libkis/InfoObject.cpp
new file mode 100644
index 0000000000..5f1ed1ef24
--- /dev/null
+++ b/libs/libkis/InfoObject.cpp
@@ -0,0 +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.
+ */
+#include "InfoObject.h"
+
+#include <kis_properties_configuration.h>
+
+struct InfoObject::Private {
+ Private() {}
+
+ KisPropertiesConfigurationSP properties;
+};
+
+InfoObject::InfoObject(KisPropertiesConfigurationSP configuration)
+ : QObject(0)
+ , d(new Private)
+{
+ d->properties = configuration;
+}
+
+InfoObject::InfoObject(QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->properties = new KisPropertiesConfiguration();
+}
+
+InfoObject::~InfoObject()
+{
+ delete d;
+}
+
+bool InfoObject::operator==(const InfoObject &other) const
+{
+ return (d->properties == other.d->properties);
+}
+
+bool InfoObject::operator!=(const InfoObject &other) const
+{
+ return !(operator==(other));
+}
+
+QMap<QString, QVariant> InfoObject::properties() const
+{
+ return d->properties->getProperties();
+}
+
+void InfoObject::setProperties(QMap<QString, QVariant> proprertyMap)
+{
+ Q_FOREACH(const QString & key, proprertyMap.keys()) {
+ d->properties->setProperty(key, proprertyMap[key]);
+ }
+}
+
+void InfoObject::setProperty(const QString &key, QVariant value)
+{
+ d->properties->setProperty(key, value);
+}
+
+QVariant InfoObject::property(const QString &key)
+{
+ QVariant v;
+ if (d->properties->hasProperty(key)) {
+ d->properties->getProperty(key, v);
+ }
+ return v;
+}
+
+KisPropertiesConfigurationSP InfoObject::configuration() const
+{
+ return d->properties;
+}
+
+
diff --git a/libs/libkis/InfoObject.h b/libs/libkis/InfoObject.h
new file mode 100644
index 0000000000..b06129e3f5
--- /dev/null
+++ b/libs/libkis/InfoObject.h
@@ -0,0 +1,83 @@
+/*
+ * 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);
+ virtual ~InfoObject();
+
+ 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 @param propertyMap to this InfoObject
+ */
+ void setProperties(QMap<QString, QVariant> proprertyMap);
+
+public Q_SLOTS:
+ /**
+ * set the property identified by @key to @value
+ */
+ void setProperty(const QString &key, QVariant value);
+
+ /**
+ * return the value for the property identified by key, or None if there is no suck key.
+ */
+ QVariant property(const QString &key);
+
+private:
+
+ friend class Filter;
+ friend class Document;
+ /**
+ * @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/Krita.cpp b/libs/libkis/Krita.cpp
new file mode 100644
index 0000000000..0275aeb97e
--- /dev/null
+++ b/libs/libkis/Krita.cpp
@@ -0,0 +1,377 @@
+/*
+ * 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 "Krita.h"
+
+#include <QPointer>
+#include <QVariant>
+
+#include <ksharedconfig.h>
+#include <kconfiggroup.h>
+
+#include <KoColorSpaceRegistry.h>
+#include <KoColorProfile.h>
+#include <KoColorSpace.h>
+#include <KoDockRegistry.h>
+#include <KoColorSpaceEngine.h>
+#include <KoColorModelStandardIds.h>
+
+#include <kactioncollection.h>
+#include <KisPart.h>
+#include <KisMainWindow.h>
+#include <KisDocument.h>
+#include <kis_image.h>
+#include <kis_action.h>
+#include <kis_script_manager.h>
+#include <KisViewManager.h>
+#include <KritaVersionWrapper.h>
+#include <kis_filter_registry.h>
+#include <kis_filter.h>
+#include <kis_filter_configuration.h>
+#include <kis_properties_configuration.h>
+#include <kis_config.h>
+#include <kis_resource_server_provider.h>
+#include <kis_workspace_resource.h>
+#include <brushengine/kis_paintop_preset.h>
+#include <kis_brush_server.h>
+#include <KoResourceServerProvider.h>
+
+#include "View.h"
+#include "Document.h"
+#include "Window.h"
+#include "Extension.h"
+#include "DockWidgetFactoryBase.h"
+#include "Filter.h"
+#include "InfoObject.h"
+#include "Resource.h"
+
+Krita* Krita::s_instance = 0;
+
+struct Krita::Private {
+ Private() {}
+ QList<Extension*> extensions;
+ bool batchMode {false};
+ Notifier *notifier{new Notifier()};
+};
+
+Krita::Krita(QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ qRegisterMetaType<Notifier*>();
+}
+
+Krita::~Krita()
+{
+ qDeleteAll(d->extensions);
+ delete d->notifier;
+ delete d;
+}
+
+QList<Action *> Krita::actions() const
+{
+ QList<Action*> actionList;
+ KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
+ if (!mainWindow) {
+ return actionList;
+ }
+ KActionCollection *actionCollection = mainWindow->actionCollection();
+ Q_FOREACH(QAction *action, actionCollection->actions()) {
+ actionList << new Action(action->objectName(), action);
+ }
+ return actionList;
+}
+
+Action *Krita::action(const QString &name) const
+{
+ KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
+ if (!mainWindow) {
+ return 0;
+ }
+ KActionCollection *actionCollection = mainWindow->actionCollection();
+ QAction *action = actionCollection->action(name);
+ if (action) {
+ return new Action(name, action);
+ }
+ return 0;
+}
+
+Document* Krita::activeDocument() const
+{
+ KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
+ if (!mainWindow) {
+ return 0;
+ }
+ KisView *view = mainWindow->activeView();
+ if (!view) {
+ return 0;
+ }
+ KisDocument *document = view->document();
+ return new Document(document);
+}
+
+void Krita::setActiveDocument(Document* value)
+{
+ Q_FOREACH(KisView *view, KisPart::instance()->views()) {
+ if (view->document() == value->document().data()) {
+ view->activateWindow();
+ break;
+ }
+ }
+}
+
+bool Krita::batchmode() const
+{
+ return d->batchMode;
+}
+
+void Krita::setBatchmode(bool value)
+{
+ d->batchMode = value;
+}
+
+
+QList<Document *> Krita::documents() const
+{
+ QList<Document *> ret;
+ foreach(QPointer<KisDocument> doc, KisPart::instance()->documents()) {
+ ret << new Document(doc);
+ }
+ return ret;
+}
+
+QStringList Krita::filters() const
+{
+ QStringList ls = KisFilterRegistry::instance()->keys();
+ qSort(ls);
+ return ls;
+}
+
+Filter *Krita::filter(const QString &name) const
+{
+ if (!filters().contains(name)) return 0;
+
+ Filter *filter = new Filter();
+ filter->setName(name);
+ KisFilterSP f = KisFilterRegistry::instance()->value(name);
+ KisFilterConfigurationSP fc = f->defaultConfiguration();
+ InfoObject *info = new InfoObject(fc);
+ filter->setConfiguration(info);
+ return filter;
+}
+
+QStringList Krita::profiles(const QString &colorModel, const QString &colorDepth) const
+{
+ QSet<QString> profileNames;
+ QString id = KoColorSpaceRegistry::instance()->colorSpaceId(colorModel, colorDepth);
+ QList<const KoColorProfile *> profiles = KoColorSpaceRegistry::instance()->profilesFor(id);
+ Q_FOREACH(const KoColorProfile *profile, profiles) {
+ profileNames << profile->name();
+ }
+ return profileNames.toList();
+}
+
+bool Krita::addProfile(const QString &profilePath)
+{
+ KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
+ return iccEngine->addProfile(profilePath);
+}
+
+Notifier* Krita::notifier() const
+{
+ return d->notifier;
+}
+
+QString Krita::version() const
+{
+ return KritaVersionWrapper::versionString(true);
+}
+
+QList<View *> Krita::views() const
+{
+ QList<View *> ret;
+ foreach(QPointer<KisView> view, KisPart::instance()->views()) {
+ ret << new View(view);
+ }
+ return ret;
+}
+
+Window *Krita::activeWindow() const
+{
+ KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
+ if (!mainWindow) {
+ return 0;
+ }
+ return new Window(mainWindow);
+}
+
+QList<Window*> Krita::windows() const
+{
+ QList<Window*> ret;
+ foreach(QPointer<KisMainWindow> mainWin, KisPart::instance()->mainWindows()) {
+ ret << new Window(mainWin);
+ }
+ return ret;
+}
+
+QMap<QString, Resource *> Krita::resources(const QString &type) const
+{
+ QMap<QString, Resource *> resources = QMap<QString, Resource *> ();
+
+ if (type == "pattern") {
+ KoResourceServer<KoPattern>* server = KoResourceServerProvider::instance()->patternServer();
+ Q_FOREACH (KoResource *res, server->resources()) {
+ resources[res->name()] = new Resource(res);
+ }
+ }
+ else if (type == "gradient") {
+ KoResourceServer<KoAbstractGradient>* server = KoResourceServerProvider::instance()->gradientServer();
+ Q_FOREACH (KoResource *res, server->resources()) {
+ resources[res->name()] = new Resource(res);
+ }
+ }
+ else if (type == "brush") {
+ KisBrushResourceServer* server = KisBrushServer::instance()->brushServer();
+ Q_FOREACH (KisBrushSP res, server->resources()) {
+ resources[res->name()] = new Resource(res.data());
+ }
+ }
+ else if (type == "preset") {
+ KisPaintOpPresetResourceServer* server = KisResourceServerProvider::instance()->paintOpPresetServer();
+ Q_FOREACH (KisPaintOpPresetSP res, server->resources()) {
+ resources[res->name()] = new Resource(res.data());
+ }
+ }
+ else if (type == "palette") {
+ KoResourceServer<KoColorSet>* server = KoResourceServerProvider::instance()->paletteServer();
+ Q_FOREACH (KoResource *res, server->resources()) {
+ resources[res->name()] = new Resource(res);
+ }
+ }
+ else if (type == "workspace") {
+ KoResourceServer< KisWorkspaceResource >* server = KisResourceServerProvider::instance()->workspaceServer();
+ Q_FOREACH (KoResource *res, server->resources()) {
+ resources[res->name()] = new Resource(res);
+ }
+ }
+ return resources;
+}
+
+Document* Krita::createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile)
+{
+ KisDocument *document = KisPart::instance()->createDocument();
+ KisPart::instance()->addDocument(document);
+ const KoColorSpace *cs = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, profile);
+ Q_ASSERT(cs);
+
+ QColor qc(Qt::white);
+ qc.setAlpha(0);
+ KoColor bgColor(qc, cs);
+
+ if (!document->newImage(name, width, height, cs, bgColor, true, 1, "", 100.0)) {
+ qDebug() << "Could not create a new image";
+ return 0;
+ }
+
+ Q_ASSERT(document->image());
+ qDebug() << document->image()->objectName();
+
+ return new Document(document);
+}
+
+Document* Krita::openDocument(const QString &filename)
+{
+ KisDocument *document = KisPart::instance()->createDocument();
+ KisPart::instance()->addDocument(document);
+ document->openUrl(QUrl::fromLocalFile(filename), KisDocument::OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES);
+ return new Document(document);
+}
+
+Window* Krita::openWindow()
+{
+ KisMainWindow *mw = KisPart::instance()->createMainWindow();
+ return new Window(mw);
+}
+
+
+Action *Krita::createAction(const QString &text)
+{
+ KisAction *action = new KisAction(text, this);
+ KisPart::instance()->addScriptAction(action);
+ return new Action(action->objectName(), action);
+}
+
+void Krita::addExtension(Extension* extension)
+{
+ d->extensions.append(extension);
+}
+
+QList< Extension* > Krita::extensions()
+{
+ return d->extensions;
+}
+
+void Krita::writeSetting(const QString &group, const QString &name, const QString &value)
+{
+ KConfigGroup grp = KSharedConfig::openConfig()->group(group);
+ grp.writeEntry(name, value);
+}
+
+QString Krita::readSetting(const QString &group, const QString &name, const QString &defaultValue)
+{
+ KConfigGroup grp = KSharedConfig::openConfig()->group(group);
+ return grp.readEntry(name, defaultValue);
+}
+
+void Krita::addDockWidgetFactory(DockWidgetFactoryBase* factory)
+{
+ KoDockRegistry::instance()->add(factory);
+}
+
+Krita* Krita::instance()
+{
+ if (!s_instance)
+ {
+ s_instance = new Krita;
+ }
+ return s_instance;
+}
+
+/**
+ * Scripter.fromVariant(variant)
+ * variant is a QVariant
+ * returns instance of QObject-subclass
+ *
+ * This is a helper method for PyQt because PyQt cannot cast a variant to a QObject or QWidget
+ */
+QObject *Krita::fromVariant(const QVariant& v)
+{
+
+ if (v.canConvert< QWidget* >())
+ {
+ QObject* obj = qvariant_cast< QWidget* >(v);
+ return obj;
+ }
+ else if (v.canConvert< QObject* >())
+ {
+ QObject* obj = qvariant_cast< QObject* >(v);
+ return obj;
+ }
+ else
+ return 0;
+}
+
diff --git a/libs/libkis/Krita.h b/libs/libkis/Krita.h
new file mode 100644
index 0000000000..42c65d7730
--- /dev/null
+++ b/libs/libkis/Krita.h
@@ -0,0 +1,293 @@
+/*
+ * 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_KRITA_H
+#define LIBKIS_KRITA_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+#include "Extension.h"
+#include "Document.h"
+#include "Window.h"
+#include "View.h"
+#include "Action.h"
+#include "Notifier.h"
+
+class QAction;
+
+/**
+ * Krita is a singleton class that offers the root access to the Krita object hierarchy.
+ *
+ * The Krita.instance() is aliased as two builtins: Scripter and Application.
+ */
+class KRITALIBKIS_EXPORT Krita : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Krita(QObject *parent = 0);
+ virtual ~Krita();
+
+public Q_SLOTS:
+
+
+ /**
+ * @return the currently active document, if there is one.
+ */
+ Document* activeDocument() const;
+
+ /**
+ * @brief setActiveDocument activates the first view that shows the given document
+ * @param value the document we want to activate
+ */
+ void setActiveDocument(Document* value);
+
+ /**
+ * @brief batchmode determines whether the script is run in batch mode. If batchmode
+ * is true, scripts should now show messageboxes or dialog boxes.
+ *
+ * Note that this separate from Document.setBatchmode(), which determines whether
+ * export/save option dialogs are shown.
+ *
+ * @return true if the script is run in batchmode
+ */
+ bool batchmode() const;
+
+ /**
+ * @brief setBatchmode sets the the batchmode to @param value; if true, scripts should
+ * not show dialogs or messageboxes.
+ */
+ void setBatchmode(bool value);
+
+ /**
+ * @return return a list of all actions for the currently active mainWindow.
+ */
+ QList<Action*> actions() const;
+
+ /**
+ * @return the action that has been registered under the given name, or 0 if no such action exists.
+ */
+ Action *action(const QString &name) const;
+
+ /**
+ * @return a list of all open Documents
+ */
+ QList<Document*> documents() const;
+
+ /**
+ * @brief Filters are identified by an internal name. This function returns a list
+ * of all existing registered filters.
+ * @return a list of all registered filters
+ */
+ QStringList filters() const;
+
+ /**
+ * @brief filter construct a Filter object with a default configuration.
+ * @param name the name of the filter. Use Krita.instance().filters() to get
+ * a list of all possible filters.
+ * @return the filter or None if there is no such filter.
+ */
+ Filter *filter(const QString &name) const;
+
+ /**
+ * @brief profiles creates a list with the names of all color profiles compatible
+ * with the given color model and color depth.
+ * @param colorModel A string describing the color model of the image:
+ * <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>
+ * @return a list with valid names
+ */
+ QStringList profiles(const QString &colorModel, const QString &colorDepth) const;
+
+ /**
+ * @brief addProfile load the given profile into the profile registry.
+ * @param profilePath the path to the profile.
+ * @return true if adding the profile succeeded.
+ */
+ bool addProfile(const QString &profilePath);
+
+ /**
+ * @brief notifier the Notifier singleton emits signals when documents are opened and
+ * closed, the configuration changes, views are opened and closed or windows are opened.
+ * @return the notifier object
+ */
+ Notifier* notifier() const;
+
+ /**
+ * @brief version Determine the version of Krita
+ *
+ * Usage: print(Application.version ())
+ *
+ * @return the version string including git sha1 if Krita was built from git
+ */
+ QString version() const;
+
+ /**
+ * @return a list of all views. A Document can be shown in more than one view.
+ */
+ QList<View*> views() const;
+
+ /**
+ * @return the currently active window or None if there is no window
+ */
+ Window *activeWindow() const;
+
+ /**
+ * @return a list of all windows
+ */
+ QList<Window *> windows() const;
+
+ /**
+ * @brief resources returns a list of Resource objects of the given type
+ * @param type Valid types are:
+ *
+ * <ul>
+ * <li>pattern</li>
+ * <li>gradient</li>
+ * <li>brush</li>
+ * <li>preset</li>
+ * <li>palette</li>
+ * <li>workspace</li>
+ * </ul>
+ */
+ QMap<QString, Resource*> resources(const QString &type) const;
+
+ /**
+ * @brief createDocument creates a new document and image and registers the document with the Krita application.
+ *
+ * The document will have one transparent layer.
+ *
+ * @param width the width in pixels
+ * @param height the height in pixels
+ * @param name the name of the image (not the filename of the document)
+ * @param colorModel A string describing the color model of the image:
+ * <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 profile The name of an icc profile that is known to Krita. If an empty string is passed, the default is
+ * taken.
+ * @return the created document.
+ */
+ Document *createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile);
+
+ /**
+ * @brief openDocument creates a new Document, registers it with the Krita application and loads the given file.
+ * @param filename the file to open in the document
+ * @return the document
+ */
+ Document *openDocument(const QString &filename);
+
+ /**
+ * @brief openWindow create a new main window. The window is not shown by default.
+ */
+ Window *openWindow();
+
+ /**
+ * @brief createAction creates an action with the given text and passes it to Krita. Every newly created
+ * mainwindow will create an instance of this action.
+ * @param text the user-visible text
+ * @return the Action you can connect a slot to.
+ */
+ Action *createAction(const QString &text);
+
+ /**
+ * @brief addExtension add the given plugin to Krita. There will be a single instance of each Extension in the Krita process.
+ * @param extension the extension to add.
+ */
+ void addExtension(Extension* extension);
+
+ /**
+ * return a list with all registered extension objects.
+ */
+ QList<Extension*> extensions();
+
+ /**
+ * @brief addDockWidgetFactory Add the given docker factory to the application. For scripts
+ * loaded on startup, this means that every window will have one of the dockers created by the
+ * factory.
+ * @param factory The factory object.
+ */
+ void addDockWidgetFactory(DockWidgetFactoryBase* factory );
+
+ /**
+ * @brief writeSetting write the given setting under the given name to the kritarc file in
+ * the given settings group.
+ * @param group The group the setting belongs to. If empty, then the setting is written in the
+ * general section
+ * @param name The name of the setting
+ * @param value The value of the setting. Script settings are always written as strings.
+ */
+ void writeSetting(const QString &group, const QString &name, const QString &value);
+
+ /**
+ * @brief readSetting read the given setting value from the kritarc file.
+ * @param group The group the setting is part of. If empty, then the setting is read from
+ * the general group.
+ * @param name The name of the setting
+ * @param defaultValue The default value of the setting
+ * @return a string representing the setting.
+ */
+ QString readSetting(const QString &group, const QString &name, const QString &defaultValue);
+
+ /**
+ * @brief instance retrieve the singleton instance of the Application object.
+ */
+ static Krita* instance();
+
+ // Internal only: for use with mikro.py
+ static QObject *fromVariant(const QVariant& v);
+
+private:
+ struct Private;
+ Private *const d;
+ static Krita* s_instance;
+
+};
+
+Q_DECLARE_METATYPE(Notifier*);
+
+#endif // LIBKIS_KRITA_H
diff --git a/libs/libkis/Mainpage.dox b/libs/libkis/Mainpage.dox
new file mode 100644
index 0000000000..2c3db3feef
--- /dev/null
+++ b/libs/libkis/Mainpage.dox
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+/**
+ \mainpage Krita Scripting and Plugin Wrapper Library: libkis
+
+ Libkis is a QObject-based wrapper around Krita's internal libraries.
+ The wrapper can be used from C++ plugins that do not need the
+ advanced and volatile internal libraries of Krita, or can be bound
+ to scripting languages like Python or Javascript.
+
+ All classes are based on QObject, so QMetaObject introspection can
+ be used to discover properties, slots and signals and automatically
+ expose all functionality to the client.
+
+ Note that all objects that are created are wrapper objects that are
+ owned by the scripting environement or the plugin.
+
+
+ Using the functionality in this library, either through a scripting
+ environment like Python or Javascript, or directly from C++, you can,
+ among other things achieve the following functionality:
+
+ <ul>
+ <li>Open, save, export, rename files.
+ <li>Access the layers and masks in a file
+ <li>Read and write pixel data
+ <li>Add menu items, toolbar items and docker palettes
+ </ul>
+
+ The reference implementation of scripting in Krita is implemented
+ in Python 3. All examples in the documentation for scripting will be
+ provided using Python, although the api documentation is generated
+ from C++ header files and shows c++ syntax for arguments.
+
+ Autostarting Scripts
+ ====================
+
+ Autostarting scripts or script-based plugins are scripts that Krita
+ loads on startup. You can add autostarting scripts to Krita by placing
+ them in the pykrita folder in the resources folder: go to settings/
+ manage resources and press the Open Resources Folder to open your
+ local resources folder.
+
+ Scripts are identified by a file that ends in a `.desktop` extension
+ that contain information about the script itself. For example:
+
+@code
+ [Desktop Entry]
+ Type=Service
+ ServiceTypes=Krita/PythonPlugin
+ X-KDE-Library=hello
+ X-Python-2-Compatible=false
+ Name=Hello World
+ Comment=Basic plugin to test PyKrita
+@endcode
+
+ The Python code itself should be placed in the pykrita/hello folder.
+ Your Python plugin needs to be a module, so needs to have a `__init__.py`
+ file:
+
+@code
+ # let's make a module
+ from .hello import *
+@endcode
+
+Krita is a Qt-based application. In principle, you can use any Python
+binding to Qt as long as it's using exactly the same version of Qt
+that Krita uses. In our examples we will be using [PyQt](https://www.riverbankcomputing.com/software/pyqt/intro).
+
+The easiest access to the Krita api is by simply importing the "krita"
+module. This automatically adds two built-ins: Scripter and Application.
+
+This is an alias for Krita.instance(), which is the first place from
+which to access the running Krita instance.
+
+@code
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from krita import *
+
+def hello():
+ QMessageBox.information(QWidget(), "Test", "Hello World")
+
+class HelloExtension(Extension):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ def setup(self, viewManager):
+ action = viewManager.createAction("Hello")
+ action.triggered.connect(hello)
+
+Krita.instance().addExtension(HelloExtension(Krita.instance()))
+@endcode
+
+
+The Krita Object Model
+======================
+
+The starting point is the @see Krita class, which provides a singleton
+object for easy reference. From the Krita class, a hierarchy is provided
+where you can access windows, lviews, documents, nodes and channels.
+
+You can access the Krita class as
+
+ * Krita.instance()
+ * Application
+ * Scripter
+
+For ease of use.
+
+The Document class is provided to allow access to the images Krita has
+loaded. *Note*: internally, Krita distinguishes between images and documents.
+
+A document contains an image and knows the filename of the image, the image
+itself only knows about the layers and masks it contains. The generic
+name for layers and masks is *node*.
+
+A Node can be a group layer, a file layer, a vector layer, a filter layer,
+a generator layer, a clone layer, a paint layer or a transform mask, a selection
+mask, a transparency mask or a colorize mask. You can change some properties
+of Nodes, but not all of them.
+
+The Window class is provided to allow access to the windows and views Krita
+has open. A given Document can be shown in more than one View, and in more than
+one Window. You can open and close windows and views.
+
+ */
+
+// DOXYGEN_SET_PROJECT_NAME = Krita
+// DOXYGEN_SET_IGNORE_PREFIX = Kis Ko K
diff --git a/libs/libkis/Node.cpp b/libs/libkis/Node.cpp
new file mode 100644
index 0000000000..24348dbeb1
--- /dev/null
+++ b/libs/libkis/Node.cpp
@@ -0,0 +1,503 @@
+/*
+ * 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 <QUrl>
+#include <QScopedPointer>
+
+#include <KoColorSpace.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoColorTransformation.h>
+
+#include <KisDocument.h>
+#include <KisMimeDatabase.h>
+#include <KisPart.h>
+#include <kis_change_profile_visitor.h>
+#include <kis_colorspace_convert_visitor.h>
+#include <kis_image.h>
+#include <kis_types.h>
+#include <kis_node.h>
+#include <kis_paint_layer.h>
+#include <kis_group_layer.h>
+#include <kis_file_layer.h>
+#include <kis_adjustment_layer.h>
+#include <kis_generator_layer.h>
+#include <kis_clone_layer.h>
+#include <kis_shape_layer.h>
+#include <kis_transparency_mask.h>
+#include <kis_filter_mask.h>
+#include <kis_transform_mask.h>
+#include <kis_selection_mask.h>
+#include <lazybrush/kis_colorize_mask.h>
+#include <kis_layer.h>
+#include <kis_meta_data_merge_strategy.h>
+#include <metadata/kis_meta_data_merge_strategy_registry.h>
+
+#include "Krita.h"
+#include "Node.h"
+#include "Channel.h"
+#include "Filter.h"
+#include "Selection.h"
+
+
+struct Node::Private {
+ Private() {}
+ KisImageSP image;
+ KisNodeSP node;
+};
+
+Node::Node(KisImageSP image, KisNodeSP node, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->image = image;
+ d->node = node;
+}
+
+Node::~Node()
+{
+ delete d;
+}
+
+bool Node::operator==(const Node &other) const
+{
+ return (d->node == other.d->node
+ && d->image == other.d->image);
+}
+
+bool Node::operator!=(const Node &other) const
+{
+ return !(operator==(other));
+}
+
+
+bool Node::alphaLocked() const
+{
+ if (!d->node) return false;
+ KisPaintLayerSP paintLayer = qobject_cast<KisPaintLayer*>(d->node.data());
+ if (paintLayer) {
+ return paintLayer->alphaLocked();
+ }
+ return false;
+}
+
+void Node::setAlphaLocked(bool value)
+{
+ if (!d->node) return;
+ KisPaintLayerSP paintLayer = qobject_cast<KisPaintLayer*>(d->node.data());
+ if (paintLayer) {
+ paintLayer->setAlphaLocked(value);
+ }
+}
+
+
+QString Node::blendingMode() const
+{
+ if (!d->node) return QString();
+
+ return d->node->compositeOpId();
+}
+
+void Node::setBlendingMode(QString value)
+{
+ if (!d->node) return;
+ d->node->setCompositeOpId(value);
+}
+
+
+QList<Channel*> Node::channels() const
+{
+ QList<Channel*> channels;
+
+ if (!d->node) return channels;
+ if (!d->node->inherits("KisLayer")) return channels;
+
+ Q_FOREACH(KoChannelInfo *info, d->node->colorSpace()->channels()) {
+ Channel *channel = new Channel(d->node, info);
+ channels << channel;
+ }
+
+ return channels;
+}
+
+QList<Node*> Node::childNodes() const
+{
+ QList<Node*> nodes;
+ if (d->node) {
+ int childCount = d->node->childCount();
+ for (int i = 0; i < childCount; ++i) {
+ nodes << new Node(d->image, d->node->at(i));
+ }
+ }
+ return nodes;
+}
+
+bool Node::addChildNode(Node *child, Node *above)
+{
+ if (!d->node) return false;
+ return d->image->addNode(child->node(), d->node, above->node());
+}
+
+bool Node::removeChildNode(Node *child)
+{
+ if (!d->node) return false;
+ return d->image->removeNode(child->node());
+}
+
+void Node::setChildNodes(QList<Node*> nodes)
+{
+ if (!d->node) return;
+ KisNodeSP node = d->node->firstChild();
+ while (node) {
+ d->image->removeNode(node);
+ node = node->nextSibling();
+ }
+ Q_FOREACH(Node *node, nodes) {
+ d->image->addNode(node->node(), d->node);
+ }
+}
+
+int Node::colorLabel() const
+{
+ if (!d->node) return 0;
+ return d->node->colorLabelIndex();
+}
+
+void Node::setColorLabel(int index)
+{
+ if (!d->node) return;
+ d->node->setColorLabelIndex(index);
+}
+
+QString Node::colorDepth() const
+{
+ if (!d->node) return "";
+ return d->node->colorSpace()->colorDepthId().id();
+}
+
+QString Node::colorModel() const
+{
+ if (!d->node) return "";
+ return d->node->colorSpace()->colorModelId().id();
+}
+
+
+QString Node::colorProfile() const
+{
+ if (!d->node) return "";
+ return d->node->colorSpace()->profile()->name();
+}
+
+bool Node::setColorProfile(const QString &colorProfile)
+{
+ if (!d->node) return false;
+ if (!d->node->inherits("KisLayer")) return false;
+ KisLayer *layer = qobject_cast<KisLayer*>(d->node.data());
+ const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile);
+ const KoColorSpace *srcCS = layer->colorSpace();
+ const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(srcCS->colorModelId().id(),
+ srcCS->colorDepthId().id(),
+ profile);
+ KisChangeProfileVisitor v(srcCS, dstCs);
+ return layer->accept(v);
+}
+
+bool Node::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile)
+{
+ if (!d->node) return false;
+ if (!d->node->inherits("KisLayer")) return false;
+ KisLayer *layer = qobject_cast<KisLayer*>(d->node.data());
+ const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(colorProfile);
+ const KoColorSpace *srcCS = layer->colorSpace();
+ const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorModel,
+ colorDepth,
+ profile);
+ KisColorSpaceConvertVisitor v(d->image, srcCS, dstCs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
+ return layer->accept(v);
+}
+
+bool Node::animated() const
+{
+ if (!d->node) return false;
+ return d->node->isAnimated();
+}
+
+void Node::enableAnimation() const
+{
+ if (!d->node) return;
+ d->node->enableAnimation();
+}
+
+bool Node::collapsed() const
+{
+ if (!d->node) return false;
+ return d->node->collapsed();
+}
+
+void Node::setCollapsed(bool collapsed)
+{
+ if (!d->node) return;
+ d->node->setCollapsed(collapsed);
+}
+
+bool Node::inheritAlpha() const
+{
+ if (!d->node) return false;
+ if (!d->node->inherits("KisLayer")) return false;
+ return qobject_cast<const KisLayer*>(d->node)->alphaChannelDisabled();
+}
+
+void Node::setInheritAlpha(bool value)
+{
+ if (!d->node) return;
+ if (!d->node->inherits("KisLayer")) return;
+ const_cast<KisLayer*>(qobject_cast<const KisLayer*>(d->node))->disableAlphaChannel(value);
+}
+
+bool Node::locked() const
+{
+ if (!d->node) return false;
+ return d->node->userLocked() || d->node->systemLocked();
+}
+
+void Node::setLocked(bool value)
+{
+ if (!d->node) return;
+ d->node->setUserLocked(value);
+}
+
+
+QString Node::name() const
+{
+ if (!d->node) return QString();
+ return d->node->name();
+}
+
+void Node::setName(QString name)
+{
+ if (!d->node) return;
+ d->node->setName(name);
+}
+
+
+int Node::opacity() const
+{
+ if (!d->node) return 0;
+ return d->node->opacity();
+}
+
+void Node::setOpacity(int value)
+{
+ if (!d->node) return;
+ if (value < 0) value = 0;
+ if (value > 255) value = 255;
+ d->node->setOpacity(value);
+}
+
+
+Node* Node::parentNode() const
+{
+ if (!d->node) return 0;
+ return new Node(d->image, d->node->parent());
+}
+
+QString Node::type() const
+{
+ if (!d->node) return QString();
+ return QString();
+ if (qobject_cast<const KisPaintLayer*>(d->node)) {
+ return "paintlayer";
+ }
+ else if (qobject_cast<const KisGroupLayer*>(d->node)) {
+ return "grouplayer";
+ }
+ if (qobject_cast<const KisFileLayer*>(d->node)) {
+ return "filelayer";
+ }
+ if (qobject_cast<const KisAdjustmentLayer*>(d->node)) {
+ return "filterlayer";
+ }
+ if (qobject_cast<const KisGeneratorLayer*>(d->node)) {
+ return "filllayer";
+ }
+ if (qobject_cast<const KisCloneLayer*>(d->node)) {
+ return "clonelayer";
+ }
+ if (qobject_cast<const KisShapeLayer*>(d->node)) {
+ return "shapelayer";
+ }
+ if (qobject_cast<const KisTransparencyMask*>(d->node)) {
+ return "transparencymask";
+ }
+ if (qobject_cast<const KisFilterMask*>(d->node)) {
+ return "filtermask";
+ }
+ if (qobject_cast<const KisTransformMask*>(d->node)) {
+ return "transformmask";
+ }
+ if (qobject_cast<const KisSelectionMask*>(d->node)) {
+ return "selectionmask";
+ }
+ if (qobject_cast<const KisColorizeMask*>(d->node)) {
+ return "colorizemask";
+ }
+}
+
+bool Node::visible() const
+{
+ if (!d->node) return false;
+ return d->node->visible();;
+}
+
+void Node::setVisible(bool visible)
+{
+ if (!d->node) return;
+ d->node->setVisible(visible);
+}
+
+
+QByteArray Node::pixelData(int x, int y, int w, int h) const
+{
+ QByteArray ba;
+
+ if (!d->node) return ba;
+
+ KisPaintDeviceSP dev = d->node->paintDevice();
+ if (!dev) return ba;
+
+ ba.resize(w * h * dev->pixelSize());
+ dev->readBytes(reinterpret_cast<quint8*>(ba.data()), x, y, w, h);
+ return ba;
+}
+
+
+QByteArray Node::projectionPixelData(int x, int y, int w, int h) const
+{
+ QByteArray ba;
+
+ if (!d->node) return ba;
+
+ KisPaintDeviceSP dev = d->node->projection();
+ ba.resize(w * h * dev->pixelSize());
+ dev->readBytes(reinterpret_cast<quint8*>(ba.data()), x, y, w, h);
+ return ba;
+}
+
+void Node::setPixelData(QByteArray value, int x, int y, int w, int h)
+{
+ if (!d->node) return;
+ KisPaintDeviceSP dev = d->node->paintDevice();
+ if (!dev) return;
+ dev->writeBytes((const quint8*)value.constData(), x, y, w, h);
+}
+
+QRect Node::bounds() const
+{
+ if (!d->node) return QRect();
+ return d->node->exactBounds();
+}
+
+void Node::move(int x, int y)
+{
+ if (!d->node) return;
+ d->node->setX(x);
+ d->node->setY(y);
+}
+
+QPoint Node::position() const
+{
+ if (!d->node) return QPoint();
+ return QPoint(d->node->x(), d->node->y());
+}
+
+bool Node::remove()
+{
+ if (!d->node) return false;
+ if (!d->node->parent()) return false;
+ return d->image->removeNode(d->node);
+}
+
+Node* Node::duplicate()
+{
+ if (!d->node) return 0;
+ return new Node(d->image, d->node->clone());
+}
+
+bool Node::save(const QString &filename, double xRes, double yRes)
+{
+ if (!d->node) return false;
+ if (filename.isEmpty()) return false;
+
+ KisPaintDeviceSP projection = d->node->projection();
+ QRect bounds = d->node->exactBounds();
+
+ QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);;
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+
+ KisImageSP dst = new KisImage(doc->createUndoStore(),
+ bounds.width(),
+ bounds.height(),
+ projection->compositionSourceColorSpace(),
+ d->node->name());
+ dst->setResolution(xRes, yRes);
+ doc->setFileBatchMode(Krita::instance()->batchmode());
+ doc->setCurrentImage(dst);
+ KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", d->node->opacity());
+ paintLayer->paintDevice()->makeCloneFrom(projection, bounds);
+ dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
+ dst->initialRefreshGraph();
+ doc->setOutputMimeType(mimefilter.toLatin1());
+
+ bool r = doc->exportDocument(QUrl::fromLocalFile(filename));
+ if (!r) {
+ qWarning() << doc->errorMessage();
+ }
+ return r;
+}
+
+Node *Node::mergeDown()
+{
+ if (!d->node) return 0;
+ if (!qobject_cast<KisLayer*>(d->node.data())) return 0;
+ if (!d->node->nextSibling()) return 0;
+ if (!d->node->parent()) return 0;
+
+ int index = d->node->parent()->index(d->node->prevSibling());
+ d->image->mergeDown(qobject_cast<KisLayer*>(d->node.data()), KisMetaData::MergeStrategyRegistry::instance()->get("Drop"));
+ d->image->waitForDone();
+ return new Node(d->image, d->node->parent()->at(index));
+}
+
+QImage Node::thumbnail(int w, int h)
+{
+ if (!d->node) return QImage();
+ return d->node->createThumbnail(w, h);
+}
+
+KisPaintDeviceSP Node::paintDevice() const
+{
+ return d->node->paintDevice();
+}
+
+KisImageSP Node::image() const
+{
+ return d->image;
+}
+
+KisNodeSP Node::node() const
+{
+ return d->node;
+}
diff --git a/libs/libkis/Node.h b/libs/libkis/Node.h
new file mode 100644
index 0000000000..9fb39a78cb
--- /dev/null
+++ b/libs/libkis/Node.h
@@ -0,0 +1,471 @@
+/*
+ * 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_NODE_H
+#define LIBKIS_NODE_H
+
+#include <QObject>
+
+#include <kis_types.h>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+/**
+ * Node represents a layer or mask in a Krita image's Node hierarchy. Group layers can contain
+ * other layers and masks; layers can contain masks.
+ *
+ */
+class KRITALIBKIS_EXPORT Node : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Node)
+
+public:
+ explicit Node(KisImageSP image, KisNodeSP node, QObject *parent = 0);
+ virtual ~Node();
+ bool operator==(const Node &other) const;
+ bool operator!=(const Node &other) const;
+public Q_SLOTS:
+ /**
+ * @brief alphaLocked checks whether the node is a paint layer and returns whether it is alpha locked
+ * @return whether the paint layer is alpha locked, or false if the node is not a paint layer
+ */
+ bool alphaLocked() const;
+
+ /**
+ * @brief setAlphaLocked set the layer to value if the the node is paint layer.
+ */
+ void setAlphaLocked(bool value);
+
+ /**
+ * @return the blending mode of the layer. The values of the blending modes are defined in @see KoCompositeOpRegistry.h
+ */
+ QString blendingMode() const;
+
+ /**
+ * @brief setBlendingMode set the blending mode of the node to the given value
+ * @param value one of the string values from @see KoCompositeOpRegistry.h
+ */
+ void setBlendingMode(QString value);
+
+ /**
+ * @brief channels creates a list of Channel objects that can be used individually to
+ * show or hide certain channels, and to retrieve the contents of each channel in a
+ * node separately.
+ *
+ * Only layers have channels, masks do not, and calling channels on a Node that is a mask
+ * will return an empty list.
+ *
+ * @return the list of channels ordered in by position of the channels in pixel position
+ */
+ QList<Channel*> channels() const;
+
+ /**
+ * Return a list of child nodes of the current node. The nodes are ordered from the bottommost up.
+ * The function is not recursive.
+ */
+ QList<Node*> childNodes() const;
+
+ /**
+ * @brief addChildNode adds the given node in the list of children.
+ * @param child the node to be added
+ * @param above the node above which this node will be placed
+ * @return false if adding the node failed
+ */
+ bool addChildNode(Node *child, Node *above);
+
+ /**
+ * @brief removeChildNode removes the given node from the list of children.
+ * @param child the node to be removed
+ */
+ bool removeChildNode(Node *child);
+
+ /**
+ * @brief setChildNodes this replaces the existing set of child nodes with the new set.
+ * @param nodes The list of nodes that will become children, bottom-up -- the first node,
+ * is the bottom-most node in the stack.
+ */
+ void setChildNodes(QList<Node*> nodes);
+
+ /**
+ * 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 if assigining the colorprofiel worked
+ */
+ bool setColorProfile(const QString &colorProfile);
+
+ /**
+ * @brief setColorSpace convert the node to the given colorspace
+ * @param colorModel A string describing the color model of the node:
+ * <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.
+ */
+ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile);
+
+ /**
+ * @brief Krita layers can be animated, i.e., have frames.
+ * @return return true if the layer has frames. Currently, the scripting framework
+ * does not give access to the animation features.
+ */
+ bool animated() const;
+
+ /**
+ * @brief enableAnimation make the current layer animated, so it can have frames.
+ */
+ void enableAnimation() const;
+
+ /**
+ * Sets the state of the node to the value of @param collapsed
+ */
+ void setCollapsed(bool collapsed);
+
+ /**
+ * returns the collapsed state of this node
+ */
+ bool collapsed() const;
+
+ /**
+ * Sets a color label index associated to the layer. The actual
+ * color of the label and the number of available colors is
+ * defined by Krita GUI configuration.
+ */
+ int colorLabel() const;
+
+ /**
+ * @brief setColorLabel sets a color label index associated to the layer. The actual
+ * color of the label and the number of available colors is
+ * defined by Krita GUI configuration.
+ * @param index an integer corresponding to the set of available color labels.
+ */
+ void setColorLabel(int index);
+
+ /**
+ * @brief inheritAlpha checks whether this node has the inherits alpha flag set
+ * @return true if the Inherit Alpha is set
+ */
+ bool inheritAlpha() const;
+
+ /**
+ * set the Inherit Alpha flag to the given value
+ */
+ void setInheritAlpha(bool value);
+
+ /**
+ * @brief locked checkes whether the Node is locked. A locked node cannot be changed.
+ * @return true if the Node is locked, false if it hasn't been locked.
+ */
+ bool locked() const;
+
+ /**
+ * set the Locked flag to the give value
+ */
+ void setLocked(bool value);
+
+ /**
+ * @return the user-visible name of this node.
+ */
+ QString name() const;
+
+ /**
+ * rename the Node to the given name
+ */
+ void setName(QString name);
+
+ /**
+ * return the opacity of the Node. The opacity is a value between 0 and 255.
+ */
+ int opacity() const;
+
+ /**
+ * set the opacity of the Node to the given value. The opacity is a value between 0 and 255.
+ */
+ void setOpacity(int value);
+
+ /**
+ * return the Node that is the parent of the current Node, or 0 if this is the root Node.
+ */
+ Node* parentNode() const;
+
+ /**
+ * @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.
+ */
+ QString type() const;
+
+ /**
+ * Check whether the current Node is visible in the layer stack
+ */
+ bool visible() const;
+
+ /**
+ * Set the visibility of the current node to @param visible
+ */
+ void setVisible(bool visible);
+
+ /**
+ * @brief pixelData reads the given rectangle from the Node's paintable pixels, if those
+ * exist, and returns it as a byte array. The pixel data starts top-left, and is ordered row-first.
+ *
+ * The byte array can be interpreted as follows: 8 bits images have one byte per channel,
+ * and as many bytes as there are channels. 16 bits integer images have two bytes per channel,
+ * representing an unsigned short. 16 bits float images have two bytes per channel, representing
+ * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a
+ * float.
+ *
+ * You can read outside the node boundaries; those pixels will be transparent black.
+ *
+ * The order of channels is:
+ *
+ * <ul>
+ * <li>Integer RGBA: Blue, Green, Red, Alpha
+ * <li>Float RGBA: Red, Green, Blue, Alpha
+ * <li>GrayA: Gray, Alpha
+ * <li>Selection: selectedness
+ * <li>LabA: L, a, b, Alpha
+ * <li>CMYKA: Cyan, Magenta, Yellow, Key, Alpha
+ * <li>XYZA: X, Y, Z, A
+ * <li>YCbCrA: Y, Cb, Cr, Alpha
+ * </ul>
+ *
+ * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray
+ * and the struct module to interpret the data and construct, for instance, a Pillow Image object.
+ *
+ * If you read the pixeldata of a mask, a filter or generator layer, you get the selection bytes,
+ * which is one channel with values in the range from 0..255.
+ *
+ * If you want to change the pixels of a node you can write the pixels back after manipulation
+ * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups
+ * or file layers.
+ *
+ * @param x x position from where to start reading
+ * @param y y position from where to start reading
+ * @param w row length to read
+ * @param h number of rows to read
+ * @return a QByteArray with the pixel data. The byte array may be empty.
+
+ */
+ QByteArray pixelData(int x, int y, int w, int h) const;
+
+ /**
+ * @brief projectionPixelData reads the given rectangle from the Node's projection (that is, what the node
+ * looks like after all sub-Nodes (like layers in a group or masks on a layer) have been applied,
+ * and returns it as a byte array. The pixel data starts top-left, and is ordered row-first.
+ *
+ * The byte array can be interpreted as follows: 8 bits images have one byte per channel,
+ * and as many bytes as there are channels. 16 bits integer images have two bytes per channel,
+ * representing an unsigned short. 16 bits float images have two bytes per channel, representing
+ * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a
+ * float.
+ *
+ * You can read outside the node boundaries; those pixels will be transparent black.
+ *
+ * The order of channels is:
+ *
+ * <ul>
+ * <li>Integer RGBA: Blue, Green, Red, Alpha
+ * <li>Float RGBA: Red, Green, Blue, Alpha
+ * <li>GrayA: Gray, Alpha
+ * <li>Selection: selectedness
+ * <li>LabA: L, a, b, Alpha
+ * <li>CMYKA: Cyan, Magenta, Yellow, Key, Alpha
+ * <li>XYZA: X, Y, Z, A
+ * <li>YCbCrA: Y, Cb, Cr, Alpha
+ * </ul>
+ *
+ * The byte array is a copy of the original node data. In Python, you can use bytes, bytearray
+ * and the struct module to interpret the data and construct, for instance, a Pillow Image object.
+ *
+ * If you read the projection of a mask, you get the selection bytes, which is one channel with
+ * values in the range from 0..255.
+ *
+ * If you want to change the pixels of a node you can write the pixels back after manipulation
+ * with setPixelData(). This will only succeed on nodes with writable pixel data, e.g not on groups
+ * or file layers.
+ *
+ * @param x x position from where to start reading
+ * @param y y position from where to start reading
+ * @param w row length to read
+ * @param h number of rows to read
+ * @return a QByteArray with the pixel data. The byte array may be empty.
+ */
+ QByteArray projectionPixelData(int x, int y, int w, int h) const;
+
+ /**
+ * @brief setPixelData writes the given bytes, of which there must be enough, into the
+ * Node, if the Node has writable pixel data:
+ *
+ * <ul>
+ * <li>paint layer: the layer's original pixels are overwritten
+ * <li>filter layer, generator layer, any mask: the embedded selection's pixels are overwritten.
+ * <b>Note:</b> for these
+ * </ul>
+ *
+ * File layers, Group layers, Clone layers cannot be written to. Calling setPixelData on
+ * those layer types will silently do nothing.
+ *
+ * @param value the byte array representing the pixels. There must be enough bytes available.
+ * Krita will take the raw pointer from the QByteArray and start reading, not stopping before
+ * (number of channels * size of channel * w * h) bytes are read.
+ *
+ * @param x the x position to start writing from
+ * @param y the y position to start writing from
+ * @param w the width of each row
+ * @param h the number of rows to write
+ */
+ void setPixelData(QByteArray value, int x, int y, int w, int h);
+
+ /**
+ * @brief bounds return the exact bounds of the node's paint device
+ * @return the bounds, or an empty QRect if the node has no paint device or is empty.
+ */
+ QRect bounds() const;
+
+ /**
+ * move the pixels to the given x, y location in the image coordinate space.
+ */
+ void move(int x, int y);
+
+ /**
+ * @brief position returns the position of the paint device of this node
+ * @return the top-left position of the node
+ */
+ QPoint position() const;
+
+ /**
+ * @brief remove removes this node from its parent image.
+ */
+ bool remove();
+
+ /**
+ * @brief duplicate returns a full copy of the current node. The node is not inserted in the graphc
+ * @return a valid Node object or 0 if the node couldn't be duplicated.
+ */
+ Node* duplicate();
+
+ /**
+ * @brief save exports the given node with this filename. The extension of the filename determins the filetype.
+ * @param filename the filename including extension
+ * @param xRes the horizontal resolution in pixels per pt (there are 72 pts in an inch)
+ * @param yRes the horizontal resolution in pixels per pt (there are 72 pts in an inch)
+ * @return true if saving succeeded, false if it failed.
+ */
+ bool save(const QString &filename, double xRes, double yRes);
+
+ /**
+ * @brief mergeDown merges the given node with the first visible node underneath this node in the layerstack.
+ * This will drop all per-layer metadata.
+ * @param node the node to merge down; this node will be removed from the layer stack
+ */
+ Node *mergeDown();
+
+ /**
+ * @brief thumbnail create a thumbnail of the given dimensions. The thumbnail is sized according
+ * to the layer dimensions, not the image dimensions. If the requested size is too big a null
+ * QImage is created. If the current node cannot generate a thumbnail, a transparent QImage of the
+ * requested size is generated.
+ * @return a QImage representing the layer contents.
+ */
+ QImage thumbnail(int w, int h);
+
+private:
+
+ friend class Filter;
+ friend class Document;
+ friend class Selection;
+ /**
+ * @brief paintDevice gives access to the internal paint device of this Node
+ * @return the paintdevice or 0 if the node does not have an editable paint device.
+ */
+ KisPaintDeviceSP paintDevice() const;
+ KisImageSP image() const;
+ KisNodeSP node() const;
+
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_NODE_H
diff --git a/libs/libkis/Notifier.cpp b/libs/libkis/Notifier.cpp
new file mode 100644
index 0000000000..5e9375f633
--- /dev/null
+++ b/libs/libkis/Notifier.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "Notifier.h"
+#include <KisApplication.h>
+#include <KisPart.h>
+#include <kis_config_notifier.h>
+#include "View.h"
+#include "Window.h"
+#include "Document.h"
+
+struct Notifier::Private {
+ Private() {}
+ bool active {false};
+};
+
+Notifier::Notifier(QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ connect(qApp, SIGNAL(aboutToQuit()), SIGNAL(applicationClosing()));
+
+ connect(KisPart::instance(), SIGNAL(sigDocumentAdded(KisDocument*)), SLOT(imageCreated(KisDocument*)));
+ connect(KisPart::instance(), SIGNAL(sigDocumentSaved(QString)), SIGNAL(imageSaved(QString)));
+ connect(KisPart::instance(), SIGNAL(sigDocumentRemoved(QString)), SIGNAL(imageClosed(QString)));
+
+ connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(viewCreated(KisView*)));
+ connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(viewClosed(KisView*)));
+
+ connect(KisPart::instance(), SIGNAL(sigWindowAdded(KisMainWindow*)), SLOT(windowCreated(KisMainWindow*)));
+
+ connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SIGNAL(configurationChanged()));
+
+}
+
+
+Notifier::~Notifier()
+{
+ delete d;
+}
+
+bool Notifier::active() const
+{
+ return d->active;
+}
+
+void Notifier::setActive(bool value)
+{
+ d->active = value;
+ blockSignals(value);
+}
+
+void Notifier::imageCreated(KisDocument* document)
+{
+ Document *doc = new Document(document);
+ emit imageCreated(doc);
+}
+
+void Notifier::viewCreated(KisView *view)
+{
+ View *v = new View(view);
+ emit viewCreated(v);
+}
+
+void Notifier::viewClosed(KisView *view)
+{
+ View *v = new View(view);
+ emit viewClosed(v);
+}
+
+void Notifier::windowCreated(KisMainWindow *window)
+{
+ Window *w = new Window(window);
+ emit windowCreated(w);
+}
+
diff --git a/libs/libkis/Notifier.h b/libs/libkis/Notifier.h
new file mode 100644
index 0000000000..dfcf5f61db
--- /dev/null
+++ b/libs/libkis/Notifier.h
@@ -0,0 +1,121 @@
+/*
+ * 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_NOTIFIER_H
+#define LIBKIS_NOTIFIER_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include <kis_types.h>
+#include "libkis.h"
+#include <KisDocument.h>
+#include <KisView.h>
+#include <KisMainWindow.h>
+
+/**
+ * The Notifier can be used to be informed of state changes in the Krita application.
+ */
+class KRITALIBKIS_EXPORT Notifier : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(Notifier)
+
+ Q_PROPERTY(bool Active READ active WRITE setActive)
+
+public:
+ explicit Notifier(QObject *parent = 0);
+ virtual ~Notifier();
+
+ /**
+ * @return true if the Notifier is active.
+ */
+ bool active() const;
+
+ /**
+ * Enable or disable the Notifier
+ */
+ void setActive(bool value);
+
+Q_SIGNALS:
+
+ /**
+ * @brief applicationClosing is emitted when the application is about to close. This
+ * happens after any documents and windows are closed.
+ */
+ void applicationClosing();
+
+ /**
+ * @brief imageCreated is emitted whenever a new image is created and registered with
+ * the application.
+ */
+ void imageCreated(Document *image);
+
+ /**
+ * @brief imageSaved is emitted whenever a document is saved.
+ * @param filename the filename of the document that has been saved.
+ */
+ void imageSaved(const QString &filename);
+
+ /**
+ * @brief imageClosed is emitted whenever the last view on an image is closed. The image
+ * does not exist anymore in Krita
+ * @param filename the filename of the image.
+ */
+ void imageClosed(const QString &filename);
+
+ /**
+ * @brief viewCreated is emitted whenever a new view is created.
+ * @param view the view
+ */
+ void viewCreated(View *view);
+
+ /**
+ * @brief viewClosed is emitted whenever a view is closed
+ * @param view the view
+ */
+ void viewClosed(View *view);
+
+ /**
+ * @brief windowCreated is emitted whenever a window is created
+ * @param window the window
+ */
+ void windowCreated(Window *window);
+
+ /**
+ * @brief configurationChanged is emitted every time Krita's configuration
+ * has changed.
+ */
+ void configurationChanged();
+
+private Q_SLOTS:
+
+ void imageCreated(KisDocument *document);
+
+ void viewCreated(KisView *view);
+ void viewClosed(KisView *view);
+
+ void windowCreated(KisMainWindow *window);
+
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_NOTIFIER_H
diff --git a/libs/libkis/PresetChooser.cpp b/libs/libkis/PresetChooser.cpp
new file mode 100644
index 0000000000..d865215b96
--- /dev/null
+++ b/libs/libkis/PresetChooser.cpp
@@ -0,0 +1,55 @@
+/*
+ * 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 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 "PresetChooser.h"
+
+#include <KoResource.h>
+#include <kis_config.h>
+#include "Resource.h"
+
+PresetChooser::PresetChooser(QWidget *parent)
+ : KisPresetChooser(parent)
+{
+ connect(this, SIGNAL(resourceSelected(KoResource*)), SLOT(slotResourceSelected(KoResource*)));
+ connect(this, SIGNAL(resourceClicked(KoResource*)), SLOT(slotResourceClicked(KoResource*)));
+ showTaggingBar(true);
+}
+
+
+void PresetChooser::setCurrentPreset(Resource *resource)
+{
+ KoResource *r = resource->resource();
+ setCurrentResource(r);
+}
+
+Resource *PresetChooser::currentPreset() const
+{
+ KoResource *r = currentResource();
+ return new Resource(r);
+}
+
+void PresetChooser::slotResourceSelected(KoResource *resource)
+{
+ Resource *r = new Resource(resource);
+ emit presetSelected(r);
+}
+
+void PresetChooser::slotResourceClicked(KoResource *resource)
+{
+ Resource *r = new Resource(resource);
+ emit presetClicked(r);
+}
diff --git a/libs/libkis/PresetChooser.h b/libs/libkis/PresetChooser.h
new file mode 100644
index 0000000000..396a5dad6a
--- /dev/null
+++ b/libs/libkis/PresetChooser.h
@@ -0,0 +1,75 @@
+/*
+ * 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 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 PRESETCHOOSER_H
+#define PRESETCHOOSER_H
+
+#include <QObject>
+#include <QWidget>
+
+#include <kis_preset_chooser.h>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+class Resource;
+
+/**
+ * @brief The PresetChooser widget wraps the KisPresetChooser widget.
+ * The widget provides for selecting brush presets. It has a tagging
+ * bar and a filter field. It is not automatically synchronized with
+ * the currently selected preset in the current Windows.
+ */
+class KRITALIBKIS_EXPORT PresetChooser : public KisPresetChooser
+{
+ Q_OBJECT
+public:
+ PresetChooser(QWidget *parent = 0);
+ virtual ~PresetChooser() {}
+
+public Q_SLOTS:
+
+ /**
+ * Make the given preset active.
+ */
+ void setCurrentPreset(Resource *resource);
+
+ /**
+ * @return a Resource wrapper around the currently selected
+ * preset.
+ */
+ Resource *currentPreset() const;
+
+Q_SIGNALS:
+
+ /**
+ * Emited whenever a user selects the given preset.
+ */
+ void presetSelected(Resource *resource);
+
+ /**
+ * Emited whenever a user clicks on the given preset.
+ */
+ void presetClicked(Resource *resource);
+
+private Q_SLOTS:
+
+ void slotResourceSelected(KoResource *resource);
+ void slotResourceClicked(KoResource *resource);
+};
+
+#endif // PRESETCHOOSER_H
diff --git a/libs/libkis/Resource.cpp b/libs/libkis/Resource.cpp
new file mode 100644
index 0000000000..d773f367c7
--- /dev/null
+++ b/libs/libkis/Resource.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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 "Resource.h"
+#include <KoResource.h>
+#include <QByteArray>
+#include <QBuffer>
+
+#include <KoPattern.h>
+#include <KoAbstractGradient.h>
+#include <kis_brush.h>
+#include <kis_paintop_preset.h>
+#include <KoColorSet.h>
+#include <kis_workspace_resource.h>
+
+struct Resource::Private {
+ Private(KoResource *_resource)
+ : resource(_resource)
+ {}
+
+ KoResource *resource {0};
+};
+
+Resource::Resource(KoResource *resource, QObject *parent)
+ : QObject(parent)
+ , d(new Private(resource))
+{
+}
+
+Resource::~Resource()
+{
+ delete d;
+}
+
+bool Resource::operator==(const Resource &other) const
+{
+ return (d->resource == other.d->resource);
+}
+
+bool Resource::operator!=(const Resource &other) const
+{
+ return !(operator==(other));
+}
+
+QString Resource::type() const
+{
+ if (!d->resource) return QString();
+ if (dynamic_cast<KoPattern*>(d->resource)) return "pattern";
+ else if (dynamic_cast<KoAbstractGradient*>(d->resource)) return "gradient";
+ else if (dynamic_cast<KisBrush*>(d->resource)) return "brush";
+ else if (dynamic_cast<KisPaintOpPreset*>(d->resource)) return "preset";
+ else if (dynamic_cast<KoColorSet*>(d->resource)) return "palette";
+ else if (dynamic_cast<KisWorkspaceResource*>(d->resource)) return "workspace";
+ else return "";
+}
+
+QString Resource::name() const
+{
+ if (!d->resource) return QString();
+ return d->resource->name();
+}
+
+void Resource::setName(QString value)
+{
+ if (!d->resource) return;
+ d->resource->setName(value);
+}
+
+
+QString Resource::filename() const
+{
+ if (!d->resource) return QString();
+ return d->resource->filename();
+}
+
+
+QImage Resource::image() const
+{
+ if (!d->resource) return QImage();
+ return d->resource->image();
+}
+
+void Resource::setImage(QImage image)
+{
+ if (!d->resource) return;
+ d->resource->setImage(image);
+}
+
+QByteArray Resource::data() const
+{
+ QByteArray ba;
+
+ if (!d->resource) return ba;
+
+ QBuffer buf(&ba);
+ d->resource->saveToDevice(&buf);
+ return ba;
+}
+
+bool Resource::setData(QByteArray data)
+{
+ if (!d->resource) return false;
+ QBuffer buf(&data);
+ return d->resource->loadFromDevice(&buf);
+}
+
+KoResource *Resource::resource() const
+{
+ return d->resource;
+}
+
+
+
+
+
+
diff --git a/libs/libkis/Resource.h b/libs/libkis/Resource.h
new file mode 100644
index 0000000000..dabdb87788
--- /dev/null
+++ b/libs/libkis/Resource.h
@@ -0,0 +1,120 @@
+/*
+ * 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_RESOURCE_H
+#define LIBKIS_RESOURCE_H
+
+#include <QObject>
+#include <kis_types.h>
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+class KoResource;
+
+/**
+ * A Resource represents a gradient, pattern, brush tip, brush preset, palette or
+ * workspace definition.
+ *
+ * @code
+ * allPresets = Application.resources("preset")
+ * for preset in allPresets:
+ * print(preset.name())
+ * @endcode
+ *
+ * Resources are identified by their type, name and filename. If you want to change
+ * the contents of a resource, you should read its data using data(), parse it and
+ * write the changed contents back.
+ */
+class KRITALIBKIS_EXPORT Resource : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Resource(KoResource *resource, QObject *parent = 0);
+ virtual ~Resource();
+ bool operator==(const Resource &other) const;
+ bool operator!=(const Resource &other) const;
+
+
+public Q_SLOTS:
+
+ /**
+ * Return the type of this resource. Valid types are:
+ * <ul>
+ * <li>pattern: a raster image representing a pattern
+ * <li>gradient: a gradient
+ * <li>brush: a brush tip
+ * <li>preset: a brush preset
+ * <li>palette: a color set
+ * <li>workspace: a workspace definition.
+ * </ul>
+ */
+ QString type() const;
+
+ /**
+ * The user-visible name of the resource.
+ */
+ QString name() const;
+
+ /**
+ * setName changes the user-visible name of the current resource.
+ */
+ void setName(QString value);
+
+ /**
+ * The filename of the resource, if present. Not all resources
+ * are loaded from files.
+ */
+ QString filename() const;
+
+ /**
+ * An image that can be used to represent the resource in the
+ * user interface. For some resources, like patterns, the
+ * image is identical to the resource, for others it's a mere
+ * icon.
+ */
+ QImage image() const;
+
+ /**
+ * Change the image for this resource.
+ */
+ void setImage(QImage image);
+
+ /**
+ * Return the resource as a byte array.
+ */
+ QByteArray data() const;
+
+ /**
+ * Change the internal data of the resource to the given byte
+ * array. If the byte array is not valid, setData returns
+ * false, otherwwise true.
+ */
+ bool setData(QByteArray data);
+
+private:
+
+ friend class PresetChooser;
+ friend class View;
+ KoResource *resource() const;
+
+ struct Private;
+ const Private *const d;
+
+};
+
+#endif // LIBKIS_RESOURCE_H
diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp
new file mode 100644
index 0000000000..766a893144
--- /dev/null
+++ b/libs/libkis/Selection.cpp
@@ -0,0 +1,315 @@
+/*
+ * 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 "Selection.h"
+
+#include <KoColorSpace.h>
+#include "kis_iterator_ng.h"
+#include <kis_selection.h>
+#include <kis_pixel_selection.h>
+#include <kis_paint_device.h>
+#include <kis_selection_filters.h>
+#include <kis_painter.h>
+#include <kis_clipboard.h>
+#include <kis_painter.h>
+#include <QByteArray>
+
+#include <Node.h>
+
+struct Selection::Private {
+ Private() {}
+ KisSelectionSP selection;
+};
+
+Selection::Selection(KisSelectionSP selection, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->selection = selection;
+}
+
+
+Selection::Selection(QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->selection = new KisSelection();
+}
+
+Selection::~Selection()
+{
+ delete d;
+}
+
+bool Selection::operator==(const Selection &other) const
+{
+ return (d->selection == other.d->selection);
+}
+
+bool Selection::operator!=(const Selection &other) const
+{
+ return !(operator==(other));
+}
+
+int Selection::width() const
+{
+ if (!d->selection) return 0;
+ return d->selection->selectedExactRect().width();
+}
+
+int Selection::height() const
+{
+ if (!d->selection) return 0;
+ return d->selection->selectedExactRect().height();
+}
+
+int Selection::x() const
+{
+ if (!d->selection) return 0;
+ return d->selection->x();
+}
+
+int Selection::y() const
+{
+ if (!d->selection) return 0;
+ return d->selection->y();
+}
+
+void Selection::move(int x, int y)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->moveTo(QPoint(x, y));
+}
+
+
+void Selection::clear()
+{
+ if (!d->selection) return;
+ d->selection->clear();
+}
+
+void Selection::contract(int value)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->select(QRect(x(), y(), width() - value, height() - value));
+}
+
+void Selection::copy(Node *node)
+{
+ if (!node) return;
+ if (!d->selection) return;
+ if (node->node()->exactBounds().isEmpty()) return;
+ if (!node->node()->hasEditablePaintDevice()) return;
+
+ KisPaintDeviceSP dev = node->node()->paintDevice();
+ KisPaintDeviceSP clip = new KisPaintDevice(dev->colorSpace());
+ KisPaintDeviceSP selectionProjection = d->selection->projection();
+
+ const KoColorSpace *cs = clip->colorSpace();
+ const KoColorSpace *selCs = d->selection->projection()->colorSpace();
+
+ QRect rc = d->selection->selectedExactRect();
+
+ KisPainter::copyAreaOptimized(QPoint(), dev, clip, rc);
+
+ KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width());
+ KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
+
+ for (qint32 y = 0; y < rc.height(); y++) {
+ for (qint32 x = 0; x < rc.width(); x++) {
+
+ qreal dstAlpha = cs->opacityF(layerIt->rawData());
+ qreal sel = selCs->opacityF(selectionIt->oldRawData());
+ qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha);
+ float mask = newAlpha / dstAlpha;
+
+ cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1);
+
+ layerIt->nextPixel();
+ selectionIt->nextPixel();
+ }
+ layerIt->nextRow();
+ selectionIt->nextRow();
+ }
+
+ KisClipboard::instance()->setClip(clip, rc.topLeft());
+}
+
+void Selection::cut(Node* node)
+{
+ if (!node) return;
+ if (!d->selection) return;
+ if (node->node()->exactBounds().isEmpty()) return;
+ if (!node->node()->hasEditablePaintDevice()) return;
+ KisPaintDeviceSP dev = node->node()->paintDevice();
+ copy(node);
+ dev->clearSelection(d->selection);
+ node->node()->setDirty(d->selection->selectedExactRect());
+}
+
+void Selection::paste(Node *destination, int x, int y)
+{
+ if (!destination) return;
+ if (!d->selection) return;
+ if (!KisClipboard::instance()->hasClip()) return;
+
+ KisPaintDeviceSP src = KisClipboard::instance()->clip(QRect(), false);
+ KisPaintDeviceSP dst = destination->node()->paintDevice();
+ if (!dst) return;
+ KisPainter::copyAreaOptimized(QPoint(x, y),
+ src,
+ dst,
+ src->exactBounds(),
+ d->selection);
+ destination->node()->setDirty();
+}
+
+void Selection::erode()
+{
+ if (!d->selection) return;
+ KisErodeSelectionFilter esf;
+ QRect rc = esf.changeRect(d->selection->selectedExactRect());
+ esf.process(d->selection->pixelSelection(), rc);
+}
+
+void Selection::dilate()
+{
+ if (!d->selection) return;
+ KisDilateSelectionFilter dsf;
+ QRect rc = dsf.changeRect(d->selection->selectedExactRect());
+ dsf.process(d->selection->pixelSelection(), rc);
+}
+
+void Selection::border(int xRadius, int yRadius)
+{
+ if (!d->selection) return;
+ KisBorderSelectionFilter sf(xRadius, yRadius);
+ QRect rc = sf.changeRect(d->selection->selectedExactRect());
+ sf.process(d->selection->pixelSelection(), rc);
+}
+
+void Selection::feather(int radius)
+{
+ if (!d->selection) return;
+ KisFeatherSelectionFilter fsf(radius);
+ QRect rc = fsf.changeRect(d->selection->selectedExactRect());
+ fsf.process(d->selection->pixelSelection(), rc);
+}
+
+void Selection::grow(int xradius, int yradius)
+{
+ if (!d->selection) return;
+ KisGrowSelectionFilter gsf(xradius, yradius);
+ QRect rc = gsf.changeRect(d->selection->selectedExactRect());
+ gsf.process(d->selection->pixelSelection(), rc);
+}
+
+
+void Selection::shrink(int xRadius, int yRadius, bool edgeLock)
+{
+ if (!d->selection) return;
+ KisShrinkSelectionFilter sf(xRadius, yRadius, edgeLock);
+ QRect rc = sf.changeRect(d->selection->selectedExactRect());
+ sf.process(d->selection->pixelSelection(), rc);
+}
+
+void Selection::smooth()
+{
+ if (!d->selection) return;
+ KisSmoothSelectionFilter sf;
+ QRect rc = sf.changeRect(d->selection->selectedExactRect());
+ sf.process(d->selection->pixelSelection(), rc);
+}
+
+
+void Selection::invert()
+{
+ if (!d->selection) return;
+ KisInvertSelectionFilter sf;
+ QRect rc = sf.changeRect(d->selection->selectedExactRect());
+ sf.process(d->selection->pixelSelection(), rc);
+}
+
+void Selection::resize(int w, int h)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->select(QRect(x(), y(), w, h));
+}
+
+void Selection::select(int x, int y, int w, int h, int value)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->select(QRect(x, y, w, h), value);
+}
+
+void Selection::selectAll(Node *node, int value)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->select(node->node()->exactBounds(), value);
+}
+
+void Selection::replace(Selection *selection)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_REPLACE);
+}
+
+void Selection::add(Selection *selection)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_ADD);
+}
+
+void Selection::subtract(Selection *selection)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_SUBTRACT);
+}
+
+void Selection::intersect(Selection *selection)
+{
+ if (!d->selection) return;
+ d->selection->pixelSelection()->applySelection(selection->selection()->pixelSelection(), SELECTION_INTERSECT);
+}
+
+
+QByteArray Selection::pixelData(int x, int y, int w, int h) const
+{
+ QByteArray ba;
+ if (!d->selection) return ba;
+ KisPaintDeviceSP dev = d->selection->projection();
+ quint8 *data = new quint8[w * h];
+ dev->readBytes(data, x, y, w, h);
+ ba = QByteArray((const char*)data, (int)(w * h));
+ delete[] data;
+ return ba;
+}
+
+void Selection::setPixelData(QByteArray value, int x, int y, int w, int h)
+{
+ if (!d->selection) return;
+ KisPixelSelectionSP dev = d->selection->pixelSelection();
+ if (!dev) return;
+ dev->writeBytes((const quint8*)value.constData(), x, y, w, h);
+}
+
+KisSelectionSP Selection::selection() const
+{
+ return d->selection;
+}
+
+
diff --git a/libs/libkis/Selection.h b/libs/libkis/Selection.h
new file mode 100644
index 0000000000..35147010da
--- /dev/null
+++ b/libs/libkis/Selection.h
@@ -0,0 +1,242 @@
+/*
+ * 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_SELECTION_H
+#define LIBKIS_SELECTION_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+#include <kis_types.h>
+
+/**
+ * Selection represents a selection on Krita. A selection is
+ * not necessarily associated with a particular Node or Image.
+ *
+ * @code
+ * from krita import *
+ *
+ * d = Application.activeDocument()
+ * n = d.activeNode()
+ * r = n.bounds()
+ * s = Selection()
+ * s.select(r.width() / 3, r.height() / 3, r.width() / 3, r.height() / 3, 255)
+ * s.cut(n)
+ * @endcode
+ */
+class KRITALIBKIS_EXPORT Selection : public QObject
+{
+ Q_OBJECT
+
+
+public:
+
+ /**
+ * For internal use only.
+ */
+ Selection(KisSelectionSP selection, QObject *parent = 0);
+
+ /**
+ * Create a new, empty selection object.
+ */
+ explicit Selection(QObject *parent = 0);
+ virtual ~Selection();
+
+ bool operator==(const Selection &other) const;
+ bool operator!=(const Selection &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * @return the width of the selection
+ */
+ int width() const;
+
+ /**
+ * @return the height of the selection
+ */
+ int height() const;
+
+ /**
+ * @return the left-hand position of the selection.
+ */
+ int x() const;
+
+ /**
+ * @return the top position of the selection.
+ */
+ int y() const;
+
+ /**
+ * Move the selection's top-left corner to the given coordinates.
+ */
+ void move(int x, int y);
+
+ /**
+ * Make the selection entirely unselected.
+ */
+ void clear();
+
+ /**
+ * Make the selection's width and height smaller by the given value.
+ * This will not move the selection's top-left position.
+ */
+ void contract(int value);
+
+ /**
+ * @brief copy copies the area defined by the selection from the node to the clipboard.
+ * @param node the node from where the pixels will be copied.
+ */
+ void copy(Node *node);
+
+ /**
+ * @brief cut erases the area defined by the selection from the node and puts a copy on the clipboard.
+ * @param node the node from which the selection will be cut.
+ */
+ void cut(Node *node);
+
+ /**
+ * @brief paste pastes the content of the clipboard to the given node, limited by the area of the current
+ * selection.
+ * @param destination the node where the pixels will be written
+ * @param x: the x position at which the clip will be written
+ * @param y: the y position at which the clip will be written
+ */
+ void paste(Node *destination, int x, int y);
+
+ /**
+ * Erode the selection with a radius of 1 pixel.
+ */
+ void erode();
+
+ /**
+ * Dilate the selection with a radius of 1 pixel.
+ */
+ void dilate();
+
+ /**
+ * Border the selection with the given radius.
+ */
+ void border(int xRadius, int yRadius);
+
+ /**
+ * Feather the selection with the given radius.
+ */
+ void feather(int radius);
+
+ /**
+ * Grow the selection with the given radius.
+ */
+ void grow(int xradius, int yradius);
+
+ /**
+ * Shrink the selection with the given radius.
+ */
+ void shrink(int xRadius, int yRadius, bool edgeLock);
+
+ /**
+ * Smooth the selection.
+ */
+ void smooth();
+
+ /**
+ * Invert the selection.
+ */
+ void invert();
+
+ /**
+ * Resize the selection to the given width and height. The top-left position will not be moved.
+ */
+ void resize(int w, int h);
+
+ /**
+ * Select the given area. The value can be between 0 and 255; 0 is
+ * totally unselected, 255 is totally selected.
+ */
+ void select(int x, int y, int w, int h, int value);
+
+ /**
+ * Select all pixels in the given node. The value can be between 0 and 255; 0 is
+ * totally unselected, 255 is totally selected.
+ */
+ void selectAll(Node *node, int value);
+
+ /**
+ * Replace the current selection's selection with the one of the given selection.
+ */
+ void replace(Selection *selection);
+
+ /**
+ * Add the given selection's selected pixels to the current selection.
+ */
+ void add(Selection *selection);
+
+ /**
+ * Subtract the given selection's selected pixels from the current selection.
+ */
+ void subtract(Selection *selection);
+
+ /**
+ * Intersect the given selection with this selection.
+ */
+ void intersect(Selection *selection);
+
+ /**
+ * @brief pixelData reads the given rectangle from the Selection's mask and returns it as a
+ * byte array. The pixel data starts top-left, and is ordered row-first.
+ *
+ * The byte array will contain one byte for every pixel, representing the selectedness. 0
+ * is totally unselected, 255 is fully selected.
+ *
+ * You can read outside the Selection's boundaries; those pixels will be unselected.
+ *
+ * The byte array is a copy of the original selection data.
+ * @param x x position from where to start reading
+ * @param y y position from where to start reading
+ * @param w row length to read
+ * @param h number of rows to read
+ * @return a QByteArray with the pixel data. The byte array may be empty.
+ */
+ QByteArray pixelData(int x, int y, int w, int h) const;
+
+ /**
+ * @brief setPixelData writes the given bytes, of which there must be enough, into the
+ * Selection.
+ *
+ * @param value the byte array representing the pixels. There must be enough bytes available.
+ * Krita will take the raw pointer from the QByteArray and start reading, not stopping before
+ * (w * h) bytes are read.
+ *
+ * @param x the x position to start writing from
+ * @param y the y position to start writing from
+ * @param w the width of each row
+ * @param h the number of rows to write
+ */
+ void setPixelData(QByteArray value, int x, int y, int w, int h);
+
+private:
+ friend class Document;
+
+ KisSelectionSP selection() const;
+
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_SELECTION_H
diff --git a/libs/libkis/View.cpp b/libs/libkis/View.cpp
new file mode 100644
index 0000000000..ca2d62ccec
--- /dev/null
+++ b/libs/libkis/View.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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 "View.h"
+#include <QPointer>
+
+#include <KoPattern.h>
+#include <KoAbstractGradient.h>
+#include <kis_paintop_preset.h>
+#include <KisView.h>
+#include <KisViewManager.h>
+#include <kis_selection_manager.h>
+#include <kis_canvas_resource_provider.h>
+#include <kis_paintop_box.h>
+#include <KisViewManager.h>
+#include <KisMainWindow.h>
+#include <KoCanvasBase.h>
+#include <kis_canvas2.h>
+#include "Document.h"
+#include "Canvas.h"
+#include "Window.h"
+#include "Resource.h"
+
+struct View::Private {
+ Private() {}
+ QPointer<KisView> view;
+};
+
+View::View(KisView* view, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->view = view;
+}
+
+View::~View()
+{
+ delete d;
+}
+
+bool View::operator==(const View &other) const
+{
+ return (d->view == other.d->view);
+}
+
+bool View::operator!=(const View &other) const
+{
+ return !(operator==(other));
+}
+
+Window* View::window() const
+{
+ if (!d->view) return 0;
+ KisMainWindow *mainwin = d->view->mainWindow();
+ Window *win = new Window(mainwin);
+ return win;
+}
+
+
+Document* View::document() const
+{
+ if (!d->view) return 0;
+ Document *doc = new Document(d->view->document());
+ return doc;
+}
+
+bool View::visible() const
+{
+ if (!d->view) return false;
+ return d->view->isVisible();
+}
+
+void View::setVisible()
+{
+ if (!d->view) return;
+ KisMainWindow *mainwin = d->view->mainWindow();
+ mainwin->setActiveView(d->view);
+ mainwin->subWindowActivated();
+}
+
+Canvas* View::canvas() const
+{
+ if (!d->view) return 0;
+ Canvas *c = new Canvas(d->view->canvasBase());
+ return c;
+}
+
+KisView *View::view()
+{
+ return d->view;
+}
+
+void View::activateResource(Resource *resource)
+{
+ if (!d->view) return;
+ if (!resource) return;
+
+ KoResource *r= resource->resource();
+ if (!r) return;
+
+ if (dynamic_cast<KoPattern*>(r)) {
+ QVariant v;
+ v.setValue(static_cast<void*>(r));
+ d->view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::CurrentPattern, v);
+ }
+ else if (dynamic_cast<KoAbstractGradient*>(r)) {
+ QVariant v;
+ v.setValue(static_cast<void*>(r));
+ d->view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::CurrentGradient, v);
+ }
+ else if (dynamic_cast<KisPaintOpPreset*>(r)) {
+ d->view->viewManager()->paintOpBox()->resourceSelected(r);
+ }
+
+}
+
+
+
diff --git a/libs/libkis/View.h b/libs/libkis/View.h
new file mode 100644
index 0000000000..c5a74fc43d
--- /dev/null
+++ b/libs/libkis/View.h
@@ -0,0 +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_VIEW_H
+#define LIBKIS_VIEW_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+class KisView;
+
+/**
+ * View represents one view on a document. A document can be
+ * shown in more than one view at a time.
+ */
+class KRITALIBKIS_EXPORT View : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(View)
+
+public:
+ explicit View(KisView *view, QObject *parent = 0);
+ virtual ~View();
+
+ bool operator==(const View &other) const;
+ bool operator!=(const View &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * @return the window this view is shown in.
+ */
+ Window* window() const;
+
+ /**
+ * @return the document this view is showing.
+ */
+ Document* document() const;
+
+ /**
+ * @return true if the current view is visible, false if not.
+ */
+ bool visible() const;
+
+ /**
+ * Make the current view visible.
+ */
+ void setVisible();
+
+ /**
+ * @return the canvas this view is showing. The canvas controls
+ * things like zoom and rotation.
+ */
+ Canvas* canvas() const;
+
+ /**
+ * @brief activateResource activates the given resource.
+ * @param resource: a pattern, gradient or paintop preset
+ */
+ void activateResource(Resource *resource);
+
+private:
+
+ friend class Window;
+ KisView *view();
+
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_VIEW_H
diff --git a/libs/libkis/Window.cpp b/libs/libkis/Window.cpp
new file mode 100644
index 0000000000..f11e49bb58
--- /dev/null
+++ b/libs/libkis/Window.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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 "Window.h"
+
+#include <KisMainWindow.h>
+#include <KisPart.h>
+#include <KisDocument.h>
+
+#include <Document.h>
+#include <View.h>
+
+
+struct Window::Private {
+ Private() {}
+
+ QPointer<KisMainWindow> window;
+};
+
+Window::Window(KisMainWindow *window, QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+ d->window = window;
+ connect(window, SIGNAL(destroyed(QObject*)), SIGNAL(windowClosed()));
+}
+
+Window::~Window()
+{
+ delete d;
+}
+
+bool Window::operator==(const Window &other) const
+{
+ return (d->window == other.d->window);
+}
+
+bool Window::operator!=(const Window &other) const
+{
+ return !(operator==(other));
+}
+
+QMainWindow *Window::qwindow() const
+{
+ return d->window;
+}
+
+QList<View*> Window::views() const
+{
+ QList<View *> ret;
+ if (d->window) {
+ foreach(QPointer<KisView> view, KisPart::instance()->views()) {
+ if (view->mainWindow() == d->window) {
+ ret << new View(view);
+ }
+ }
+ }
+ return ret;
+
+}
+
+View *Window::addView(Document *document)
+{
+ if (d->window) {
+ KisView *view = KisPart::instance()->createView(document->document(),
+ d->window->resourceManager(),
+ d->window->actionCollection(),
+ d->window);
+ d->window->addView(view);
+ return new View(view);
+ }
+ return 0;
+}
+
+void Window::showView(View *view)
+{
+ if (views().contains(view)) {
+ KisView *v = view->view();
+ d->window->showView(v);
+ }
+}
+
+void Window::activate()
+{
+ if (d->window) {
+ d->window->activateWindow();
+ }
+}
+
+void Window::close()
+{
+ if (d->window) {
+ KisPart::instance()->removeMainWindow(d->window);
+ d->window->close();
+ }
+}
+
+
+
diff --git a/libs/libkis/Window.h b/libs/libkis/Window.h
new file mode 100644
index 0000000000..c9a9511a9d
--- /dev/null
+++ b/libs/libkis/Window.h
@@ -0,0 +1,89 @@
+/*
+ * 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_WINDOW_H
+#define LIBKIS_WINDOW_H
+
+#include <QObject>
+#include <QMainWindow>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+#include <KisMainWindow.h>
+/**
+ * Window represents one Krita mainwindow. A window can have any number
+ * of views open on any number of documents.
+ */
+class KRITALIBKIS_EXPORT Window : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit Window(KisMainWindow *window, QObject *parent = 0);
+ virtual ~Window();
+
+ bool operator==(const Window &other) const;
+ bool operator!=(const Window &other) const;
+
+public Q_SLOTS:
+
+ /**
+ * Return a handle to the QMainWindow widget. This is useful
+ * to e.g. parent dialog boxes and message box.
+ */
+ QMainWindow *qwindow() const;
+
+ /**
+ * @return a list of open views in this window
+ */
+ QList<View*> views() const;
+
+ /**
+ * Open a new view on the given document in this window
+ */
+ View *addView(Document *document);
+
+ /**
+ * Make the given view active in this window. If the view
+ * does not belong to this window, nothing happens.
+ */
+ void showView(View *view);
+
+ /**
+ * @brief activate activates this Window.
+ */
+ void activate();
+
+ /**
+ * @brief close the active window and all its Views. If there
+ * are no Views left for a given Document, that Document will
+ * also be closed.
+ */
+ void close();
+
+Q_SIGNALS:
+ /// Emitted when the window is closed.
+ void windowClosed();
+
+private:
+ struct Private;
+ Private *const d;
+
+};
+
+#endif // LIBKIS_WINDOW_H
diff --git a/libs/libkis/libkis.h b/libs/libkis/libkis.h
new file mode 100644
index 0000000000..1f8106a627
--- /dev/null
+++ b/libs/libkis/libkis.h
@@ -0,0 +1,27 @@
+#include <QList>
+#include <QString>
+#include <QVariant>
+#include <QMap>
+#include <QByteArray>
+#include <QAction>
+
+class Action;
+class Canvas;
+class Channel;
+class ColorDepth;
+class ColorManager;
+class ColorModel;
+class ColorProfile;
+class DockWidget;
+class DockWidgetFactoryBase;
+class Document;
+class Filter;
+class InfoObject;
+class Krita;
+class Node;
+class Notifier;
+class Resource;
+class Selection;
+class View;
+class Extension;
+class Window;
diff --git a/libs/libkis/tests/CMakeLists.txt b/libs/libkis/tests/CMakeLists.txt
new file mode 100644
index 0000000000..203ecbb6fe
--- /dev/null
+++ b/libs/libkis/tests/CMakeLists.txt
@@ -0,0 +1,15 @@
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+
+include(ECMAddTests)
+include(KritaAddBrokenUnitTest)
+
+macro_add_unittest_definitions()
+
+ecm_add_tests(
+ #TestKrita.cpp
+ TestChannel.cpp
+ TestDocument.cpp
+ TestNode.cpp
+ TestFilter.cpp
+ NAME_PREFIX "libs-kritalibkis-"
+ LINK_LIBRARIES kritalibkis Qt5::Test)
diff --git a/libs/libkis/tests/TestChannel.cpp b/libs/libkis/tests/TestChannel.cpp
new file mode 100644
index 0000000000..5e1ae5fb0a
--- /dev/null
+++ b/libs/libkis/tests/TestChannel.cpp
@@ -0,0 +1,103 @@
+/* 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 "TestChannel.h"
+#include <QTest>
+#include <QColor>
+#include <QDataStream>
+
+#include <KritaVersionWrapper.h>
+#include <Node.h>
+#include <Channel.h>
+#include <Krita.h>
+
+#include <KoColorSpaceRegistry.h>
+#include <KoColorProfile.h>
+#include <KoColor.h>
+
+#include <kis_image.h>
+#include <kis_fill_painter.h>
+#include <kis_paint_layer.h>
+
+void TestChannel::testPixelDataU8()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ Node node(image, layer);
+ qDebug() << node.colorModel() << node.colorDepth() << node.colorProfile();
+ QList<Channel*> channels = node.channels();
+ Q_FOREACH(Channel *channel, channels) {
+ qDebug() << ">>>>>>>>>>>>>>>>>>>>" << channel->name() << channel->bounds();
+ //QVERIFY(channel->bounds() == QRect(0, 0, 100, 100));
+ QVERIFY(channel->channelSize() == 1);
+ }
+
+}
+
+void TestChannel::testPixelDataU16()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb16(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ Node node(image, layer);
+ qDebug() << node.colorModel() << node.colorDepth() << node.colorProfile();
+ QList<Channel*> channels = node.channels();
+ Q_FOREACH(Channel *channel, channels) {
+ qDebug() << ">>>>>>>>>>>>>>>>>>>>" << channel->name() << channel->bounds();
+ //QVERIFY(channel->bounds() == QRect(0, 0, 100, 100));
+ QVERIFY(channel->channelSize() == 2);
+ }
+}
+
+void TestChannel::testPixelDataF16()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F16", ""), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ Node node(image, layer);
+ qDebug() << node.colorModel() << node.colorDepth() << node.colorProfile();
+ QList<Channel*> channels = node.channels();
+ Q_FOREACH(Channel *channel, channels) {
+ qDebug() << ">>>>>>>>>>>>>>>>>>>>" << channel->name() << channel->bounds();
+ //QVERIFY(channel->bounds() == QRect(0, 0, 100, 100));
+ QVERIFY(channel->channelSize() == 2);
+ }
+}
+
+void TestChannel::testPixelDataF32()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->colorSpace("RGBA", "F32", ""), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ Node node(image, layer);
+ qDebug() << node.colorModel() << node.colorDepth() << node.colorProfile();
+ QList<Channel*> channels = node.channels();
+ Q_FOREACH(Channel *channel, channels) {
+ qDebug() << ">>>>>>>>>>>>>>>>>>>>" << channel->name() << channel->bounds();
+ //QVERIFY(channel->bounds() == QRect(0, 0, 100, 100));
+ QVERIFY(channel->channelSize() == 4);
+ }
+}
+
+
+QTEST_MAIN(TestChannel)
+
diff --git a/libs/libkis/tests/TestChannel.h b/libs/libkis/tests/TestChannel.h
new file mode 100644
index 0000000000..3478b86321
--- /dev/null
+++ b/libs/libkis/tests/TestChannel.h
@@ -0,0 +1,35 @@
+/* This file is part of the KDE project
+ Copyright (C) 2017 Boudewijn Rempt
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef TESTCHANNEL_H
+#define TESTCHANNEL_H
+
+#include <QObject>
+
+class TestChannel : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testPixelDataU8();
+ void testPixelDataU16();
+ void testPixelDataF16();
+ void testPixelDataF32();
+};
+
+#endif
+
diff --git a/libs/libkis/tests/TestDocument.cpp b/libs/libkis/tests/TestDocument.cpp
new file mode 100644
index 0000000000..f27cdd0b5e
--- /dev/null
+++ b/libs/libkis/tests/TestDocument.cpp
@@ -0,0 +1,137 @@
+/* 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 "TestDocument.h"
+#include <QTest>
+
+#include <KritaVersionWrapper.h>
+#include <QTest>
+#include <QColor>
+#include <QDataStream>
+
+#include <KritaVersionWrapper.h>
+#include <Node.h>
+#include <Krita.h>
+#include <Document.h>
+
+#include <KoColorSpaceRegistry.h>
+#include <KoColorProfile.h>
+#include <KoColor.h>
+
+#include <KisDocument.h>
+#include <kis_image.h>
+#include <kis_fill_painter.h>
+#include <kis_paint_layer.h>
+#include <KisPart.h>
+
+void TestDocument::testSetColorSpace()
+{
+ KisDocument *kisdoc = KisPart::instance()->createDocument();
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ image->addNode(layer);
+ kisdoc->setCurrentImage(image);
+
+ Document d(kisdoc);
+ QStringList profiles = Krita().profiles("GRAYA", "U16");
+ d.setColorSpace("GRAYA", "U16", profiles.first());
+
+ QVERIFY(layer->colorSpace()->colorModelId().id() == "GRAYA");
+ QVERIFY(layer->colorSpace()->colorDepthId().id() == "U16");
+ QVERIFY(layer->colorSpace()->profile()->name() == "gray built-in");
+}
+
+void TestDocument::testSetColorProfile()
+{
+ KisDocument *kisdoc = KisPart::instance()->createDocument();
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ image->addNode(layer);
+ kisdoc->setCurrentImage(image);
+
+ Document d(kisdoc);
+
+ QStringList profiles = Krita().profiles("RGBA", "U8");
+ Q_FOREACH(const QString &profile, profiles) {
+ d.setColorProfile(profile);
+ QVERIFY(image->colorSpace()->profile()->name() == profile);
+ }
+}
+
+void TestDocument::testPixelData()
+{
+ KisDocument *kisdoc = KisPart::instance()->createDocument();
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ image->addNode(layer);
+ kisdoc->setCurrentImage(image);
+
+ Document d(kisdoc);
+ d.refreshProjection();
+
+ QByteArray ba = d.pixelData(0, 0, 100, 100);
+ QDataStream ds(ba);
+ do {
+ quint8 channelvalue;
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 255);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 255);
+ } while (!ds.atEnd());
+}
+
+void TestDocument::testThumbnail()
+{
+ KisDocument *kisdoc = KisPart::instance()->createDocument();
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ image->addNode(layer);
+ kisdoc->setCurrentImage(image);
+
+ Document d(kisdoc);
+ d.refreshProjection();
+
+ QImage thumb = d.thumbnail(10, 10);
+ thumb.save("thumb.png");
+ QVERIFY(thumb.width() == 10);
+ QVERIFY(thumb.height() == 10);
+ // Our thumbnail calculater in KisPaintDevice cannot make a filled 10x10 thumbnail from a 100x100 device,
+ // it makes it 10x10 empty, then puts 8x8 pixels in there... Not a bug in the Node class
+ for (int i = 0; i < 8; ++i) {
+ for (int j = 0; j < 8; ++j) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
+ QVERIFY(thumb.pixelColor(i, j) == QColor(Qt::red));
+#else
+ QVERIFY(QColor(thumb.pixel(i, j)) == QColor(Qt::red));
+#endif
+ }
+ }
+
+}
+
+
+
+QTEST_MAIN(TestDocument)
+
diff --git a/libs/libkis/tests/TestDocument.h b/libs/libkis/tests/TestDocument.h
new file mode 100644
index 0000000000..65d5584af8
--- /dev/null
+++ b/libs/libkis/tests/TestDocument.h
@@ -0,0 +1,35 @@
+/* This file is part of the KDE project
+ Copyright (C) 2017 Boudewijn Rempt
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef TESTDOCUMENT_H
+#define TESTDOCUMENT_H
+
+#include <QObject>
+
+class TestDocument : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testSetColorSpace();
+ void testSetColorProfile();
+ void testPixelData();
+ void testThumbnail();
+};
+
+#endif
+
diff --git a/libs/libkis/tests/TestFilter.cpp b/libs/libkis/tests/TestFilter.cpp
new file mode 100644
index 0000000000..2a3f9861ca
--- /dev/null
+++ b/libs/libkis/tests/TestFilter.cpp
@@ -0,0 +1,102 @@
+/* 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 "TestFilter.h"
+#include <QTest>
+
+#include <KritaVersionWrapper.h>
+#include <QTest>
+#include <QColor>
+#include <QDataStream>
+
+#include <KritaVersionWrapper.h>
+#include <Node.h>
+#include <Krita.h>
+#include <Document.h>
+#include <Filter.h>
+
+#include <KoColorSpaceRegistry.h>
+#include <KoColorProfile.h>
+#include <KoColor.h>
+
+#include <KisDocument.h>
+#include <kis_image.h>
+#include <kis_fill_painter.h>
+#include <kis_paint_layer.h>
+#include <KisPart.h>
+
+void TestFilter::testApply()
+{
+ KisDocument *kisdoc = KisPart::instance()->createDocument();
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::black, layer->colorSpace()));
+ image->addNode(layer);
+ kisdoc->setCurrentImage(image);
+ Document d(kisdoc);
+ Node node(image, layer);
+
+ Filter f;
+ f.setName("invert");
+ QVERIFY(f.configuration());
+
+ d.lock();
+ f.apply(&node, 0, 0, 100, 100);
+ d.unlock();
+ d.refreshProjection();
+
+ for (int i = 0; i < 100 ; i++) {
+ for (int j = 0; j < 100 ; j++) {
+ QColor pixel;
+ layer->paintDevice()->pixel(i, j, &pixel);
+ QVERIFY(pixel == QColor(Qt::white));
+ }
+ }
+
+}
+
+void TestFilter::testStartFilter()
+{
+ KisDocument *kisdoc = KisPart::instance()->createDocument();
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::black, layer->colorSpace()));
+ image->addNode(layer);
+ kisdoc->setCurrentImage(image);
+ Document d(kisdoc);
+ Node node(image, layer);
+
+ Filter f;
+ f.setName("invert");
+ QVERIFY(f.configuration());
+
+ f.startFilter(&node, 0, 0, 100, 100);
+ image->waitForDone();
+
+ for (int i = 0; i < 100 ; i++) {
+ for (int j = 0; j < 100 ; j++) {
+ QColor pixel;
+ layer->paintDevice()->pixel(i, j, &pixel);
+ QVERIFY(pixel == QColor(Qt::white));
+ }
+ }
+}
+
+QTEST_MAIN(TestFilter)
+
diff --git a/libs/libkis/tests/TestFilter.h b/libs/libkis/tests/TestFilter.h
new file mode 100644
index 0000000000..aa2ea234ae
--- /dev/null
+++ b/libs/libkis/tests/TestFilter.h
@@ -0,0 +1,33 @@
+/* This file is part of the KDE project
+ Copyright (C) 2017 Boudewijn Rempt
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef TESTFILTER_H
+#define TESTFILTER_H
+
+#include <QObject>
+
+class TestFilter : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testApply();
+ void testStartFilter();
+};
+
+#endif
+
diff --git a/libs/libkis/tests/TestKrita.cpp b/libs/libkis/tests/TestKrita.cpp
new file mode 100644
index 0000000000..cd986c4fc4
--- /dev/null
+++ b/libs/libkis/tests/TestKrita.cpp
@@ -0,0 +1,64 @@
+/* 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 "TestKrita.h"
+#include <QTest>
+
+#include <KritaVersionWrapper.h>
+#include <Krita.h>
+#include <Window.h>
+#include <Document.h>
+
+void TestKrita::initTestCase()
+{
+ Krita::instance();
+}
+
+void TestKrita::testKrita()
+{
+ Krita *krita = Krita::instance();
+ QVERIFY2(krita, "Could not create krita instance.");
+ QCOMPARE(krita->batchmode(), false);
+ krita->setBatchmode(true);
+ QCOMPARE(krita->batchmode(), true);
+
+ QVERIFY(krita->filters().size() > 0);
+ QVERIFY(krita->filter(krita->filters().first()) != 0);
+
+ //QVERIFY(krita->generators().size() > 0);
+ //QVERIFY(krita->generator(krita->generators().first()) != 0);
+
+ QStringList profiles = krita->profiles("RGBA", "U8");
+ QVERIFY(profiles.size() != 0);
+ Document *doc = krita->createDocument(100, 100, "test", "RGBA", "U8", profiles.first());
+ QVERIFY(doc);
+ QCOMPARE(krita->documents().size(), 1);
+
+
+}
+
+void TestKrita::cleanupTestCase()
+{
+ if (m_win) {
+ m_win->close();
+ }
+ QTest::qWait(1000);
+}
+
+
+QTEST_MAIN(TestKrita)
+
diff --git a/libs/libkis/tests/TestKrita.h b/libs/libkis/tests/TestKrita.h
new file mode 100644
index 0000000000..57b9181b3d
--- /dev/null
+++ b/libs/libkis/tests/TestKrita.h
@@ -0,0 +1,37 @@
+/* This file is part of the KDE project
+ Copyright (C) 2017 Boudewijn Rempt
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef TESTKRITA_H
+#define TESTKRITA_H
+
+#include <QObject>
+class Window;
+
+class TestKrita : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void initTestCase();
+ void testKrita();
+ void cleanupTestCase();
+private:
+ Window *m_win {0};
+};
+
+#endif
+
diff --git a/libs/libkis/tests/TestNode.cpp b/libs/libkis/tests/TestNode.cpp
new file mode 100644
index 0000000000..eb7c15e9d9
--- /dev/null
+++ b/libs/libkis/tests/TestNode.cpp
@@ -0,0 +1,167 @@
+/* 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 "TestNode.h"
+#include <QTest>
+#include <QColor>
+#include <QDataStream>
+
+#include <KritaVersionWrapper.h>
+#include <Node.h>
+#include <Krita.h>
+
+#include <KoColorSpaceRegistry.h>
+#include <KoColorProfile.h>
+#include <KoColor.h>
+
+#include <kis_image.h>
+#include <kis_fill_painter.h>
+#include <kis_paint_layer.h>
+
+void TestNode::testSetColorSpace()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ Node node(image, layer);
+ QStringList profiles = Krita().profiles("GRAYA", "U16");
+ node.setColorSpace("GRAYA", "U16", profiles.first());
+ QVERIFY(layer->colorSpace()->colorModelId().id() == "GRAYA");
+ QVERIFY(layer->colorSpace()->colorDepthId().id() == "U16");
+ QVERIFY(layer->colorSpace()->profile()->name() == "gray built-in");
+}
+
+void TestNode::testSetColorProfile()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ Node node(image, layer);
+ QStringList profiles = Krita().profiles("RGBA", "U8");
+ Q_FOREACH(const QString &profile, profiles) {
+ node.setColorProfile(profile);
+ QVERIFY(layer->colorSpace()->profile()->name() == profile);
+ }
+}
+
+void TestNode::testPixelData()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::red, layer->colorSpace()));
+ Node node(image, layer);
+ QByteArray ba = node.pixelData(0, 0, 100, 100);
+ QDataStream ds(ba);
+ do {
+ quint8 channelvalue;
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 255);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 255);
+ } while (!ds.atEnd());
+
+ QDataStream ds2(&ba, QIODevice::WriteOnly);
+ for (int i = 0; i < 100 * 100; i++) {
+ ds2 << 255;
+ ds2 << 255;
+ ds2 << 255;
+ ds2 << 255;
+ }
+
+ node.setPixelData(ba, 0, 0, 100, 100);
+ for (int i = 0; i < 100 ; i++) {
+ for (int j = 0; j < 100 ; j++) {
+ QColor pixel;
+ layer->paintDevice()->pixel(i, j, &pixel);
+ QVERIFY(pixel == QColor(Qt::black));
+ }
+ }
+}
+
+void TestNode::testProjectionPixelData()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::gray, layer->colorSpace()));
+ Node node(image, layer);
+ QByteArray ba = node.projectionPixelData(0, 0, 100, 100);
+ QDataStream ds(ba);
+ for (int i = 0; i < 100 * 100; i++) {
+ quint8 channelvalue;
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0xA4);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0xA0);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0xA0);
+ ds >> channelvalue;
+ QVERIFY(channelvalue == 0xFF);
+ }
+}
+
+void TestNode::testThumbnail()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::gray, layer->colorSpace()));
+ Node node(image, layer);
+ QImage thumb = node.thumbnail(10, 10);
+ thumb.save("thumb.png");
+ QVERIFY(thumb.width() == 10);
+ QVERIFY(thumb.height() == 10);
+ // Our thumbnail calculater in KisPaintDevice cannot make a filled 10x10 thumbnail from a 100x100 device,
+ // it makes it 10x10 empty, then puts 8x8 pixels in there... Not a bug in the Node class
+ for (int i = 0; i < 8; ++i) {
+ for (int j = 0; j < 8; ++j) {
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
+ QVERIFY(thumb.pixelColor(i, j) == QColor(Qt::gray));
+#else
+ QVERIFY(QColor(thumb.pixel(i, j)) == QColor(Qt::gray));
+#endif
+ }
+ }
+}
+
+void TestNode::testMergeDown()
+{
+ KisImageSP image = new KisImage(0, 100, 100, KoColorSpaceRegistry::instance()->rgb8(), "test");
+
+ KisNodeSP layer = new KisPaintLayer(image, "test1", 255);
+ {
+ KisFillPainter gc(layer->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::gray, layer->colorSpace()));
+ }
+ image->addNode(layer);
+
+ KisNodeSP layer2 = new KisPaintLayer(image, "test2", 255);
+ {
+ KisFillPainter gc(layer2->paintDevice());
+ gc.fillRect(0, 0, 100, 100, KoColor(Qt::gray, layer2->colorSpace()));
+ }
+ image->addNode(layer2);
+ Node n1(image, layer);
+ Node *n2 = n1.mergeDown();
+ delete n2;
+}
+
+QTEST_MAIN(TestNode)
+
diff --git a/libs/libkis/tests/TestNode.h b/libs/libkis/tests/TestNode.h
new file mode 100644
index 0000000000..eebf577e4a
--- /dev/null
+++ b/libs/libkis/tests/TestNode.h
@@ -0,0 +1,37 @@
+/* This file is part of the KDE project
+ Copyright (C) 2017 Boudewijn Rempt
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#ifndef TESTNODE_H
+#define TESTNODE_H
+
+#include <QObject>
+
+class TestNode : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testSetColorSpace();
+ void testSetColorProfile();
+ void testPixelData();
+ void testProjectionPixelData();
+ void testThumbnail();
+ void testMergeDown();
+};
+
+#endif
+
diff --git a/libs/odf/CMakeLists.txt b/libs/odf/CMakeLists.txt
index 1ba16f491c..6acbf4d1a5 100644
--- a/libs/odf/CMakeLists.txt
+++ b/libs/odf/CMakeLists.txt
@@ -1,46 +1,45 @@
add_subdirectory( tests )
set(kritaodf_LIB_SRCS
KoOdf.cpp
KoOdfManifestEntry.cpp
KoDocumentInfo.cpp
KoGenStyle.cpp
KoGenStyles.cpp
KoFontFace.cpp
KoOdfLoadingContext.cpp
KoOasisSettings.cpp
KoOdfStylesReader.cpp
KoOdfNumberStyles.cpp
- KoOdfPaste.cpp
KoOdfReadStore.cpp
KoOdfWriteStore.cpp
KoStyleStack.cpp
KoOdfGraphicStyles.cpp
KoGenChange.cpp
KoGenChanges.cpp
KoDocumentBase.cpp
KoEmbeddedDocumentSaver.cpp
KoBorder.cpp
KoShadowStyle.cpp
KoPageLayout.cpp
KoPageFormat.cpp
KoColumns.cpp
KoUnit.cpp
KoOdfNotesConfiguration.cpp
KoOdfBibliographyConfiguration.cpp
KoOdfNumberDefinition.cpp
KoOdfLineNumberingConfiguration.cpp
KoElementReference.cpp
OdfDebug.cpp
)
add_library(kritaodf SHARED ${kritaodf_LIB_SRCS})
generate_export_header(kritaodf BASE_NAME kritaodf)
target_link_libraries(kritaodf kritaversion kritaplugin kritastore KF5::CoreAddons KF5::ConfigCore KF5::I18n Qt5::PrintSupport Qt5::Gui Qt5::Xml)
set_target_properties(kritaodf PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaodf ${INSTALL_TARGETS_DEFAULT_ARGS} )
diff --git a/libs/odf/KoDocumentInfo.cpp b/libs/odf/KoDocumentInfo.cpp
index 73df833b3c..918a08249e 100644
--- a/libs/odf/KoDocumentInfo.cpp
+++ b/libs/odf/KoDocumentInfo.cpp
@@ -1,471 +1,427 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2004 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoDocumentInfo.h"
#include "KoDocumentBase.h"
#include "KoOdfWriteStore.h"
#include "KoXmlNS.h"
#include <QDateTime>
#include <KoStoreDevice.h>
#include <KoXmlWriter.h>
#include <QDomDocument>
#include <KoXmlReader.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <OdfDebug.h>
#include <klocalizedstring.h>
#include <kuser.h>
#include <kemailsettings.h>
#include <KritaVersionWrapper.h>
KoDocumentInfo::KoDocumentInfo(QObject *parent) : QObject(parent)
{
m_aboutTags << "title" << "description" << "subject" << "abstract"
<< "keyword" << "initial-creator" << "editing-cycles" << "editing-time"
<< "date" << "creation-date" << "language";
m_authorTags << "creator" << "initial" << "author-title"
<< "email" << "telephone" << "telephone-work"
<< "fax" << "country" << "postal-code" << "city"
<< "street" << "position" << "company";
setAboutInfo("editing-cycles", "0");
setAboutInfo("time-elapsed", "0");
setAboutInfo("initial-creator", i18n("Unknown"));
setAboutInfo("creation-date", QDateTime::currentDateTime()
.toString(Qt::ISODate));
}
KoDocumentInfo::~KoDocumentInfo()
{
}
bool KoDocumentInfo::load(const KoXmlDocument &doc)
{
m_authorInfo.clear();
if (!loadAboutInfo(doc.documentElement()))
return false;
if (!loadAuthorInfo(doc.documentElement()))
return false;
return true;
}
-bool KoDocumentInfo::loadOasis(const KoXmlDocument &metaDoc)
-{
- m_authorInfo.clear();
-
- KoXmlNode t = KoXml::namedItemNS(metaDoc, KoXmlNS::office, "document-meta");
- KoXmlNode office = KoXml::namedItemNS(t, KoXmlNS::office, "meta");
-
- if (office.isNull())
- return false;
-
- if (!loadOasisAboutInfo(office))
- return false;
-
- if (!loadOasisAuthorInfo(office))
- return false;
-
- return true;
-}
QDomDocument KoDocumentInfo::save(QDomDocument &doc)
{
updateParametersAndBumpNumCycles();
QDomElement s = saveAboutInfo(doc);
if (!s.isNull())
doc.documentElement().appendChild(s);
s = saveAuthorInfo(doc);
if (!s.isNull())
doc.documentElement().appendChild(s);
if (doc.documentElement().isNull())
return QDomDocument();
return doc;
}
-bool KoDocumentInfo::saveOasis(KoStore *store)
-{
- updateParametersAndBumpNumCycles();
-
- KoStoreDevice dev(store);
- KoXmlWriter* xmlWriter = KoOdfWriteStore::createOasisXmlWriter(&dev,
- "office:document-meta");
- xmlWriter->startElement("office:meta");
-
- xmlWriter->startElement("meta:generator");
- xmlWriter->addTextNode(QString("Calligra/%1")
- .arg(KritaVersionWrapper::versionString()));
- xmlWriter->endElement();
-
- if (!saveOasisAboutInfo(*xmlWriter))
- return false;
- if (!saveOasisAuthorInfo(*xmlWriter))
- return false;
-
- xmlWriter->endElement();
- xmlWriter->endElement(); // root element
- xmlWriter->endDocument();
- delete xmlWriter;
- return true;
-}
-
void KoDocumentInfo::setAuthorInfo(const QString &info, const QString &data)
{
if (!m_authorTags.contains(info)) {
return;
}
m_authorInfoOverride.insert(info, data);
}
void KoDocumentInfo::setActiveAuthorInfo(const QString &info, const QString &data)
{
if (!m_authorTags.contains(info)) {
return;
}
if (data.isEmpty()) {
m_authorInfo.remove(info);
} else {
m_authorInfo.insert(info, data);
}
emit infoUpdated(info, data);
}
QString KoDocumentInfo::authorInfo(const QString &info) const
{
if (!m_authorTags.contains(info))
return QString();
return m_authorInfo[ info ];
}
void KoDocumentInfo::setAboutInfo(const QString &info, const QString &data)
{
if (!m_aboutTags.contains(info))
return;
m_aboutInfo.insert(info, data);
emit infoUpdated(info, data);
}
QString KoDocumentInfo::aboutInfo(const QString &info) const
{
if (!m_aboutTags.contains(info)) {
return QString();
}
return m_aboutInfo[info];
}
bool KoDocumentInfo::saveOasisAuthorInfo(KoXmlWriter &xmlWriter)
{
Q_FOREACH (const QString & tag, m_authorTags) {
if (!authorInfo(tag).isEmpty() && tag == "creator") {
xmlWriter.startElement("dc:creator");
xmlWriter.addTextNode(authorInfo("creator"));
xmlWriter.endElement();
} else if (!authorInfo(tag).isEmpty()) {
xmlWriter.startElement("meta:user-defined");
xmlWriter.addAttribute("meta:name", tag);
xmlWriter.addTextNode(authorInfo(tag));
xmlWriter.endElement();
}
}
return true;
}
bool KoDocumentInfo::loadOasisAuthorInfo(const KoXmlNode &metaDoc)
{
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, "creator");
if (!e.isNull() && !e.text().isEmpty())
setActiveAuthorInfo("creator", e.text());
KoXmlNode n = metaDoc.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (!n.isElement())
continue;
KoXmlElement e = n.toElement();
if (!(e.namespaceURI() == KoXmlNS::meta &&
e.localName() == "user-defined" && !e.text().isEmpty()))
continue;
QString name = e.attributeNS(KoXmlNS::meta, "name", QString());
setActiveAuthorInfo(name, e.text());
}
return true;
}
bool KoDocumentInfo::loadAuthorInfo(const KoXmlElement &e)
{
KoXmlNode n = e.namedItem("author").firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
KoXmlElement e = n.toElement();
if (e.isNull())
continue;
if (e.tagName() == "full-name")
setActiveAuthorInfo("creator", e.text().trimmed());
else
setActiveAuthorInfo(e.tagName(), e.text().trimmed());
}
return true;
}
QDomElement KoDocumentInfo::saveAuthorInfo(QDomDocument &doc)
{
QDomElement e = doc.createElement("author");
QDomElement t;
Q_FOREACH (const QString &tag, m_authorTags) {
if (tag == "creator")
t = doc.createElement("full-name");
else
t = doc.createElement(tag);
e.appendChild(t);
t.appendChild(doc.createTextNode(authorInfo(tag)));
}
return e;
}
bool KoDocumentInfo::saveOasisAboutInfo(KoXmlWriter &xmlWriter)
{
Q_FOREACH (const QString &tag, m_aboutTags) {
if (!aboutInfo(tag).isEmpty() || tag == "title") {
if (tag == "keyword") {
Q_FOREACH (const QString & tmp, aboutInfo("keyword").split(';')) {
xmlWriter.startElement("meta:keyword");
xmlWriter.addTextNode(tmp);
xmlWriter.endElement();
}
} else if (tag == "title" || tag == "description" || tag == "subject" ||
tag == "date" || tag == "language") {
QByteArray elementName(QString("dc:" + tag).toLatin1());
xmlWriter.startElement(elementName.constData());
xmlWriter.addTextNode(aboutInfo(tag));
xmlWriter.endElement();
} else {
QByteArray elementName(QString("meta:" + tag).toLatin1());
xmlWriter.startElement(elementName.constData());
xmlWriter.addTextNode(aboutInfo(tag));
xmlWriter.endElement();
}
}
}
return true;
}
bool KoDocumentInfo::loadOasisAboutInfo(const KoXmlNode &metaDoc)
{
QStringList keywords;
KoXmlElement e;
forEachElement(e, metaDoc) {
QString tag(e.localName());
if (! m_aboutTags.contains(tag) && tag != "generator") {
continue;
}
//debugOdf<<"localName="<<e.localName();
if (tag == "keyword") {
if (!e.text().isEmpty())
keywords << e.text().trimmed();
} else if (tag == "description") {
//this is the odf way but add meta:comment if is already loaded
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo("description", aboutInfo("description") + e.text().trimmed());
} else if (tag == "abstract") {
//this was the old way so add it to dc:description
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::meta, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo("description", aboutInfo("description") + e.text().trimmed());
} else if (tag == "title"|| tag == "subject"
|| tag == "date" || tag == "language") {
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::dc, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo(tag, e.text().trimmed());
} else if (tag == "generator") {
setOriginalGenerator(e.text().trimmed());
} else {
KoXmlElement e = KoXml::namedItemNS(metaDoc, KoXmlNS::meta, tag);
if (!e.isNull() && !e.text().isEmpty())
setAboutInfo(tag, e.text().trimmed());
}
}
if (keywords.count() > 0) {
setAboutInfo("keyword", keywords.join(", "));
}
return true;
}
bool KoDocumentInfo::loadAboutInfo(const KoXmlElement &e)
{
KoXmlNode n = e.namedItem("about").firstChild();
KoXmlElement tmp;
for (; !n.isNull(); n = n.nextSibling()) {
tmp = n.toElement();
if (tmp.isNull())
continue;
if (tmp.tagName() == "abstract")
setAboutInfo("abstract", tmp.text());
setAboutInfo(tmp.tagName(), tmp.text());
}
return true;
}
QDomElement KoDocumentInfo::saveAboutInfo(QDomDocument &doc)
{
QDomElement e = doc.createElement("about");
QDomElement t;
Q_FOREACH (const QString &tag, m_aboutTags) {
if (tag == "abstract") {
t = doc.createElement("abstract");
e.appendChild(t);
t.appendChild(doc.createCDATASection(aboutInfo(tag)));
} else {
t = doc.createElement(tag);
e.appendChild(t);
t.appendChild(doc.createTextNode(aboutInfo(tag)));
}
}
return e;
}
void KoDocumentInfo::updateParametersAndBumpNumCycles()
{
KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent());
if (doc && doc->isAutosaving()) {
return;
}
setAboutInfo("editing-cycles", QString::number(aboutInfo("editing-cycles").toInt() + 1));
setAboutInfo("date", QDateTime::currentDateTime().toString(Qt::ISODate));
updateParameters();
}
void KoDocumentInfo::updateParameters()
{
KoDocumentBase *doc = dynamic_cast< KoDocumentBase *>(parent());
if (doc && (!doc->isModified())) {
return;
}
KConfig config("kritarc");
config.reparseConfiguration();
KConfigGroup authorGroup(&config, "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
config.reparseConfiguration();
KConfigGroup appAuthorGroup(&config, "Author");
QString profile = appAuthorGroup.readEntry("active-profile", "");
if (profiles.contains(profile)) {
KConfigGroup cgs(&authorGroup, "Author-" + profile);
setActiveAuthorInfo("creator", cgs.readEntry("creator"));
setActiveAuthorInfo("initial", cgs.readEntry("initial"));
setActiveAuthorInfo("author-title", cgs.readEntry("author-title"));
setActiveAuthorInfo("email", cgs.readEntry("email"));
setActiveAuthorInfo("telephone", cgs.readEntry("telephone"));
setActiveAuthorInfo("telephone-work", cgs.readEntry("telephone-work"));
setActiveAuthorInfo("fax", cgs.readEntry("fax"));
setActiveAuthorInfo("country",cgs.readEntry("country"));
setActiveAuthorInfo("postal-code",cgs.readEntry("postal-code"));
setActiveAuthorInfo("city", cgs.readEntry("city"));
setActiveAuthorInfo("street", cgs.readEntry("street"));
setActiveAuthorInfo("position", cgs.readEntry("position"));
setActiveAuthorInfo("company", cgs.readEntry("company"));
} else {
if (profile == "anonymous") {
setActiveAuthorInfo("creator", QString());
setActiveAuthorInfo("telephone", QString());
setActiveAuthorInfo("telephone-work", QString());
setActiveAuthorInfo("email", QString());
} else {
KUser user(KUser::UseRealUserID);
setActiveAuthorInfo("creator", user.property(KUser::FullName).toString());
setActiveAuthorInfo("telephone-work", user.property(KUser::WorkPhone).toString());
setActiveAuthorInfo("telephone", user.property(KUser::HomePhone).toString());
KEMailSettings eMailSettings;
setActiveAuthorInfo("email", eMailSettings.getSetting(KEMailSettings::EmailAddress));
}
setActiveAuthorInfo("initial", "");
setActiveAuthorInfo("author-title", "");
setActiveAuthorInfo("fax", "");
setActiveAuthorInfo("country", "");
setActiveAuthorInfo("postal-code", "");
setActiveAuthorInfo("city", "");
setActiveAuthorInfo("street", "");
setActiveAuthorInfo("position", "");
setActiveAuthorInfo("company", "");
}
//alllow author info set programatically to override info from author profile
Q_FOREACH (const QString &tag, m_authorTags) {
if (m_authorInfoOverride.contains(tag)) {
setActiveAuthorInfo(tag, m_authorInfoOverride.value(tag));
}
}
}
void KoDocumentInfo::resetMetaData()
{
setAboutInfo("editing-cycles", QString::number(0));
setAboutInfo("initial-creator", authorInfo("creator"));
setAboutInfo("creation-date", QDateTime::currentDateTime().toString(Qt::ISODate));
setAboutInfo("editing-time", QString::number(0));
}
QString KoDocumentInfo::originalGenerator() const
{
return m_generator;
}
void KoDocumentInfo::setOriginalGenerator(const QString &generator)
{
m_generator = generator;
}
diff --git a/libs/odf/KoDocumentInfo.h b/libs/odf/KoDocumentInfo.h
index 37817812db..c047cfc8cd 100644
--- a/libs/odf/KoDocumentInfo.h
+++ b/libs/odf/KoDocumentInfo.h
@@ -1,228 +1,213 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999, 2000 Torben Weis <weis@kde.org>
Copyright (C) 2004 David Faure <faure@kde.org>
Copyright (C) 2006 Martin Pfeiffer <hubipete@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 KO_DOCUMENT_INFO_H
#define KO_DOCUMENT_INFO_H
#include <QObject>
#include <QMap>
#include <QString>
#include <QStringList>
#include "kritaodf_export.h"
#include <KoXmlReaderForward.h>
class QDomDocument;
class QDomElement;
class KoStore;
class KoXmlWriter;
/**
* @short The class containing all meta information about a document
*
* @author Torben Weis <weis@kde.org>
* @author David Faure <faure@kde.org>
* @author Martin Pfeiffer <hubipete@gmx.net>
* @see KoDocumentInfoDlg
*
* This class contains the meta information for a document. They are
* stored in two QMap and can be accessed through aboutInfo() and authorInfo().
* The about info can be changed with setAboutInfo() and setAuthorInfo()
*/
class KRITAODF_EXPORT KoDocumentInfo : public QObject
{
Q_OBJECT
public:
/**
* The constructor
* @param parent a pointer to the parent object
*/
explicit KoDocumentInfo(QObject *parent = 0);
/** The destructor */
~KoDocumentInfo();
-
- /**
- * Load the KoDocumentInfo from an OASIS document
- * @param metaDoc the QDomDocument with the metaInformation
- * @return true if success
- */
- bool loadOasis(const KoXmlDocument& metaDoc);
-
- /**
- * Save the KoDocumentInfo to an OASIS document
- * @param store a pointer to a KoStore to save in
- * @return true if success
- */
- bool saveOasis(KoStore* store);
-
/**
* Load the KoDocumentInfo from an Calligra-1.3 DomDocument
* @param doc the QDomDocument to load from
* @return true if success
*/
bool load(const KoXmlDocument& doc);
/**
* Save the KoDocumentInfo to an Calligra-1.3 DomDocument
* @return the QDomDocument to which was saved
*/
QDomDocument save(QDomDocument &doc);
/**
* Set information about the author.
* This will override any information retrieved from the author profile
* But it does not change the author profile
* Note: authorInfo() will not return the new value until the document has been
* saved by the user.(autosave doesn't count)
* @param info the kind of information to set
* @param data the data to set for this information
*/
void setAuthorInfo(const QString& info, const QString& data);
/**
* Obtain information about the author
* @param info the kind of information to obtain
* @return a QString with the information
*/
QString authorInfo(const QString& info) const;
/**
* Set information about the document
* @param info the kind of information to set
* @param data the data to set for this information
*/
void setAboutInfo(const QString& info, const QString& data);
/**
* Obtain information about the document
* @param info the kind of information to obtain
* @return a QString with the information
*/
QString aboutInfo(const QString& info) const;
/**
* Obtain the generator of the document, as it was loaded from the document
*/
QString originalGenerator() const;
/**
* Sets the original generator of the document. This does not affect what gets
* saved to a document in the meta:generator field, it only changes what
* originalGenerator() will return.
*/
void setOriginalGenerator(const QString& generator);
/** Resets part of the meta data */
void resetMetaData();
/** Takes care of updating the document info from configuration correctly */
void updateParameters();
private:
/// Bumps the editing cycles count and save date, and then calls updateParameters
void updateParametersAndBumpNumCycles();
/**
* Set information about the author
* This sets what is actually saved to file. The public method setAuthorInfo() can be used to set
* values that overide what is fetched from the author profile. During saveParameters() author
* profile and any overrides is combined resulting in calls to this method.
* @param info the kind of information to set
* @param data the data to set for this information
*/
void setActiveAuthorInfo(const QString& info, const QString& data);
/**
* Load the information about the document from an OASIS file
* @param metaDoc a reference to the information node
* @return true if success
*/
bool loadOasisAboutInfo(const KoXmlNode& metaDoc);
/**
* Save the information about the document to an OASIS file
* @param xmlWriter a reference to the KoXmlWriter to write in
* @return true if success
*/
bool saveOasisAboutInfo(KoXmlWriter &xmlWriter);
/**
* Load the information about the document from a Calligra-1.3 file
* @param e the element to load from
* @return true if success
*/
bool loadAboutInfo(const KoXmlElement& e);
/**
* Save the information about the document to a Calligra-1.3 file
* @param doc the QDomDocument to save in
* @return the QDomElement to which was saved
*/
QDomElement saveAboutInfo(QDomDocument& doc);
/**
* Load the information about the document from an OASIS file
* @param metaDoc a reference to the information node
* @return true if success
*/
bool loadOasisAuthorInfo(const KoXmlNode& metaDoc);
/**
* Load the information about the document from a Calligra-1.3 file
* @param e the element to load from
* @return true if success
*/
bool loadAuthorInfo(const KoXmlElement& e);
/**
* Save the information about the author to a Calligra-1.3 file
* @param doc the QDomDocument to save in
* @return the QDomElement to which was saved
*/
QDomElement saveAuthorInfo(QDomDocument& doc);
/**
* Save the information about the document to an OASIS file
* @param xmlWriter a reference to the KoXmlWriter to write in
* @return true if success
*/
bool saveOasisAuthorInfo(KoXmlWriter &xmlWriter);
/** A QStringList containing all tags for the document information */
QStringList m_aboutTags;
/** A QStringList containing all tags for the author information */
QStringList m_authorTags;
/** The map containing information about the author */
QMap<QString, QString> m_authorInfo;
/** The map containing information about the author set programatically*/
QMap<QString, QString> m_authorInfoOverride;
/** The map containing information about the document */
QMap<QString, QString> m_aboutInfo;
/** The original meta:generator of the document */
QString m_generator;
Q_SIGNALS:
void infoUpdated(const QString &info, const QString &data);
};
#endif
diff --git a/libs/odf/KoOdfPaste.cpp b/libs/odf/KoOdfPaste.cpp
deleted file mode 100644
index d7d3885299..0000000000
--- a/libs/odf/KoOdfPaste.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 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 "KoOdfPaste.h"
-
-#include <QBuffer>
-#include <QByteArray>
-#include <QMimeData>
-#include <QString>
-
-#include <OdfDebug.h>
-
-#include <KoStore.h>
-#include <KoOdfReadStore.h>
-#include <KoXmlReader.h>
-#include <KoXmlNS.h>
-
-KoOdfPaste::KoOdfPaste()
-{
-}
-
-KoOdfPaste::~KoOdfPaste()
-{
-}
-
-bool KoOdfPaste::paste(KoOdf::DocumentType documentType, const QMimeData *data)
-{
- QByteArray arr = data->data(KoOdf::mimeType(documentType));
- return paste(documentType, arr);
-}
-
-bool KoOdfPaste::paste(KoOdf::DocumentType documentType, const QByteArray &bytes)
-{
- if (bytes.isEmpty())
- return false;
-
- QBuffer buffer;
- buffer.setData(bytes);
- KoStore *store = KoStore::createStore(&buffer, KoStore::Read);
- //FIXME: Use shared_ptr or smth like these to auto delete store on return
- // and delete all next "delete store;".
-
- KoOdfReadStore odfStore(store); // KoOdfReadStore does not delete the store on destruction
-
- QString errorMessage;
- if (! odfStore.loadAndParse(errorMessage)) {
- warnOdf << "loading and parsing failed:" << errorMessage;
- delete store;
- return false;
- }
-
- KoXmlElement content = odfStore.contentDoc().documentElement();
- KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
-
- if (realBody.isNull()) {
- warnOdf << "No body tag found";
- delete store;
- return false;
- }
-
- KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, KoOdf::bodyContentElement(documentType, false));
-
- if (body.isNull()) {
- warnOdf << "No" << KoOdf::bodyContentElement(documentType, true) << "tag found";
- delete store;
- return false;
- }
-
- bool retval = process(body, odfStore);
- delete store;
- return retval;
-}
diff --git a/libs/odf/KoOdfPaste.h b/libs/odf/KoOdfPaste.h
deleted file mode 100644
index 1cb00d2665..0000000000
--- a/libs/odf/KoOdfPaste.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 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 KOODFPASTE_H
-#define KOODFPASTE_H
-
-#include "KoOdf.h"
-#include "kritaodf_export.h"
-#include "KoXmlReaderForward.h"
-
-class QMimeData;
-class QByteArray;
-class KoOdfReadStore;
-
-/**
- * This is a helper class to help you paste odf snippets.
- */
-class KRITAODF_EXPORT KoOdfPaste
-{
-public:
- KoOdfPaste();
- virtual ~KoOdfPaste();
-
- bool paste(KoOdf::DocumentType documentType, const QMimeData *data);
- /**
- * This is an overloaded member function, provided for convenience. It differs
- * from the above function only in what argument(s) it accepts.
- */
- bool paste(KoOdf::DocumentType documentType, const QByteArray &data);
-
-protected:
- virtual bool process(const KoXmlElement &body, KoOdfReadStore &odfStore) = 0;
-};
-
-#endif /* KOODFPASTE_H */
diff --git a/libs/pigment/CMakeLists.txt b/libs/pigment/CMakeLists.txt
index c44fcb81c4..0920b9e0ba 100644
--- a/libs/pigment/CMakeLists.txt
+++ b/libs/pigment/CMakeLists.txt
@@ -1,122 +1,132 @@
project(kritapigment)
# we have to repeat platform specifics from top-level
if (WIN32)
include_directories(${CMAKE_SOURCE_DIR}/winquirks)
add_definitions(-D_USE_MATH_DEFINES)
add_definitions(-DNOMINMAX)
set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib)
endif ()
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_SOURCE_DIR}/compositeops)
include_directories(SYSTEM
${Boost_INCLUDE_DIRS}
)
set(FILE_OPENEXR_SOURCES)
set(LINK_OPENEXR_LIB)
if(OPENEXR_FOUND)
include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR})
set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES})
add_definitions(${OPENEXR_DEFINITIONS})
endif()
set(LINK_VC_LIB)
if(HAVE_VC)
include_directories(SYSTEM ${Vc_INCLUDE_DIR})
set(LINK_VC_LIB ${Vc_LIBRARIES})
ko_compile_for_all_implementations_no_scalar(__per_arch_factory_objs compositeops/KoOptimizedCompositeOpFactoryPerArch.cpp)
message("Following objects are generated from the per-arch lib")
message(${__per_arch_factory_objs})
endif()
add_subdirectory(tests)
add_subdirectory(benchmarks)
set(kritapigment_SRCS
DebugPigment.cpp
KoBasicHistogramProducers.cpp
KoColor.cpp
KoColorDisplayRendererInterface.cpp
KoColorConversionAlphaTransformation.cpp
KoColorConversionCache.cpp
KoColorConversions.cpp
KoColorConversionSystem.cpp
KoColorConversionTransformation.cpp
KoColorProofingConversionTransformation.cpp
KoColorConversionTransformationFactory.cpp
KoColorModelStandardIds.cpp
KoColorProfile.cpp
KoColorSpace.cpp
KoColorSpaceEngine.cpp
KoColorSpaceFactory.cpp
KoColorSpaceMaths.cpp
KoColorSpaceRegistry.cpp
KoColorTransformation.cpp
KoColorTransformationFactory.cpp
KoColorTransformationFactoryRegistry.cpp
KoCompositeColorTransformation.cpp
KoCompositeOp.cpp
KoCompositeOpRegistry.cpp
KoCopyColorConversionTransformation.cpp
KoFallBackColorTransformation.cpp
KoHistogramProducer.cpp
KoMultipleColorConversionTransformation.cpp
KoUniqueNumberForIdServer.cpp
colorspaces/KoAlphaColorSpace.cpp
+ colorspaces/KoAlphaU16ColorSpace.cpp
+ colorspaces/KoAlphaF16ColorSpace.cpp
+ colorspaces/KoAlphaF32ColorSpace.cpp
colorspaces/KoLabColorSpace.cpp
colorspaces/KoRgbU16ColorSpace.cpp
colorspaces/KoRgbU8ColorSpace.cpp
colorspaces/KoSimpleColorSpaceEngine.cpp
compositeops/KoOptimizedCompositeOpFactory.cpp
compositeops/KoOptimizedCompositeOpFactoryPerArch_Scalar.cpp
${__per_arch_factory_objs}
colorprofiles/KoDummyColorProfile.cpp
resources/KoAbstractGradient.cpp
resources/KoColorSet.cpp
resources/KoPattern.cpp
resources/KoResource.cpp
resources/KoMD5Generator.cpp
resources/KoHashGeneratorProvider.cpp
resources/KoStopGradient.cpp
resources/KoSegmentGradient.cpp
)
+if (HAVE_LCMS24 AND OPENEXR_FOUND)
+ set (kritapigment_SRCS
+ ${kritapigment_SRCS}
+ colorspaces/KoAlphaF16ColorSpace.cpp
+ )
+endif()
+
set (EXTRA_LIBRARIES ${LINK_OPENEXR_LIB} ${LINK_VC_LIB})
if(MSVC OR (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel"))
# avoid "cannot open file 'LIBC.lib'" error
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /NODEFAULTLIB:LIBC.LIB")
endif()
add_library(kritapigment SHARED ${kritapigment_SRCS})
generate_export_header(kritapigment)
target_include_directories( kritapigment
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/resources>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/compositeops>
)
target_link_libraries( kritapigment
PUBLIC
kritaplugin
kritastore
${EXTRA_LIBRARIES}
KF5::I18n
KF5::ConfigCore
Qt5::Core
Qt5::Gui
Qt5::Xml
${WIN32_PLATFORM_NET_LIBS}
)
set_target_properties(kritapigment PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritapigment ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/pigment/KoChannelInfo.h b/libs/pigment/KoChannelInfo.h
index b5854550c4..8c3a80d359 100644
--- a/libs/pigment/KoChannelInfo.h
+++ b/libs/pigment/KoChannelInfo.h
@@ -1,276 +1,276 @@
/*
* Copyright (c) 2004 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 KOCHANNELINFO_H_
#define KOCHANNELINFO_H_
#include <limits>
#include <QColor>
#include <QString>
#include <QList>
/**
* This class gives some basic information about a channel,
* that is, one of the components that makes up a particular
* pixel.
*/
class KoChannelInfo
{
public:
/**
* Used to represent a min and max range.
*/
struct DoubleRange
{
public:
double minVal, maxVal;
public:
/// creates an invalid range of 0,0
DoubleRange(void) : minVal(0), maxVal(0) { }
/// creates
DoubleRange(qreal _minVal, qreal _maxVal) : minVal(_minVal), maxVal(_maxVal) { Q_ASSERT(minVal <= maxVal); }
/// true if this range is usable
bool isValid(void) const { return minVal < maxVal; }
};
public:
/// enum to define the type of the channel
enum enumChannelType {
COLOR, ///< The channel represents a color
ALPHA ///< The channel represents the opacity of a pixel
//SUBSTANCE, ///< The channel represents a real-world substance like pigments or medium
//SUBSTRATE ///< The channel represents a real-world painting substrate like a canvas
};
/// enum to define the value of the channel
enum enumChannelValueType {
UINT8, ///< use this for an unsigned integer 8bits channel
UINT16, ///< use this for an integer 16bits channel
UINT32, ///< use this for an unsigned integer 21bits channel
FLOAT16, ///< use this for a float 16bits channel
FLOAT32, ///< use this for a float 32bits channel
FLOAT64, ///< use this for a float 64bits channel
INT8, ///< use this for an integer 8bits channel
INT16, ///< use this for an integer 16bits channel
OTHER ///< Use this if the channel is neither an integer or a float
};
-
+
public:
KoChannelInfo() { }
/**
* @param name of the channel
* @param npos position of the channel in the pixel (in bytes)
* @param displayPosition the position of the channel in the user-visible order
* @param channelType type of the channel
* @param channelValueType type of the numerical data used by the channel
* @param size number of bytes (not bits) of the channel (if -1, it is deduced from the channelType)
* @param color a color to represent that channel (for instance in an histogram)
*/
- KoChannelInfo(const QString & name,
+ KoChannelInfo(const QString & name,
qint32 npos,
- qint32 displayPosition,
- enumChannelType channelType,
+ qint32 displayPosition,
+ enumChannelType channelType,
enumChannelValueType channelValueType,
- qint32 size = -1,
+ qint32 size = -1,
const QColor &color = QColor(0, 0, 0),
const DoubleRange &uiMinMax = DoubleRange())
: m_name(name)
, m_pos(npos)
, m_displayPosition(displayPosition)
, m_channelType(channelType)
, m_channelValueType(channelValueType)
, m_size(size)
, m_color(color)
, m_uiMinMax(uiMinMax)
{
switch(m_channelValueType)
{
case UINT8:
case INT8:
Q_ASSERT(m_size == -1 || m_size == 1);
m_size = 1;
break;
case UINT16:
case INT16:
Q_ASSERT(m_size == -1 || m_size == 2);
m_size = 2;
break;
case UINT32:
Q_ASSERT(m_size == -1 || m_size == 4);
m_size = 4;
break;
case FLOAT16:
Q_ASSERT(m_size == -1 || m_size == 2);
m_size = 2;
break;
case FLOAT32:
Q_ASSERT(m_size == -1 || m_size == 4);
m_size = 4;
break;
case FLOAT64:
Q_ASSERT(m_size == -1 || m_size == 8);
m_size = 8;
break;
case OTHER:
Q_ASSERT(m_size != -1);
}
if (!uiMinMax.isValid()) {
switch (m_channelValueType) {
case UINT8:
m_uiMinMax.minVal = std::numeric_limits<quint8>::min();
m_uiMinMax.maxVal = std::numeric_limits<quint8>::max();
break;
case INT8:
m_uiMinMax.minVal = std::numeric_limits<qint8>::min();
m_uiMinMax.maxVal = std::numeric_limits<qint8>::max();
break;
case UINT16:
m_uiMinMax.minVal = std::numeric_limits<quint16>::min();
m_uiMinMax.maxVal = std::numeric_limits<quint16>::max();
break;
case INT16:
m_uiMinMax.minVal = std::numeric_limits<qint16>::min();
m_uiMinMax.maxVal = std::numeric_limits<qint16>::max();
break;
case UINT32:
m_uiMinMax.minVal = std::numeric_limits<quint32>::min();
m_uiMinMax.maxVal = std::numeric_limits<quint32>::max();
break;
default:
// assume real otherwise, which is 0..1 by default
m_uiMinMax.minVal = 0.0;
m_uiMinMax.maxVal = 1.0;
break;
}
}
Q_ASSERT(m_uiMinMax.isValid());
}
public:
-
+
/**
* converts the display position to the pixel-order index in the channels vector.
*/
static int displayPositionToChannelIndex(int displayPosition, const QList<KoChannelInfo*> &channels)
{
for (int i = 0; i < channels.size(); ++i) {
if (channels.at(i)->displayPosition() == displayPosition) {
return i;
}
}
return -1;
}
-
+
static QList<KoChannelInfo*> displayOrderSorted(const QList<KoChannelInfo*> &channels)
{
QList <KoChannelInfo*> sortedChannels;
for (int i = 0; i < channels.size(); ++i) {
Q_FOREACH (KoChannelInfo* channel, channels) {
if (channel->displayPosition() == i) {
sortedChannels << channel;
break;
}
}
}
Q_ASSERT(channels.size() == sortedChannels.size());
return sortedChannels;
}
-
+
/**
* User-friendly name for this channel for presentation purposes in the gui
*/
inline QString name() const {
return m_name;
}
-
+
/**
- * returns the position of the first byte of the channel in the pixel
+ * @return the position of the first byte of the channel in the pixel
*/
inline qint32 pos() const {
return m_pos;
}
-
+
/**
- * @return the displayPosition of the channel in pixel
+ * @return the displayPosition of the channel in the pixel
*/
inline qint32 displayPosition() const {
return m_displayPosition;
}
-
+
/**
- * returns the number of bytes this channel takes
+ * @return the number of bytes this channel takes
*/
inline qint32 size() const {
return m_size;
}
-
+
/**
- * returns the type of the channel
+ * @return the type of the channel
*/
inline enumChannelType channelType() const {
return m_channelType;
}
/**
- * return the type of the value of the channel (float, uint8 or uint16)
+ * @return the type of the value of the channel (float, uint8 or uint16)
*/
inline enumChannelValueType channelValueType() const {
return m_channelValueType;
}
/**
* This is a color that can be used to represent this channel in histograms and so.
* By default this is black, so keep in mind that many channels might look the same
*/
inline QColor color() const {
return m_color;
}
-
+
/**
* A channel is less than another channel if its pos is smaller.
*/
inline bool operator<(const KoChannelInfo & info) {
return m_pos < info.m_pos;
}
/**
* Gets the minimum value that this channel should have.
* This is suitable for UI use.
*/
inline double getUIMin(void) const {
return m_uiMinMax.minVal;
}
/**
* Gets the minimum value that this channel should have.
* This is suitable for UI use.
*/
inline double getUIMax(void) const {
return m_uiMinMax.maxVal;
}
-
+
private:
-
+
QString m_name;
qint32 m_pos;
qint32 m_displayPosition;
enumChannelType m_channelType;
enumChannelValueType m_channelValueType;
qint32 m_size;
QColor m_color;
DoubleRange m_uiMinMax;
-
+
};
#endif // KOCHANNELINFO_H_
diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h
index b4653f25f7..b6e4e9c8a8 100644
--- a/libs/pigment/KoColor.h
+++ b/libs/pigment/KoColor.h
@@ -1,173 +1,174 @@
/*
* Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOCOLOR_H
#define KOCOLOR_H
#include <QColor>
#include <QMetaType>
#include "kritapigment_export.h"
#include "KoColorConversionTransformation.h"
+#include <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
+class KRITAPIGMENT_EXPORT KoColor : public boost::equality_comparable<KoColor>
{
public:
/// Create an empty KoColor. It will be valid, but also black and transparent
KoColor();
~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);
/**
* assignment operator to copy the data from the param color into this one.
* @param other the color we are going to copy
* @return this color
*/
KoColor &operator=(const KoColor &other);
bool operator==(const KoColor &other) const;
/// return the current colorSpace
const KoColorSpace * colorSpace() const;
/// return the current profile
const KoColorProfile * profile() const;
/// Convert this KoColor to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, do nothing. Returns the converted KoColor.
void convertTo(const KoColorSpace * cs,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
void convertTo(const KoColorSpace * cs);
/// assign new profile without converting pixel data
void setProfile(const KoColorProfile *profile);
/// Replace the existing color data, and colorspace with the specified data.
/// The data is copied.
void setColor(const quint8 * data, const KoColorSpace * colorSpace = 0);
/// Convert the color from src and replace the value of the current color with the converted data.
/// Don't convert the color if src and this have the same colorspace.
void fromKoColor(const KoColor& src);
/// a convenience method for the above.
void toQColor(QColor *c) const;
/// a convenience method for the above.
QColor toQColor() const;
/**
* Convenient function to set the opacity of the color.
*/
void setOpacity(quint8 alpha);
void setOpacity(qreal alpha);
/**
* Convenient function that return the opacity of the color
*/
quint8 opacityU8() const;
qreal opacityF() const;
/// Convenient function for converting from a QColor
void fromQColor(const QColor& c) const;
/**
* @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 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;
/**
* Serialize this color following Create's swatch color specification available
* at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
*
* This function doesn't create the <color /> element but rather the <CMYK />,
* <sRGB />, <RGB /> ... elements. It is assumed that colorElt is the <color />
* element.
*
* @param colorElt root element for the serialization, it is assumed that this
* element is <color />
* @param doc is the document containing colorElt
*/
void toXML(QDomDocument& doc, QDomElement& colorElt) const;
/**
* Unserialize a color following Create's swatch color specification available
* at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
*
* @param elt the element to unserialize (<CMYK />, <sRGB />, <RGB />)
* @param bitDepthId the bit depth is unspecified by the spec, this allow to select
* a preferred bit depth for creating the KoColor object (if that
* bit depth isn't available, this function will randomly select
* an other bit depth)
* @return the unserialize color, or an empty color object if the function failed
* to unserialize the color
*/
static KoColor fromXML(const QDomElement& elt, const QString & bitDepthId);
static QString toQString(const KoColor &color);
#ifndef NODEBUG
/// use qDebug calls to print internal info
void dump() const;
#endif
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoColor)
#endif
diff --git a/libs/pigment/KoColorProfile.h b/libs/pigment/KoColorProfile.h
index 5d98e36b3f..f339ba60ed 100644
--- a/libs/pigment/KoColorProfile.h
+++ b/libs/pigment/KoColorProfile.h
@@ -1,209 +1,211 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_COLOR_PROFILE_H_
#define _KO_COLOR_PROFILE_H_
#include <QString>
#include <QVector>
#include <QVariant>
#include "kritapigment_export.h"
/**
* Contains information needed for color transformation.
*/
class KRITAPIGMENT_EXPORT KoColorProfile
{
public:
/**
* @param fileName file name to load or save that profile
*/
explicit KoColorProfile(const QString &fileName = QString());
KoColorProfile(const KoColorProfile& profile);
virtual ~KoColorProfile();
/**
* @return the type of this profile (icc, ctlcs etc)
*/
virtual QString type() const {
return QString();
}
/**
* Create a copy of this profile.
* Data that shall not change during the life time of the profile shouldn't be
* duplicated but shared, like for instance ICC data.
*
* Data that shall be changed like a palette or hdr information such as exposure
* must be duplicated while cloning.
*/
virtual KoColorProfile* clone() const = 0;
/**
* Load the profile in memory.
* @return true if the profile has been successfully loaded
*/
virtual bool load();
/**
* Override this function to save the profile.
* @param fileName destination
* @return true if the profile has been successfully saved
*/
virtual bool save(const QString &fileName);
/**
* @return true if the profile is valid, false if it isn't been loaded in memory yet, or
* if the loaded memory is a bad profile
*/
virtual bool valid() const = 0;
/**
* @return the name of this profile
*/
QString name() const;
/**
* @return the info of this profile
*/
QString info() const;
/** @return manufacturer of the profile
*/
QString manufacturer() const;
/**
* @return the copyright of the profile
*/
QString copyright() const;
/**
* @return the filename of the profile (it might be empty)
*/
QString fileName() const;
/**
* @param filename new filename
*/
void setFileName(const QString &filename);
/**
* Return version
*/
virtual float version() const = 0;
/**
* @return true if you can use this profile can be used to convert color from a different
* profile to this one
*/
virtual bool isSuitableForOutput() const = 0;
/**
* @return true if this profile is suitable to use for printing
*/
virtual bool isSuitableForPrinting() const = 0;
/**
* @return true if this profile is suitable to use for display
*/
virtual bool isSuitableForDisplay() const = 0;
/**
* @return which rendering intents are supported
*/
virtual bool supportsPerceptual() const = 0;
virtual bool supportsSaturation() const = 0;
virtual bool supportsAbsolute() const = 0;
virtual bool supportsRelative() const = 0;
/**
* @return if the profile has colorants.
*/
virtual bool hasColorants() const = 0;
/**
* @return a qvector <double>(9) with the RGB colorants in XYZ
*/
virtual QVector <qreal> getColorantsXYZ() const = 0;
/**
* @return a qvector <double>(9) with the RGB colorants in xyY
*/
virtual QVector <qreal> getColorantsxyY() const = 0;
/**
* @return a qvector <double>(3) with the whitepoint in XYZ
*/
virtual QVector <qreal> getWhitePointXYZ() const = 0;
/**
* @return a qvector <double>(3) with the whitepoint in xyY
*/
virtual QVector <qreal> getWhitePointxyY() const = 0;
/**
* @return estimated gamma for RGB and Grayscale profiles
*/
virtual QVector <qreal> getEstimatedTRC() const = 0;
/**
* @return if the profile has a TRC(required for linearisation).
*/
virtual bool hasTRC() const = 0;
/**
* Linearizes first 3 values of QVector, leaving other values unchanged.
* Returns the same QVector if it is not possible to linearize.
*/
virtual void linearizeFloatValue(QVector <qreal> & Value) const = 0;
/**
* Delinearizes first 3 values of QVector, leaving other values unchanged.
* Returns the same QVector if it is not possible to delinearize.
* Effectively undoes LinearizeFloatValue.
*/
virtual void delinearizeFloatValue(QVector <qreal> & Value) const = 0;
/**
* More imprecise versions of the above(limited to 16bit, and can't
* delinearize above 1.0.) Use this for filters and images.
*/
virtual void linearizeFloatValueFast(QVector <qreal> & Value) const = 0;
virtual void delinearizeFloatValueFast(QVector <qreal> & Value) const = 0;
+
+ virtual QByteArray uniqueId() const = 0;
virtual bool operator==(const KoColorProfile&) const = 0;
/**
* @return an array with the raw data of the profile
*/
virtual QByteArray rawData() const {
return QByteArray();
}
protected:
/**
* Allows to define the name of this profile.
*/
void setName(const QString &name);
/**
* Allows to set the information string of that profile.
*/
void setInfo(const QString &info);
/**
* Allows to set the manufacturer string of that profile.
*/
void setManufacturer(const QString &manufacturer);
/**
* Allows to set the copyright string of that profile.
*/
void setCopyright(const QString &copyright);
private:
struct Private;
Private* const d;
};
#endif
diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h
index e8a24ea9c6..74878b52d9 100644
--- a/libs/pigment/KoColorSpace.h
+++ b/libs/pigment/KoColorSpace.h
@@ -1,641 +1,641 @@
/*
* 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> colorants() 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.
- virtual QList<KoChannelInfo *> channels() const;
+ QList<KoChannelInfo *> channels() const;
/**
* The total number of channels for a single pixel in this color model
*/
virtual quint32 channelCount() 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 intance "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.
*/
virtual 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
*/
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
* @param dst
* @param dstColorSpace the colorspace to which we go to.
* @param proofingSpace the proofing space.
* @param numPixels the amount of pixels.
* @param renderingIntent the rendering intent used for rendering.
* @param proofingIntent the intent used for proofing.
* @param conversionFlags the conversion flags.
* @param gamutWarning the data() of a KoColor.
* @param adaptationState the state of adaptation, only affects absolute colorimetric.
* @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 different;
* 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 param 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
*
*/
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
*
* 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
*
* @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)
{
dbg.nospace() << cs->name() << " (" << cs->colorModelId().id() << "," << cs->colorDepthId().id() << " )";
return dbg.space();
}
#endif // KOCOLORSPACE_H
diff --git a/libs/pigment/KoColorSpaceEngine.h b/libs/pigment/KoColorSpaceEngine.h
index 115a8a667c..3c67d9c5de 100644
--- a/libs/pigment/KoColorSpaceEngine.h
+++ b/libs/pigment/KoColorSpaceEngine.h
@@ -1,62 +1,65 @@
/*
* Copyright (c) 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.
*/
#ifndef _KO_COLOR_SPACE_ENGINE_H_
#define _KO_COLOR_SPACE_ENGINE_H_
#include <KoColorConversionTransformationAbstractFactory.h>
#include <KoGenericRegistry.h>
+class KoColorProfile;
+
/**
* A KoColorSpaceEngine is a class use to create color conversion
* transformation between color spaces, for which all profiles can
* output to all profiles.
*
* Typically, when you have an ICC color space and color profile, you
* can convert to any other ICC color space and color profile. While
* creating a KoColorTransformationFactory for each of this transformation
* is possible, the number of links will make the Color Conversion explode
* System. KoColorSpaceEngine provides a virtual node in the Color
* Conversion System that can convert to any other node supported by the
* engine.
*/
class KRITAPIGMENT_EXPORT KoColorSpaceEngine : public KoColorConversionTransformationAbstractFactory
{
public:
KoColorSpaceEngine(const QString& id, const QString& name);
virtual ~KoColorSpaceEngine();
const QString& id() const;
const QString& name() const;
- virtual void addProfile(const QString &filename) = 0;
+ virtual const KoColorProfile* addProfile(const QString &filename) = 0;
+ virtual const KoColorProfile* addProfile(const QByteArray &data) = 0;
virtual void removeProfile(const QString &filename) = 0;
private:
struct Private;
Private* const d;
};
class KRITAPIGMENT_EXPORT KoColorSpaceEngineRegistry : public KoGenericRegistry< KoColorSpaceEngine* >
{
public:
KoColorSpaceEngineRegistry();
~KoColorSpaceEngineRegistry();
static KoColorSpaceEngineRegistry* instance();
};
#endif
diff --git a/libs/pigment/KoColorSpaceRegistry.cpp b/libs/pigment/KoColorSpaceRegistry.cpp
index 3e942c975d..eac3d718a7 100644
--- a/libs/pigment/KoColorSpaceRegistry.cpp
+++ b/libs/pigment/KoColorSpaceRegistry.cpp
@@ -1,670 +1,766 @@
/*
* Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004,2010 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; 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 "KoColorSpaceRegistry.h"
#include <QHash>
#include <QReadWriteLock>
#include <QStringList>
#include <QDir>
#include <QGlobalStatic>
#include "KoPluginLoader.h"
#include "KoGenericRegistry.h"
#include "DebugPigment.h"
#include "KoBasicHistogramProducers.h"
#include "KoColorSpace.h"
#include "KoColorProfile.h"
#include "KoColorConversionCache.h"
#include "KoColorConversionSystem.h"
+#include <KoConfig.h>
+#ifdef HAVE_OPENEXR
+#include <half.h>
+#include "colorspaces/KoAlphaF16ColorSpace.h"
+#endif
+
#include "colorspaces/KoAlphaColorSpace.h"
+#include "colorspaces/KoAlphaU16ColorSpace.h"
+#include "colorspaces/KoAlphaF32ColorSpace.h"
#include "colorspaces/KoLabColorSpace.h"
#include "colorspaces/KoRgbU16ColorSpace.h"
#include "colorspaces/KoRgbU8ColorSpace.h"
#include "colorspaces/KoSimpleColorSpaceEngine.h"
#include "KoColorSpace_p.h"
Q_GLOBAL_STATIC(KoColorSpaceRegistry, s_instance)
struct Q_DECL_HIDDEN KoColorSpaceRegistry::Private {
KoGenericRegistry<KoColorSpaceFactory *> colorSpaceFactoryRegistry;
QList<KoColorSpaceFactory *> localFactories;
QHash<QString, KoColorProfile * > profileMap;
+ QHash<QByteArray, KoColorProfile * > profileUniqueIdMap;
QHash<QString, QString> profileAlias;
- QHash<QString, const KoColorSpace * > csMap;
+ QHash<QString, const KoColorSpace *> csMap;
KoColorConversionSystem *colorConversionSystem;
KoColorConversionCache* colorConversionCache;
- KoColorSpaceFactory* alphaCSF;
const KoColorSpace *rgbU8sRGB;
const KoColorSpace *lab16sLAB;
const KoColorSpace *alphaCs;
+ const KoColorSpace *alphaU16Cs;
+#ifdef HAVE_OPENEXR
+ const KoColorSpace *alphaF16Cs;
+#endif
+ const KoColorSpace *alphaF32Cs;
QReadWriteLock registrylock;
+
+ void populateUniqueIdMap();
};
KoColorSpaceRegistry* KoColorSpaceRegistry::instance()
{
if (!s_instance.exists()) {
s_instance->init();
}
return s_instance;
}
void KoColorSpaceRegistry::init()
{
d->rgbU8sRGB = 0;
d->lab16sLAB = 0;
d->alphaCs = 0;
+ d->alphaU16Cs = 0;
+#ifdef HAVE_OPENEXR
+ d->alphaF16Cs = 0;
+#endif
+ d->alphaF32Cs = 0;
d->colorConversionSystem = new KoColorConversionSystem;
d->colorConversionCache = new KoColorConversionCache;
KoColorSpaceEngineRegistry::instance()->add(new KoSimpleColorSpaceEngine());
addProfile(new KoDummyColorProfile);
// Create the built-in colorspaces
d->localFactories << new KoLabColorSpaceFactory()
<< new KoRgbU8ColorSpaceFactory()
<< new KoRgbU16ColorSpaceFactory();
Q_FOREACH (KoColorSpaceFactory *factory, d->localFactories) {
add(factory);
}
d->alphaCs = new KoAlphaColorSpace();
d->alphaCs->d->deletability = OwnedByRegistryRegistryDeletes;
+ d->alphaU16Cs = new KoAlphaU16ColorSpace();
+ d->alphaU16Cs->d->deletability = OwnedByRegistryRegistryDeletes;
+
+ d->alphaF16Cs = new KoAlphaF16ColorSpace();
+ d->alphaF16Cs->d->deletability = OwnedByRegistryRegistryDeletes;
+
+ d->alphaF32Cs = new KoAlphaF32ColorSpace();
+ d->alphaF32Cs->d->deletability = OwnedByRegistryRegistryDeletes;
+
+
KoPluginLoader::PluginsConfig config;
config.whiteList = "ColorSpacePlugins";
config.blacklist = "ColorSpacePluginsDisabled";
config.group = "calligra";
KoPluginLoader::instance()->load("Calligra/ColorSpace", "[X-Pigment-PluginVersion] == 28", config);
KoPluginLoader::PluginsConfig configExtensions;
configExtensions.whiteList = "ColorSpaceExtensionsPlugins";
configExtensions.blacklist = "ColorSpaceExtensionsPluginsDisabled";
configExtensions.group = "calligra";
KoPluginLoader::instance()->load("Calligra/ColorSpaceExtension", "[X-Pigment-PluginVersion] == 28", configExtensions);
dbgPigment << "Loaded the following colorspaces:";
Q_FOREACH (const KoID& id, listKeys()) {
dbgPigment << "\t" << id.id() << "," << id.name();
}
}
KoColorSpaceRegistry::KoColorSpaceRegistry() : d(new Private())
{
d->colorConversionSystem = 0;
d->colorConversionCache = 0;
}
KoColorSpaceRegistry::~KoColorSpaceRegistry()
{
// Just leak on exit... It's faster.
// delete d->colorConversionSystem;
// Q_FOREACH (KoColorProfile* profile, d->profileMap) {
// delete profile;
// }
// d->profileMap.clear();
// Q_FOREACH (const KoColorSpace * cs, d->csMap) {
// cs->d->deletability = OwnedByRegistryRegistryDeletes;
// }
// d->csMap.clear();
// // deleting colorspaces calls a function in the cache
// delete d->colorConversionCache;
// d->colorConversionCache = 0;
// // Delete the colorspace factories
// qDeleteAll(d->localFactories);
-// delete d->alphaCSF;
-
delete d;
}
void KoColorSpaceRegistry::add(KoColorSpaceFactory* item)
{
{
QWriteLocker l(&d->registrylock);
d->colorSpaceFactoryRegistry.add(item);
}
d->colorConversionSystem->insertColorSpace(item);
}
void KoColorSpaceRegistry::remove(KoColorSpaceFactory* item)
{
d->registrylock.lockForRead();
QList<QString> toremove;
Q_FOREACH (const KoColorSpace * cs, d->csMap) {
if (cs->id() == item->id()) {
toremove.push_back(idsToCacheName(cs->id(), cs->profile()->name()));
cs->d->deletability = OwnedByRegistryRegistryDeletes;
}
}
d->registrylock.unlock();
d->registrylock.lockForWrite();
Q_FOREACH (const QString& id, toremove) {
d->csMap.remove(id);
// TODO: should not it delete the color space when removing it from the map ?
}
d->colorSpaceFactoryRegistry.remove(item->id());
d->registrylock.unlock();
}
void KoColorSpaceRegistry::addProfileAlias(const QString& name, const QString& to)
{
QWriteLocker l(&d->registrylock);
d->profileAlias[name] = to;
}
QString KoColorSpaceRegistry::profileAlias(const QString& _name) const
{
QReadLocker l(&d->registrylock);
return d->profileAlias.value(_name, _name);
}
const KoColorProfile * KoColorSpaceRegistry::profileByName(const QString & _name) const
{
QReadLocker l(&d->registrylock);
return d->profileMap.value( profileAlias(_name), 0);
}
+void KoColorSpaceRegistry::Private::populateUniqueIdMap()
+{
+ QWriteLocker l(&registrylock);
+ profileUniqueIdMap.clear();
+
+ for (auto it = profileMap.constBegin();
+ it != profileMap.constEnd();
+ ++it) {
+
+ KoColorProfile *profile = it.value();
+ QByteArray id = profile->uniqueId();
+
+ if (!id.isEmpty()) {
+ profileUniqueIdMap.insert(id, profile);
+ }
+ }
+}
+
+const KoColorProfile * KoColorSpaceRegistry::profileByUniqueId(const QByteArray &id) const
+{
+ {
+ QReadLocker l(&d->registrylock);
+ if (d->profileUniqueIdMap.isEmpty()) {
+ l.unlock();
+ d->populateUniqueIdMap();
+ l.relock();
+ }
+ return d->profileUniqueIdMap.value(id, 0);
+ }
+}
+
+
QList<const KoColorProfile *> KoColorSpaceRegistry::profilesFor(const QString &id) const
{
return profilesFor(d->colorSpaceFactoryRegistry.value(id));
}
const KoColorSpace * KoColorSpaceRegistry::colorSpace(const KoID &csID, const QString & profileName)
{
return colorSpace(csID.id(), profileName);
}
const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile)
{
return colorSpace(colorSpaceId(colorModelId, colorDepthId), profile);
}
const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName)
{
return colorSpace(colorSpaceId(colorModelId, colorDepthId), profileName);
}
QList<const KoColorProfile *> KoColorSpaceRegistry::profilesFor(const KoColorSpaceFactory * csf) const
{
QReadLocker l(&d->registrylock);
QList<const KoColorProfile *> profiles;
if (csf == 0)
return profiles;
QHash<QString, KoColorProfile * >::Iterator it;
for (it = d->profileMap.begin(); it != d->profileMap.end(); ++it) {
KoColorProfile * profile = it.value();
if (csf->profileIsCompatible(profile)) {
Q_ASSERT(profile);
// if (profile->colorSpaceSignature() == csf->colorSpaceSignature()) {
profiles.push_back(profile);
}
}
return profiles;
}
QList<const KoColorSpaceFactory*> KoColorSpaceRegistry::colorSpacesFor(const KoColorProfile* _profile) const
{
QReadLocker l(&d->registrylock);
QList<const KoColorSpaceFactory*> csfs;
Q_FOREACH (KoColorSpaceFactory* csf, d->colorSpaceFactoryRegistry.values()) {
if (csf->profileIsCompatible(_profile)) {
csfs.push_back(csf);
}
}
return csfs;
}
QList<const KoColorProfile *> KoColorSpaceRegistry::profilesFor(const KoID& id) const
{
return profilesFor(id.id());
}
void KoColorSpaceRegistry::addProfileToMap(KoColorProfile *p)
{
Q_ASSERT(p);
if (p->valid()) {
d->profileMap[p->name()] = p;
+ if (!d->profileUniqueIdMap.isEmpty()) {
+ d->profileUniqueIdMap.insert(p->uniqueId(), p);
+ }
}
}
void KoColorSpaceRegistry::addProfile(KoColorProfile *p)
{
Q_ASSERT(p);
if (p->valid()) {
- d->profileMap[p->name()] = p;
+ addProfileToMap(p);
d->colorConversionSystem->insertColorProfile(p);
}
}
void KoColorSpaceRegistry::addProfile(const KoColorProfile* profile)
{
addProfile(profile->clone());
}
void KoColorSpaceRegistry::removeProfile(KoColorProfile* profile)
{
d->profileMap.remove(profile->name());
+ if (!d->profileUniqueIdMap.isEmpty()) {
+ d->profileUniqueIdMap.remove(profile->uniqueId());
+ }
}
const KoColorSpace* KoColorSpaceRegistry::getCachedColorSpace(const QString & csID, const QString & profileName) const
{
auto it = d->csMap.find(idsToCacheName(csID, profileName));
if (it != d->csMap.end()) {
return it.value();
}
return 0;
}
QString KoColorSpaceRegistry::idsToCacheName(const QString & csID, const QString & profileName) const
{
return csID + "<comb>" + profileName;
}
const KoColorSpaceFactory* KoColorSpaceRegistry::colorSpaceFactory(const QString &colorSpaceId) const
{
QReadLocker l(&d->registrylock);
return d->colorSpaceFactoryRegistry.get(colorSpaceId);
}
const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString &csID, const QString &pName)
{
QString profileName = pName;
if (profileName.isEmpty()) {
QReadLocker l(&d->registrylock);
KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(csID);
if (!csf) {
dbgPigmentCSRegistry << "Unknown color space type : " << csID;
return 0;
}
profileName = csf->defaultProfile();
}
if (profileName.isEmpty()) {
return 0;
}
const KoColorSpace *cs = 0;
{
QReadLocker l(&d->registrylock);
cs = getCachedColorSpace(csID, profileName);
}
if (!cs) {
KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(csID);
if (!csf) {
dbgPigmentCSRegistry << "Unknown color space type :" << csf;
return 0;
}
// last attempt at getting a profile, sometimes the default profile, like adobe cmyk isn't available.
const KoColorProfile *p = profileByName(profileName);
if (!p) {
dbgPigmentCSRegistry << "Profile not found :" << profileName;
/**
* If the requested profile is not available, try fetching the
* default one
*/
profileName = csf->defaultProfile();
p = profileByName(profileName);
/**
* If there is no luck, try to fetch the first one
*/
if (!p) {
QList<const KoColorProfile *> profiles = profilesFor(csID);
if (!profiles.isEmpty()) {
p = profiles[0];
Q_ASSERT(p);
}
}
}
// We did our best, but still have no profile: and since csf->grabColorSpace
// needs the profile to find the colorspace, we have to give up.
if (!p) {
return 0;
}
profileName = p->name();
QWriteLocker l(&d->registrylock);
/*
* We need to check again here, a thread requesting the same colorspace could've added it
* already, in between the read unlock and write lock.
* TODO: We also potentially changed profileName content, which means we maybe are going to
* create a colorspace that's actually in the space registry cache, but currently this might
* not be an issue because the colorspace should be cached also by the factory, so it won't
* create a new instance. That being said, having two caches with the same stuff doesn't make
* much sense.
*/
cs = getCachedColorSpace(csID, profileName);
if (!cs) {
cs = csf->grabColorSpace(p);
if (!cs) {
dbgPigmentCSRegistry << "Unable to create color space";
return 0;
}
dbgPigmentCSRegistry << "colorspace count: " << d->csMap.count()
<< ", adding name: " << idsToCacheName(cs->id(), cs->profile()->name())
<< "\n\tcsID" << csID
<< "\n\tprofileName" << profileName
<< "\n\tcs->id()" << cs->id()
<< "\n\tcs->profile()->name()" << cs->profile()->name()
<< "\n\tpName" << pName;
Q_ASSERT(cs->id() == csID);
Q_ASSERT(cs->profile()->name() == profileName);
d->csMap[idsToCacheName(cs->id(), cs->profile()->name())] = cs;
cs->d->deletability = OwnedByRegistryDoNotDelete;
}
}
else {
Q_ASSERT(cs->id() == csID);
Q_ASSERT(cs->profile()->name() == profileName);
}
return cs;
}
const KoColorSpace * KoColorSpaceRegistry::colorSpace(const QString &csID, const KoColorProfile *profile)
{
if (csID.isEmpty()) {
return 0;
}
if (profile) {
d->registrylock.lockForRead();
const KoColorSpace *cs = getCachedColorSpace(csID, profile->name());
d->registrylock.unlock();
if (!d->profileMap.contains(profile->name())) {
addProfile(profile);
}
if (!cs) {
// The profile was not stored and thus not the combination either
d->registrylock.lockForRead();
KoColorSpaceFactory *csf = d->colorSpaceFactoryRegistry.value(csID);
d->registrylock.unlock();
if (!csf) {
dbgPigmentCSRegistry << "Unknown color space type :" << csf;
return 0;
}
if (!csf->profileIsCompatible(profile ) ) {
return 0;
}
QWriteLocker l(&d->registrylock);
// Check again, anything could've happened between the unlock and the write lock
cs = getCachedColorSpace(csID, profile->name());
if (!cs) {
cs = csf->grabColorSpace(profile);
if (!cs)
return 0;
QString name = idsToCacheName(csID, profile->name());
d->csMap[name] = cs;
cs->d->deletability = OwnedByRegistryDoNotDelete;
dbgPigmentCSRegistry << "colorspace count: " << d->csMap.count() << ", adding name: " << name;
}
}
return cs;
} else {
return colorSpace(csID);
}
}
const KoColorSpace * KoColorSpaceRegistry::alpha8()
{
if (!d->alphaCs) {
d->alphaCs = colorSpace(KoAlphaColorSpace::colorSpaceId());
}
Q_ASSERT(d->alphaCs);
return d->alphaCs;
}
+const KoColorSpace * KoColorSpaceRegistry::alpha16()
+{
+ if (!d->alphaU16Cs) {
+ d->alphaU16Cs = colorSpace(KoAlphaU16ColorSpace::colorSpaceId());
+ }
+ Q_ASSERT(d->alphaU16Cs);
+ return d->alphaU16Cs;
+}
+
+#ifdef HAVE_OPENEXR
+const KoColorSpace * KoColorSpaceRegistry::alpha16f()
+{
+ if (!d->alphaF16Cs) {
+ d->alphaF16Cs = colorSpace(KoAlphaF16ColorSpace::colorSpaceId());
+ }
+ Q_ASSERT(d->alphaF16Cs);
+ return d->alphaF16Cs;
+}
+#endif
+
+const KoColorSpace * KoColorSpaceRegistry::alpha32f()
+{
+ if (!d->alphaF32Cs) {
+ d->alphaF32Cs = colorSpace(KoAlphaF32ColorSpace::colorSpaceId());
+ }
+ Q_ASSERT(d->alphaF32Cs);
+ return d->alphaF32Cs;
+}
+
+
const KoColorSpace * KoColorSpaceRegistry::rgb8(const QString &profileName)
{
if (profileName.isEmpty()) {
if (!d->rgbU8sRGB) {
d->rgbU8sRGB = colorSpace(KoRgbU8ColorSpace::colorSpaceId());
}
Q_ASSERT(d->rgbU8sRGB);
return d->rgbU8sRGB;
}
return colorSpace(KoRgbU8ColorSpace::colorSpaceId(), profileName);
}
const KoColorSpace * KoColorSpaceRegistry::rgb8(const KoColorProfile * profile)
{
if (profile == 0) {
if (!d->rgbU8sRGB) {
d->rgbU8sRGB = colorSpace(KoRgbU8ColorSpace::colorSpaceId());
}
Q_ASSERT(d->rgbU8sRGB);
return d->rgbU8sRGB;
}
return colorSpace(KoRgbU8ColorSpace::colorSpaceId(), profile);
}
const KoColorSpace * KoColorSpaceRegistry::rgb16(const QString &profileName)
{
return colorSpace(KoRgbU16ColorSpace::colorSpaceId(), profileName);
}
const KoColorSpace * KoColorSpaceRegistry::rgb16(const KoColorProfile * profile)
{
return colorSpace(KoRgbU16ColorSpace::colorSpaceId(), profile);
}
const KoColorSpace * KoColorSpaceRegistry::lab16(const QString &profileName)
{
if (profileName.isEmpty()) {
if (!d->lab16sLAB) {
d->lab16sLAB = colorSpace(KoLabColorSpace::colorSpaceId(), profileName);
}
return d->lab16sLAB;
}
return colorSpace(KoLabColorSpace::colorSpaceId(), profileName);
}
const KoColorSpace * KoColorSpaceRegistry::lab16(const KoColorProfile * profile)
{
if (profile == 0) {
if (!d->lab16sLAB) {
d->lab16sLAB = colorSpace(KoLabColorSpace::colorSpaceId(), profile);
}
Q_ASSERT(d->lab16sLAB);
return d->lab16sLAB;
}
return colorSpace(KoLabColorSpace::colorSpaceId(), profile);
}
QList<KoID> KoColorSpaceRegistry::colorModelsList(ColorSpaceListVisibility option) const
{
QReadLocker l(&d->registrylock);
QList<KoID> ids;
QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
Q_FOREACH (KoColorSpaceFactory* factory, factories) {
if (!ids.contains(factory->colorModelId())
&& (option == AllColorSpaces || factory->userVisible())) {
ids << factory->colorModelId();
}
}
return ids;
}
QList<KoID> KoColorSpaceRegistry::colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const
{
return colorDepthList(colorModelId.id(), option);
}
QList<KoID> KoColorSpaceRegistry::colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const
{
QReadLocker l(&d->registrylock);
QList<KoID> ids;
QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
Q_FOREACH (KoColorSpaceFactory* factory, factories) {
if (!ids.contains(KoID(factory->colorDepthId()))
&& factory->colorModelId().id() == colorModelId
&& (option == AllColorSpaces || factory->userVisible())) {
ids << factory->colorDepthId();
}
}
return ids;
}
QString KoColorSpaceRegistry::colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const
{
QReadLocker l(&d->registrylock);
QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
Q_FOREACH (KoColorSpaceFactory* factory, factories) {
if (factory->colorModelId().id() == colorModelId && factory->colorDepthId().id() == colorDepthId) {
return factory->id();
}
}
return "";
}
QString KoColorSpaceRegistry::colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const
{
return colorSpaceId(colorModelId.id(), colorDepthId.id());
}
KoID KoColorSpaceRegistry::colorSpaceColorModelId(const QString & _colorSpaceId) const
{
QReadLocker l(&d->registrylock);
KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId);
if (factory) {
return factory->colorModelId();
} else {
return KoID();
}
}
KoID KoColorSpaceRegistry::colorSpaceColorDepthId(const QString & _colorSpaceId) const
{
QReadLocker l(&d->registrylock);
KoColorSpaceFactory* factory = d->colorSpaceFactoryRegistry.get(_colorSpaceId);
if (factory) {
return factory->colorDepthId();
} else {
return KoID();
}
}
const KoColorConversionSystem* KoColorSpaceRegistry::colorConversionSystem() const
{
return d->colorConversionSystem;
}
KoColorConversionCache* KoColorSpaceRegistry::colorConversionCache() const
{
return d->colorConversionCache;
}
const KoColorSpace* KoColorSpaceRegistry::permanentColorspace(const KoColorSpace* _colorSpace)
{
if (_colorSpace->d->deletability != NotOwnedByRegistry) {
return _colorSpace;
} else if (*_colorSpace == *d->alphaCs) {
return d->alphaCs;
} else {
const KoColorSpace* cs = colorSpace(_colorSpace->id(), _colorSpace->profile());
Q_ASSERT(cs);
Q_ASSERT(*cs == *_colorSpace);
return cs;
}
}
QList<KoID> KoColorSpaceRegistry::listKeys() const
{
QReadLocker l(&d->registrylock);
QList<KoID> answer;
Q_FOREACH (const QString& key, d->colorSpaceFactoryRegistry.keys()) {
answer.append(KoID(key, d->colorSpaceFactoryRegistry.get(key)->name()));
}
return answer;
}
const KoColorProfile* KoColorSpaceRegistry::createColorProfile(const QString& colorModelId, const QString& colorDepthId, const QByteArray& rawData)
{
QReadLocker l(&d->registrylock);
KoColorSpaceFactory* factory_ = d->colorSpaceFactoryRegistry.get(colorSpaceId(colorModelId, colorDepthId));
return factory_->colorProfile(rawData);
}
QList<const KoColorSpace*> KoColorSpaceRegistry::allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection)
{
QList<const KoColorSpace*> colorSpaces;
d->registrylock.lockForRead();
QList<KoColorSpaceFactory*> factories = d->colorSpaceFactoryRegistry.values();
d->registrylock.unlock();
Q_FOREACH (KoColorSpaceFactory* factory, factories) {
// Don't test with ycbcr for now, since we don't have a default profile for it.
if (factory->colorModelId().id().startsWith("Y")) continue;
if (visibility == AllColorSpaces || factory->userVisible()) {
if (pSelection == OnlyDefaultProfile) {
const KoColorSpace *cs = colorSpace(factory->id());
if (cs) {
colorSpaces.append(cs);
}
else {
warnPigment << "Could not create colorspace for id" << factory->id() << "since there is no working default profile";
}
} else {
QList<const KoColorProfile*> profiles = KoColorSpaceRegistry::instance()->profilesFor(factory->id());
Q_FOREACH (const KoColorProfile * profile, profiles) {
const KoColorSpace *cs = colorSpace(factory->id(), profile);
if (cs) {
colorSpaces.append(cs);
}
else {
warnPigment << "Could not create colorspace for id" << factory->id() << "and profile" << profile->name();
}
}
}
}
}
return colorSpaces;
}
diff --git a/libs/pigment/KoColorSpaceRegistry.h b/libs/pigment/KoColorSpaceRegistry.h
index e0fce3a33c..1504102427 100644
--- a/libs/pigment/KoColorSpaceRegistry.h
+++ b/libs/pigment/KoColorSpaceRegistry.h
@@ -1,355 +1,371 @@
/*
* Copyright (c) 2003 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004,2010 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; 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 KOCOLORSPACEREGISTRY_H
#define KOCOLORSPACEREGISTRY_H
#include <QObject>
#include <QList>
#include <QString>
#include "kritapigment_export.h"
#include <KoGenericRegistry.h>
#include <KoColorSpace.h>
#include <KoColorSpaceFactory.h>
class KoColorProfile;
class KoColorConversionSystem;
class KoColorConversionCache;
/**
* The registry for colorspaces and profiles.
* This class contains:
* - a registry of colorspace instantiated with specific profiles.
* - a registry of singleton colorspace factories.
* - a registry of icc profiles
*/
class KRITAPIGMENT_EXPORT KoColorSpaceRegistry
{
public:
KoColorSpaceRegistry();
enum ColorSpaceListVisibility {
OnlyUserVisible = 1, ///< Only user visible color space
AllColorSpaces = 4 ///< All color space even those not visible to the user
};
enum ColorSpaceListProfilesSelection {
OnlyDefaultProfile = 1, ///< Only add the default profile
AllProfiles = 4 ///< Add all profiles
};
/**
* Return an instance of the KoColorSpaceRegistry
* Creates an instance if that has never happened before and returns the singleton instance.
*/
static KoColorSpaceRegistry * instance();
virtual ~KoColorSpaceRegistry();
public:
/**
* add a color space to the registry
* @param item the color space factory to add
*/
void add(KoColorSpaceFactory* item);
/**
* Remove a color space factory from the registry. Note that it is the
* responsibility of the caller to ensure that the colorspaces are not
* used anymore.
*/
void remove(KoColorSpaceFactory* item);
/**
* Add a profile to the profile map but do not add it to the
* color conversion system yet.
* @param profile the new profile to be registered.
*/
void addProfileToMap(KoColorProfile *p);
/**
* register the profile with the color space registry
* @param profile the new profile to be registered so it can be combined with
* colorspaces.
*/
void addProfile(KoColorProfile* profile);
void addProfile(const KoColorProfile* profile); // TODO why ?
void removeProfile(KoColorProfile* profile);
-
+
/**
* Create an alias to a profile with a different name. Then @ref profileByName
* will return the profile @p to when passed @p name as a parameter.
*/
void addProfileAlias(const QString& name, const QString& to);
-
+
/**
* @return the profile alias, or name if not aliased
*/
QString profileAlias(const QString& name) const;
/**
* create a profile of the specified type.
*/
- const KoColorProfile* createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData);
+ const KoColorProfile *createColorProfile(const QString & colorModelId, const QString & colorDepthId, const QByteArray& rawData);
/**
* Return a profile by its given name, or 0 if none registered.
* @return a profile by its given name, or 0 if none registered.
* @param name the product name as set on the profile.
* @see addProfile()
* @see KoColorProfile::productName()
*/
- const KoColorProfile * profileByName(const QString & name) const ;
+ const KoColorProfile * profileByName(const QString & name) const ;
+
+
+ /**
+ * Returns a profile by its unique id stored/calculated in the header.
+ * The first call to this function might take long, because the map is
+ * created on the first use only (atm used by SVG only)
+ * @param id unique ProfileID of the profile (MD5 sum of its header)
+ * @return the profile or 0 if not found
+ */
+ const KoColorProfile *profileByUniqueId(const QByteArray &id) const;
/**
* Return the list of profiles for the argument colorspacefactory.
* Profiles will not work with any color space, you can query which profiles
* that are registered with this registry can be used in combination with the
* argument factory.
* @param factory the factory with which all the returned profiles will work.
* @return a list of profiles for the factory
*/
QList<const KoColorProfile *> profilesFor(const KoColorSpaceFactory * factory) const;
/**
* Return the list of profiles for a colorspace with the argument id.
* Profiles will not work with any color space, you can query which profiles
* that are registered with this registry can be used in combination with the
* argument factory.
* @param id the colorspace-id with which all the returned profiles will work.
* @return a list of profiles for the factory
*/
QList<const KoColorProfile *> profilesFor(const KoID& id) const;
/**
* @return a list of color spaces compatible with this profile
*/
QList<const KoColorSpaceFactory*> colorSpacesFor(const KoColorProfile* _profile) const;
/**
* Return the list of profiles for a colorspace with the argument id.
* Profiles will not work with any color space, you can query which profiles
* that are registered with this registry can be used in combination with the
* argument factory.
* @param colorSpaceId the colorspace-id with which all the returned profiles will work.
* @return a list of profiles for the factory
*/
QList<const KoColorProfile *> profilesFor(const QString& id) const;
const KoColorSpaceFactory* colorSpaceFactory(const QString &colorSpaceId) const;
private:
/**
* Return a colorspace that works with the parameter profile.
* @param csID the ID of the colorspace that you want to have returned
* @param profileName the name of the KoColorProfile to be combined with the colorspace
* @return the wanted colorspace, or 0 when the cs and profile can not be combined.
*/
const KoColorSpace * colorSpace(const KoID &csID, const QString & profileName);
/**
* Return a colorspace that works with the parameter profile.
* @param colorSpaceId the ID string of the colorspace that you want to have returned
* @param profile the profile be combined with the colorspace
* @return the wanted colorspace, or 0 when the cs and profile can not be combined.
*/
const KoColorSpace * colorSpace(const QString &colorSpaceId, const KoColorProfile *profile);
/**
* Return a colorspace that works with the parameter profile.
* @param profileName the name of the KoColorProfile to be combined with the colorspace
* @return the wanted colorspace, or 0 when the cs and profile can not be combined.
*/
const KoColorSpace * colorSpace(const QString &colorSpaceId, const QString &profileName = QString());
public:
/**
* Return a colorspace that works with the parameter profile.
* @param colorSpaceId the ID string of the colorspace that you want to have returned
* @param profile the profile be combined with the colorspace
* @return the wanted colorspace, or 0 when the cs and profile can not be combined.
*/
const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const KoColorProfile *profile);
/**
* Return a colorspace that works with the parameter profile.
* @param profileName the name of the KoColorProfile to be combined with the colorspace
* @return the wanted colorspace, or 0 when the cs and profile can not be combined.
*/
const KoColorSpace * colorSpace(const QString & colorModelId, const QString & colorDepthId, const QString &profileName);
/**
* Return the id of the colorspace that have the defined colorModelId with colorDepthId.
* @param colorModelId id of the color model
* @param colorDepthId id of the color depth
* @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids
*/
QString colorSpaceId(const QString & colorModelId, const QString & colorDepthId) const;
/**
* It's a convenient function that behave like the above.
* Return the id of the colorspace that have the defined colorModelId with colorDepthId.
* @param colorModelId id of the color model
* @param colorDepthId id of the color depth
* @return the id of the wanted colorspace, or "" if no colorspace correspond to those ids
*/
QString colorSpaceId(const KoID& colorModelId, const KoID& colorDepthId) const;
/**
* @return a the identifiant of the color model for the given color space id.
*
* This function is a compatibility function used to get the color space from
* all kra files.
*/
KoID colorSpaceColorModelId(const QString & _colorSpaceId) const;
/**
* @return a the identifiant of the color depth for the given color space id.
*
* This function is a compatibility function used to get the color space from
* all kra files.
*/
KoID colorSpaceColorDepthId(const QString & _colorSpaceId) const;
/**
- * Convenience method to get the often used alpha colorspace
+ * Convenience methods to get the often used alpha colorspaces
*/
- const KoColorSpace * alpha8();
+ const KoColorSpace *alpha8();
+ const KoColorSpace *alpha16();
+#include <KoConfig.h>
+#ifdef HAVE_OPENEXR
+ const KoColorSpace *alpha16f();
+#endif
+ const KoColorSpace *alpha32f();
/**
* Convenience method to get an RGBA 8bit colorspace. If a profile is not specified,
* an sRGB profile will be used.
* @param profileName the name of an RGB color profile
* @return the wanted colorspace, or 0 if the color space and profile can not be combined.
*/
const KoColorSpace * rgb8(const QString &profileName = QString());
/**
* Convenience method to get an RGBA 8bit colorspace with the given profile.
* @param profile an RGB profile
* @return the wanted colorspace, or 0 if the color space and profile can not be combined.
*/
const KoColorSpace * rgb8(const KoColorProfile * profile);
/**
* Convenience method to get an RGBA 16bit colorspace. If a profile is not specified,
* an sRGB profile will be used.
* @param profileName the name of an RGB color profile
* @return the wanted colorspace, or 0 if the color space and profile can not be combined.
*/
const KoColorSpace * rgb16(const QString &profileName = QString());
/**
* Convenience method to get an RGBA 16bit colorspace with the given profile.
* @param profile an RGB profile
* @return the wanted colorspace, or 0 if the color space and profile can not be combined.
*/
const KoColorSpace * rgb16(const KoColorProfile * profile);
/**
* Convenience method to get an Lab 16bit colorspace. If a profile is not specified,
* an Lab profile with a D50 whitepoint will be used.
* @param profileName the name of an Lab color profile
* @return the wanted colorspace, or 0 if the color space and profile can not be combined.
*/
const KoColorSpace * lab16(const QString &profileName = QString());
/**
* Convenience method to get an Lab 16bit colorspace with the given profile.
* @param profile an Lab profile
* @return the wanted colorspace, or 0 if the color space and profile can not be combined.
*/
const KoColorSpace * lab16(const KoColorProfile * profile);
/**
* @return the list of available color models
*/
QList<KoID> colorModelsList(ColorSpaceListVisibility option) const;
/**
* @return the list of available color models for the given colorModelId
*/
QList<KoID> colorDepthList(const KoID& colorModelId, ColorSpaceListVisibility option) const;
/**
* @return the list of available color models for the given colorModelId
*/
QList<KoID> colorDepthList(const QString & colorModelId, ColorSpaceListVisibility option) const;
/**
* @return the color conversion system use by the registry and the color
* spaces to create color conversion transformation
*/
const KoColorConversionSystem* colorConversionSystem() const;
/**
* @return the cache of color conversion transformation to be use by KoColorSpace
*/
KoColorConversionCache* colorConversionCache() const;
/**
* @return a permanent colorspace owned by the registry, of the same type and profile
* as the one given in argument
*/
const KoColorSpace* permanentColorspace(const KoColorSpace* _colorSpace);
/**
* This function return a list of all the keys in KoID format by using the name() method
* on the objects stored in the registry.
*/
QList<KoID> listKeys() const;
private:
friend class KisCsConversionTest;
friend class KisIteratorTest;
friend class KisPainterTest;
friend class KisCrashFilterTest;
friend class KoColorSpacesBenchmark;
friend class TestKoColorSpaceSanity;
friend class KisActionRecorderTest;
/**
* @return a list with an instance of all color space with their default profile.
*/
QList<const KoColorSpace*> allColorSpaces(ColorSpaceListVisibility visibility, ColorSpaceListProfilesSelection pSelection);
private:
/**
* The function checks if a colorspace with a certain id and profile name can be found in the cache
* NOTE: the function doesn't take any lock but it needs to be called inside a d->registryLock
* locked either in read or write.
* @param csId The colorspace id
* @param profileName The colorspace profile name
* @retval KoColorSpace The matching colorspace
* @retval 0 Null pointer if not match
*/
const KoColorSpace* getCachedColorSpace(const QString & csId, const QString & profileName) const;
QString idsToCacheName(const QString & csId, const QString & profileName) const;
private:
KoColorSpaceRegistry(const KoColorSpaceRegistry&);
KoColorSpaceRegistry operator=(const KoColorSpaceRegistry&);
void init();
private:
struct Private;
Private * const d;
};
#endif // KOCOLORSPACEREGISTRY_H
diff --git a/libs/pigment/colorprofiles/KoDummyColorProfile.cpp b/libs/pigment/colorprofiles/KoDummyColorProfile.cpp
index 3db133f14c..ec6b08a25a 100644
--- a/libs/pigment/colorprofiles/KoDummyColorProfile.cpp
+++ b/libs/pigment/colorprofiles/KoDummyColorProfile.cpp
@@ -1,136 +1,141 @@
/*
* 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 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 "KoDummyColorProfile.h"
KoDummyColorProfile::KoDummyColorProfile()
{
setName("default");
}
KoDummyColorProfile::~KoDummyColorProfile()
{
}
KoColorProfile* KoDummyColorProfile::clone() const
{
return new KoDummyColorProfile();
}
bool KoDummyColorProfile::valid() const
{
return true;
}
float KoDummyColorProfile::version() const
{
return 0.0;
}
bool KoDummyColorProfile::isSuitableForOutput() const
{
return true;
}
bool KoDummyColorProfile::isSuitableForPrinting() const
{
return true;
}
bool KoDummyColorProfile::isSuitableForDisplay() const
{
return true;
}
bool KoDummyColorProfile::supportsPerceptual() const
{
return true;
}
bool KoDummyColorProfile::supportsSaturation() const
{
return true;
}
bool KoDummyColorProfile::supportsAbsolute() const
{
return true;
}
bool KoDummyColorProfile::supportsRelative() const
{
return true;
}
bool KoDummyColorProfile::hasColorants() const
{
return true;
}
bool KoDummyColorProfile::hasTRC() const
{
return true;
}
QVector<double> KoDummyColorProfile::getColorantsXYZ() const
{
QVector<double> d50Dummy(3);
d50Dummy<<0.34773<<0.35952<<1.0;
return d50Dummy;
}
QVector<double> KoDummyColorProfile::getColorantsxyY() const
{
QVector<double> d50Dummy(3);
d50Dummy<<0.34773<<0.35952<<1.0;
return d50Dummy;
}
QVector<double> KoDummyColorProfile::getWhitePointXYZ() const
{
QVector<double> d50Dummy(3);
d50Dummy<<0.9642<<1.0000<<0.8249;
return d50Dummy;
}
QVector<double> KoDummyColorProfile::getWhitePointxyY() const
{
QVector<double> d50Dummy(3);
d50Dummy<<0.34773<<0.35952<<1.0;
return d50Dummy;
}
QVector <double> KoDummyColorProfile::getEstimatedTRC() const
{
QVector<double> Dummy(3);
Dummy.fill(2.2);
return Dummy;
}
void KoDummyColorProfile::linearizeFloatValue(QVector <double> & ) const
{
}
void KoDummyColorProfile::delinearizeFloatValue(QVector <double> & ) const
{
}
void KoDummyColorProfile::linearizeFloatValueFast(QVector <double> & ) const
{
}
void KoDummyColorProfile::delinearizeFloatValueFast(QVector <double> & ) const
{
}
bool KoDummyColorProfile::operator==(const KoColorProfile& rhs) const
{
return dynamic_cast<const KoDummyColorProfile*>(&rhs);
}
+QByteArray KoDummyColorProfile::uniqueId() const
+{
+ return QByteArray();
+}
+
diff --git a/libs/pigment/colorprofiles/KoDummyColorProfile.h b/libs/pigment/colorprofiles/KoDummyColorProfile.h
index c8a3f71f48..76e44ea696 100644
--- a/libs/pigment/colorprofiles/KoDummyColorProfile.h
+++ b/libs/pigment/colorprofiles/KoDummyColorProfile.h
@@ -1,54 +1,55 @@
/*
* 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 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_DUMMY_COLOR_PROFILE_H_
#define _KO_DUMMY_COLOR_PROFILE_H_
#include "KoColorProfile.h"
class KoDummyColorProfile : public KoColorProfile
{
public:
KoDummyColorProfile();
virtual ~KoDummyColorProfile();
virtual KoColorProfile* clone() const;
virtual bool valid() const;
virtual float version() const;
virtual bool isSuitableForOutput() const;
virtual bool isSuitableForPrinting() const;
virtual bool isSuitableForDisplay() const;
virtual bool supportsPerceptual() const;
virtual bool supportsSaturation() const;
virtual bool supportsAbsolute() const;
virtual bool supportsRelative() const;
virtual bool hasColorants() const;
virtual bool hasTRC() const;
virtual QVector <double> getColorantsXYZ() const;
virtual QVector <double> getColorantsxyY() const;
virtual QVector <double> getWhitePointXYZ() const;
virtual QVector <double> getWhitePointxyY() const;
virtual QVector <double> getEstimatedTRC() const;
virtual void linearizeFloatValue(QVector <double> & Value) const;
virtual void delinearizeFloatValue(QVector <double> & Value) const;
virtual void linearizeFloatValueFast(QVector <double> & Value) const;
virtual void delinearizeFloatValueFast(QVector <double> & Value) const;
virtual bool operator==(const KoColorProfile&) const;
+ virtual QByteArray uniqueId() const;
};
#endif
diff --git a/libs/pigment/colorspaces/KoAlphaColorSpace.cpp b/libs/pigment/colorspaces/KoAlphaColorSpace.cpp
index da7720fb68..48e87093b3 100644
--- a/libs/pigment/colorspaces/KoAlphaColorSpace.cpp
+++ b/libs/pigment/colorspaces/KoAlphaColorSpace.cpp
@@ -1,369 +1,368 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KoAlphaColorSpace.h"
#include <limits.h>
#include <stdlib.h>
#include <QImage>
#include <QBitArray>
#include <klocalizedstring.h>
#include "KoChannelInfo.h"
#include "KoID.h"
#include "KoIntegerMaths.h"
#include "KoCompositeOpOver.h"
#include "KoCompositeOpErase.h"
#include "KoCompositeOpCopy2.h"
#include "KoCompositeOpAlphaDarken.h"
#include <colorprofiles/KoDummyColorProfile.h>
namespace
{
-const quint8 PIXEL_MASK = 0;
class CompositeClear : public KoCompositeOp
{
public:
CompositeClear(KoColorSpace * cs)
: KoCompositeOp(cs, COMPOSITE_CLEAR, i18n("Clear"), KoCompositeOp::categoryMix()) {
}
public:
using KoCompositeOp::composite;
void composite(quint8 *dst,
qint32 dststride,
const quint8 *src,
qint32 srcstride,
const quint8 *maskRowStart,
qint32 maskstride,
qint32 rows,
qint32 cols,
quint8 opacity,
const QBitArray & channelFlags) const override {
Q_UNUSED(src);
Q_UNUSED(srcstride);
Q_UNUSED(opacity);
Q_UNUSED(channelFlags);
quint8 *d;
qint32 linesize;
if (rows <= 0 || cols <= 0)
return;
if (maskRowStart == 0) {
linesize = sizeof(quint8) * cols;
d = dst;
while (rows-- > 0) {
memset(d, OPACITY_TRANSPARENT_U8, linesize);
d += dststride;
}
} else {
while (rows-- > 0) {
const quint8 *mask = maskRowStart;
d = dst;
for (qint32 i = cols; i > 0; --i, ++d) {
// If the mask tells us to completely not
// blend this pixel, continue.
if (mask != 0) {
if (mask[0] == OPACITY_TRANSPARENT_U8) {
++mask;
continue;
}
++mask;
}
// linesize is uninitialized here, so it just crashes
//memset(d, OPACITY_TRANSPARENT, linesize);
}
dst += dststride;
src += srcstride;
maskRowStart += maskstride;
}
}
}
};
class CompositeSubtract : public KoCompositeOp
{
public:
CompositeSubtract(KoColorSpace * cs)
: KoCompositeOp(cs, COMPOSITE_SUBTRACT, i18n("Subtract"), KoCompositeOp::categoryArithmetic()) {
}
public:
using KoCompositeOp::composite;
void composite(quint8 *dst,
qint32 dststride,
const quint8 *src,
qint32 srcstride,
const quint8 *maskRowStart,
qint32 maskstride,
qint32 rows,
qint32 cols,
quint8 opacity,
const QBitArray & channelFlags) const override {
Q_UNUSED(opacity);
Q_UNUSED(channelFlags);
quint8 *d;
const quint8 *s;
qint32 i;
if (rows <= 0 || cols <= 0)
return;
while (rows-- > 0) {
const quint8 *mask = maskRowStart;
d = dst;
s = src;
for (i = cols; i > 0; --i, ++d, ++s) {
// If the mask tells us to completely not
// blend this pixel, continue.
if (mask != 0) {
if (mask[0] == OPACITY_TRANSPARENT_U8) {
++mask;
continue;
}
++mask;
}
- if (d[PIXEL_MASK] <= s[PIXEL_MASK]) {
- d[PIXEL_MASK] = OPACITY_TRANSPARENT_U8;
+ if (d[0] <= s[0]) {
+ d[0] = OPACITY_TRANSPARENT_U8;
} else {
- d[PIXEL_MASK] -= s[PIXEL_MASK];
+ d[0] -= s[0];
}
}
dst += dststride;
src += srcstride;
if (maskRowStart) {
maskRowStart += maskstride;
}
}
}
};
class CompositeMultiply : public KoCompositeOp
{
public:
CompositeMultiply(KoColorSpace * cs)
: KoCompositeOp(cs, COMPOSITE_MULT, i18n("Multiply"), KoCompositeOp::categoryArithmetic()) {
}
public:
using KoCompositeOp::composite;
void composite(quint8 *dst,
qint32 dststride,
const quint8 *src,
qint32 srcstride,
const quint8 *maskRowStart,
qint32 maskstride,
qint32 rows,
qint32 cols,
quint8 opacity,
const QBitArray & channelFlags) const override {
Q_UNUSED(opacity);
Q_UNUSED(channelFlags);
quint8 *destination;
const quint8 *source;
qint32 i;
if (rows <= 0 || cols <= 0)
return;
while (rows-- > 0) {
const quint8 *mask = maskRowStart;
destination = dst;
source = src;
for (i = cols; i > 0; --i, ++destination, ++source) {
// If the mask tells us to completely not
// blend this pixel, continue.
if (mask != 0) {
if (mask[0] == OPACITY_TRANSPARENT_U8) {
++mask;
continue;
}
++mask;
}
// here comes the math
- destination[PIXEL_MASK] = KoColorSpaceMaths<quint8>::multiply(destination[PIXEL_MASK], source[PIXEL_MASK]);
+ destination[0] = KoColorSpaceMaths<quint8>::multiply(destination[0], source[0]);
}
dst += dststride;
src += srcstride;
if (maskRowStart) {
maskRowStart += maskstride;
}
}
}
};
}
KoAlphaColorSpace::KoAlphaColorSpace() :
KoColorSpaceAbstract<AlphaU8Traits>("ALPHA", i18n("Alpha mask"))
{
addChannel(new KoChannelInfo(i18n("Alpha"), 0, 0, KoChannelInfo::ALPHA, KoChannelInfo::UINT8));
m_compositeOps << new KoCompositeOpOver<AlphaU8Traits>(this)
<< new CompositeClear(this)
<< new KoCompositeOpErase<AlphaU8Traits>(this)
<< new KoCompositeOpCopy2<AlphaU8Traits>(this)
<< new CompositeSubtract(this)
<< new CompositeMultiply(this)
<< new KoCompositeOpAlphaDarken<AlphaU8Traits>(this);
Q_FOREACH (KoCompositeOp *op, m_compositeOps) {
addCompositeOp(op);
}
m_profile = new KoDummyColorProfile;
}
KoAlphaColorSpace::~KoAlphaColorSpace()
{
qDeleteAll(m_compositeOps);
delete m_profile;
m_profile = 0;
}
void KoAlphaColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const
{
- dst[PIXEL_MASK] = c.alpha();
+ dst[0] = c.alpha();
}
void KoAlphaColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const
{
- c->setRgba(qRgba(255, 255, 255, src[PIXEL_MASK]));
+ c->setRgba(qRgba(255, 255, 255, src[0]));
}
quint8 KoAlphaColorSpace::difference(const quint8 *src1, const quint8 *src2) const
{
// Arithmetic operands smaller than int are converted to int automatically
- return qAbs(src2[PIXEL_MASK] - src1[PIXEL_MASK]);
+ return qAbs(src2[0] - src1[0]);
}
quint8 KoAlphaColorSpace::differenceA(const quint8 *src1, const quint8 *src2) const
{
return difference(src1, src2);
}
QString KoAlphaColorSpace::channelValueText(const quint8 *pixel, quint32 channelIndex) const
{
Q_ASSERT(channelIndex < channelCount());
quint32 channelPosition = channels()[channelIndex]->pos();
return QString().setNum(pixel[channelPosition]);
}
QString KoAlphaColorSpace::normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const
{
Q_ASSERT(channelIndex < channelCount());
quint32 channelPosition = channels()[channelIndex]->pos();
return QString().setNum(static_cast<float>(pixel[channelPosition]) / UINT8_MAX);
}
void KoAlphaColorSpace::convolveColors(quint8** colors, qreal * kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const
{
qreal totalAlpha = 0;
while (nColors--) {
qreal weight = *kernelValues;
if (weight != 0) {
- totalAlpha += (*colors)[PIXEL_MASK] * weight;
+ totalAlpha += (*colors)[0] * weight;
}
++colors;
++kernelValues;
}
- if (channelFlags.isEmpty() || channelFlags.testBit(PIXEL_MASK))
- dst[PIXEL_MASK] = CLAMP((totalAlpha / factor) + offset, 0, SCHAR_MAX);
+ if (channelFlags.isEmpty() || channelFlags.testBit(0))
+ dst[0] = CLAMP((totalAlpha / factor) + offset, 0, SCHAR_MAX);
}
QImage KoAlphaColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
const KoColorProfile * /*dstProfile*/,
KoColorConversionTransformation::Intent /*renderingIntent*/,
KoColorConversionTransformation::ConversionFlags /*conversionFlags*/) const
{
QImage img(width, height, QImage::Format_Indexed8);
QVector<QRgb> table;
for (int i = 0; i < 256; ++i) table.append(qRgb(i, i, i));
img.setColorTable(table);
quint8* data_img;
for (int i = 0; i < height; ++i) {
data_img=img.scanLine(i);
for (int j = 0; j < width; ++j)
data_img[j]=*(data++);
}
return img;
}
KoColorSpace* KoAlphaColorSpace::clone() const
{
return new KoAlphaColorSpace();
}
bool KoAlphaColorSpace::preferCompositionInSourceColorSpace() const
{
return true;
}
diff --git a/libs/pigment/colorspaces/KoAlphaColorSpace.h b/libs/pigment/colorspaces/KoAlphaColorSpace.h
index 4dc2d2a2a0..ecc01705ed 100644
--- a/libs/pigment/colorspaces/KoAlphaColorSpace.h
+++ b/libs/pigment/colorspaces/KoAlphaColorSpace.h
@@ -1,208 +1,189 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KOALPHACOLORSPACE_H
#define KOALPHACOLORSPACE_H
#include <QColor>
#include "DebugPigment.h"
#include "kritapigment_export.h"
#include "KoColorSpaceAbstract.h"
#include "KoColorSpaceTraits.h"
#include "KoColorModelStandardIds.h"
#include "KoSimpleColorSpaceFactory.h"
typedef KoColorSpaceTrait<quint8, 1, 0> AlphaU8Traits;
class QBitArray;
/**
* The alpha mask is a special color strategy that treats all pixels as
* alpha value with a color common to the mask. The default color is white.
*/
class KRITAPIGMENT_EXPORT KoAlphaColorSpace : public KoColorSpaceAbstract<AlphaU8Traits>
{
public:
KoAlphaColorSpace();
virtual ~KoAlphaColorSpace();
static QString colorSpaceId() { return "ALPHA"; }
virtual KoID colorModelId() const {
return AlphaColorModelID;
}
virtual KoID colorDepthId() const {
return Integer8BitsColorDepthID;
}
virtual KoColorSpace* clone() const;
virtual bool willDegrade(ColorSpaceIndependence independence) const {
Q_UNUSED(independence);
return false;
}
virtual bool profileIsCompatible(const KoColorProfile* /*profile*/) const {
return false;
}
virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
virtual quint8 difference(const quint8 *src1, const quint8 *src2) const;
virtual quint8 differenceA(const quint8 *src1, const quint8 *src2) const;
virtual quint32 colorChannelCount() const {
return 0;
}
virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const;
virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const;
virtual void convolveColors(quint8** colors, qreal* kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const;
virtual quint32 colorSpaceType() const {
return 0;
}
virtual bool hasHighDynamicRange() const {
return false;
}
virtual const KoColorProfile* profile() const {
return m_profile;
}
virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height,
const KoColorProfile * dstProfile,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
virtual void toLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
quint16* lab = reinterpret_cast<quint16*>(dst);
while (nPixels--) {
lab[3] = src[0];
src++;
lab += 4;
}
}
virtual void fromLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
const quint16* lab = reinterpret_cast<const quint16*>(src);
while (nPixels--) {
dst[0] = lab[3];
dst++;
lab += 4;
}
}
virtual void toRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
quint16* rgb = reinterpret_cast<quint16*>(dst);
while (nPixels--) {
rgb[3] = src[0];
src++;
rgb += 4;
}
}
virtual void fromRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
const quint16* rgb = reinterpret_cast<const quint16*>(src);
while (nPixels--) {
dst[0] = rgb[3];
dst++;
rgb += 4;
}
}
virtual KoColorTransformation* createBrightnessContrastAdjustment(const quint16* transferValues) const {
Q_UNUSED(transferValues);
warnPigment << i18n("Undefined operation in the alpha color space");
return 0;
}
virtual KoColorTransformation* createPerChannelAdjustment(const quint16* const*) const {
warnPigment << i18n("Undefined operation in the alpha color space");
return 0;
}
virtual KoColorTransformation *createDarkenAdjustment(qint32 , bool , qreal) const {
warnPigment << i18n("Undefined operation in the alpha color space");
return 0;
}
virtual void invertColor(quint8*, qint32) const {
warnPigment << i18n("Undefined operation in the alpha color space");
}
virtual void colorToXML(const quint8* , QDomDocument& , QDomElement&) const {
warnPigment << i18n("Undefined operation in the alpha color space");
}
virtual void colorFromXML(quint8* , const QDomElement&) const {
warnPigment << i18n("Undefined operation in the alpha color space");
}
virtual void toHSY(const QVector<double> &, qreal *, qreal *, qreal *) const {
warnPigment << i18n("Undefined operation in the alpha color space");
}
virtual QVector <double> fromHSY(qreal *, qreal *, qreal *) const {
warnPigment << i18n("Undefined operation in the alpha color space");
QVector <double> channelValues (1);
channelValues.fill(0.0);
return channelValues;
}
virtual void toYUV(const QVector<double> &, qreal *, qreal *, qreal *) const {
warnPigment << i18n("Undefined operation in the alpha color space");
}
virtual QVector <double> fromYUV(qreal *, qreal *, qreal *) const {
warnPigment << i18n("Undefined operation in the alpha color space");
QVector <double> channelValues (1);
channelValues.fill(0.0);
return channelValues;
}
protected:
virtual bool preferCompositionInSourceColorSpace() const;
private:
KoColorProfile* m_profile;
QList<KoCompositeOp*> m_compositeOps;
};
-class KoAlphaColorSpaceFactory : public KoSimpleColorSpaceFactory
-{
-
-public:
- KoAlphaColorSpaceFactory()
- : KoSimpleColorSpaceFactory("ALPHA",
- i18n("Alpha mask"),
- false,
- AlphaColorModelID,
- Integer8BitsColorDepthID,
- 8) {
- }
-
- virtual KoColorSpace *createColorSpace(const KoColorProfile *) const {
- return new KoAlphaColorSpace();
- }
-
-};
-
#endif // KO_COLORSPACE_ALPHA_H_
diff --git a/libs/pigment/colorspaces/KoAlphaF16ColorSpace.cpp b/libs/pigment/colorspaces/KoAlphaF16ColorSpace.cpp
new file mode 100644
index 0000000000..8ce9ded688
--- /dev/null
+++ b/libs/pigment/colorspaces/KoAlphaF16ColorSpace.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
+ * Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "KoAlphaF16ColorSpace.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include <QImage>
+#include <QBitArray>
+
+#include <klocalizedstring.h>
+
+#include "KoChannelInfo.h"
+#include "KoID.h"
+#include "KoIntegerMaths.h"
+#include "KoCompositeOpOver.h"
+#include "KoCompositeOpErase.h"
+#include "KoCompositeOpCopy2.h"
+#include "KoCompositeOpAlphaDarken.h"
+#include <colorprofiles/KoDummyColorProfile.h>
+
+KoAlphaF16ColorSpace::KoAlphaF16ColorSpace() : KoColorSpaceAbstract<AlphaF16Traits>("ALPHAF16", i18n("16 bits float alpha mask"))
+{
+ addChannel(new KoChannelInfo(i18n("Alpha"), 0, 0, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT16));
+
+ m_compositeOps << new KoCompositeOpOver<AlphaF16Traits>(this)
+ << new KoCompositeOpErase<AlphaF16Traits>(this)
+ << new KoCompositeOpCopy2<AlphaF16Traits>(this)
+ << new KoCompositeOpAlphaDarken<AlphaF16Traits>(this);
+
+ Q_FOREACH (KoCompositeOp *op, m_compositeOps) {
+ addCompositeOp(op);
+ }
+ m_profile = new KoDummyColorProfile;
+}
+
+KoAlphaF16ColorSpace::~KoAlphaF16ColorSpace()
+{
+ qDeleteAll(m_compositeOps);
+ delete m_profile;
+ m_profile = 0;
+}
+
+void KoAlphaF16ColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const
+{
+ dst[0] = c.alpha();
+}
+
+void KoAlphaF16ColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const
+{
+ c->setRgba(qRgba(255, 255, 255, src[0]));
+}
+
+quint8 KoAlphaF16ColorSpace::difference(const quint8 *src1, const quint8 *src2) const
+{
+ // Arithmetic operands smaller than int are converted to int automatically
+ return qAbs(src2[0] - src1[0]);
+}
+
+quint8 KoAlphaF16ColorSpace::differenceA(const quint8 *src1, const quint8 *src2) const
+{
+ return difference(src1, src2);
+}
+
+QString KoAlphaF16ColorSpace::channelValueText(const quint8 *pixel, quint32 channelIndex) const
+{
+ Q_ASSERT(channelIndex < channelCount());
+ quint32 channelPosition = channels()[channelIndex]->pos();
+
+ return QString().setNum(pixel[channelPosition]);
+}
+
+QString KoAlphaF16ColorSpace::normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const
+{
+ Q_ASSERT(channelIndex < channelCount());
+ quint32 channelPosition = channels()[channelIndex]->pos();
+
+ return QString().setNum(static_cast<float>(pixel[channelPosition]) / UINT8_MAX);
+}
+
+void KoAlphaF16ColorSpace::convolveColors(quint8** /*colors*/, qreal * /*kernelValues*/, quint8 */*dst*/, qreal /*factor*/, qreal /*offset*/, qint32 /*nColors*/, const QBitArray & /*channelFlags*/) const
+{
+ warnPigment << i18n("Undefined operation in the alpha color space");
+}
+
+
+QImage KoAlphaF16ColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
+ const KoColorProfile * /*dstProfile*/,
+ KoColorConversionTransformation::Intent /*renderingIntent*/,
+ KoColorConversionTransformation::ConversionFlags /*conversionFlags*/) const
+{
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QImage img(width, height, QImage::Format_Indexed8);
+ return img;
+}
+
+KoColorSpace *KoAlphaF16ColorSpace::clone() const
+{
+ return new KoAlphaF16ColorSpace();
+}
+
+bool KoAlphaF16ColorSpace::preferCompositionInSourceColorSpace() const
+{
+ return true;
+}
diff --git a/libs/pigment/colorspaces/KoAlphaF16ColorSpace.h b/libs/pigment/colorspaces/KoAlphaF16ColorSpace.h
new file mode 100644
index 0000000000..e9920d03f0
--- /dev/null
+++ b/libs/pigment/colorspaces/KoAlphaF16ColorSpace.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef KOF16ALPHACOLORSPACE_H
+#define KOF16ALPHACOLORSPACE_H
+
+#include <QColor>
+
+#include "DebugPigment.h"
+#include "kritapigment_export.h"
+
+#include "KoColorSpaceAbstract.h"
+#include "KoColorSpaceTraits.h"
+
+#include "KoColorModelStandardIds.h"
+#include "KoSimpleColorSpaceFactory.h"
+
+#include <half.h>
+typedef KoColorSpaceTrait<half, 1, 0> AlphaF16Traits;
+
+class QBitArray;
+
+/**
+ * The alpha mask is a special color strategy that treats all pixels as
+ * alpha value with a color common to the mask. The default color is white.
+ */
+class KRITAPIGMENT_EXPORT KoAlphaF16ColorSpace : public KoColorSpaceAbstract<AlphaF16Traits>
+{
+
+public:
+
+ KoAlphaF16ColorSpace();
+
+ virtual ~KoAlphaF16ColorSpace();
+
+ static QString colorSpaceId() { return "ALPHAF16"; }
+
+ virtual KoID colorModelId() const {
+ return AlphaColorModelID;
+ }
+
+ virtual KoID colorDepthId() const {
+ return Float16BitsColorDepthID;
+ }
+
+ virtual KoColorSpace* clone() const;
+
+ virtual bool willDegrade(ColorSpaceIndependence independence) const {
+ Q_UNUSED(independence);
+ return false;
+ }
+
+ virtual bool profileIsCompatible(const KoColorProfile* /*profile*/) const {
+ return false;
+ }
+
+ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
+
+ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
+
+ virtual quint8 difference(const quint8 *src1, const quint8 *src2) const;
+ virtual quint8 differenceA(const quint8 *src1, const quint8 *src2) const;
+
+ virtual quint32 colorChannelCount() const {
+ return 0;
+ }
+
+ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const;
+
+ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const;
+
+ virtual void convolveColors(quint8** colors, qreal* kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const;
+
+ virtual quint32 colorSpaceType() const {
+ return 0;
+ }
+
+ virtual bool hasHighDynamicRange() const {
+ return false;
+ }
+
+ virtual const KoColorProfile* profile() const {
+ return m_profile;
+ }
+
+ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height,
+ const KoColorProfile * dstProfile,
+ KoColorConversionTransformation::Intent renderingIntent,
+ KoColorConversionTransformation::ConversionFlags conversionFlags) const;
+
+ virtual void toLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ quint16* lab = reinterpret_cast<quint16*>(dst);
+ while (nPixels--) {
+ lab[3] = src[0];
+ src++;
+ lab += 4;
+ }
+ }
+
+ virtual void fromLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ const quint16* lab = reinterpret_cast<const quint16*>(src);
+ while (nPixels--) {
+ dst[0] = lab[3];
+ dst++;
+ lab += 4;
+ }
+ }
+
+ virtual void toRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ quint16* rgb = reinterpret_cast<quint16*>(dst);
+ while (nPixels--) {
+ rgb[3] = src[0];
+ src++;
+ rgb += 4;
+ }
+ }
+
+ virtual void fromRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ const quint16* rgb = reinterpret_cast<const quint16*>(src);
+ while (nPixels--) {
+ dst[0] = rgb[3];
+ dst++;
+ rgb += 4;
+ }
+ }
+
+ virtual KoColorTransformation* createBrightnessContrastAdjustment(const quint16* transferValues) const {
+ Q_UNUSED(transferValues);
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+
+ virtual KoColorTransformation* createPerChannelAdjustment(const quint16* const*) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+ virtual KoColorTransformation *createDarkenAdjustment(qint32 , bool , qreal) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+ virtual void invertColor(quint8*, qint32) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual void colorToXML(const quint8* , QDomDocument& , QDomElement&) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual void colorFromXML(quint8* , const QDomElement&) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual void toHSY(const QVector<double> &, qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual QVector <double> fromHSY(qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QVector <double> channelValues (1);
+ channelValues.fill(0.0);
+ return channelValues;
+ }
+ virtual void toYUV(const QVector<double> &, qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual QVector <double> fromYUV(qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QVector <double> channelValues (1);
+ channelValues.fill(0.0);
+ return channelValues;
+ }
+
+protected:
+ virtual bool preferCompositionInSourceColorSpace() const;
+
+private:
+ KoColorProfile* m_profile;
+ QList<KoCompositeOp*> m_compositeOps;
+};
+
+#endif
diff --git a/libs/pigment/colorspaces/KoAlphaF32ColorSpace.cpp b/libs/pigment/colorspaces/KoAlphaF32ColorSpace.cpp
new file mode 100644
index 0000000000..f744e173ea
--- /dev/null
+++ b/libs/pigment/colorspaces/KoAlphaF32ColorSpace.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
+ * Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "KoAlphaF32ColorSpace.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include <QImage>
+#include <QBitArray>
+
+#include <klocalizedstring.h>
+
+#include "KoChannelInfo.h"
+#include "KoID.h"
+#include "KoIntegerMaths.h"
+#include "KoCompositeOpOver.h"
+#include "KoCompositeOpErase.h"
+#include "KoCompositeOpCopy2.h"
+#include "KoCompositeOpAlphaDarken.h"
+#include <colorprofiles/KoDummyColorProfile.h>
+
+KoAlphaF32ColorSpace::KoAlphaF32ColorSpace() : KoColorSpaceAbstract<AlphaF32Traits>("ALPHAF32", i18n("32 bits float alpha mask"))
+{
+ addChannel(new KoChannelInfo(i18n("Alpha"), 0, 0, KoChannelInfo::ALPHA, KoChannelInfo::FLOAT32));
+
+ m_compositeOps << new KoCompositeOpOver<AlphaF32Traits>(this)
+ << new KoCompositeOpErase<AlphaF32Traits>(this)
+ << new KoCompositeOpCopy2<AlphaF32Traits>(this)
+ << new KoCompositeOpAlphaDarken<AlphaF32Traits>(this);
+
+ Q_FOREACH (KoCompositeOp *op, m_compositeOps) {
+ addCompositeOp(op);
+ }
+ m_profile = new KoDummyColorProfile;
+}
+
+KoAlphaF32ColorSpace::~KoAlphaF32ColorSpace()
+{
+ qDeleteAll(m_compositeOps);
+ delete m_profile;
+ m_profile = 0;
+}
+
+void KoAlphaF32ColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const
+{
+ dst[0] = c.alpha();
+}
+
+void KoAlphaF32ColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const
+{
+ c->setRgba(qRgba(255, 255, 255, src[0]));
+}
+
+quint8 KoAlphaF32ColorSpace::difference(const quint8 *src1, const quint8 *src2) const
+{
+ // Arithmetic operands smaller than int are converted to int automatically
+ return qAbs(src2[0] - src1[0]);
+}
+
+quint8 KoAlphaF32ColorSpace::differenceA(const quint8 *src1, const quint8 *src2) const
+{
+ return difference(src1, src2);
+}
+
+QString KoAlphaF32ColorSpace::channelValueText(const quint8 *pixel, quint32 channelIndex) const
+{
+ Q_ASSERT(channelIndex < channelCount());
+ quint32 channelPosition = channels()[channelIndex]->pos();
+
+ return QString().setNum(pixel[channelPosition]);
+}
+
+QString KoAlphaF32ColorSpace::normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const
+{
+ Q_ASSERT(channelIndex < channelCount());
+ quint32 channelPosition = channels()[channelIndex]->pos();
+
+ return QString().setNum(static_cast<float>(pixel[channelPosition]) / UINT8_MAX);
+}
+
+void KoAlphaF32ColorSpace::convolveColors(quint8** /*colors*/, qreal * /*kernelValues*/, quint8 */*dst*/, qreal /*factor*/, qreal /*offset*/, qint32 /*nColors*/, const QBitArray & /*channelFlags*/) const
+{
+ warnPigment << i18n("Undefined operation in the alpha color space");
+}
+
+
+QImage KoAlphaF32ColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
+ const KoColorProfile * /*dstProfile*/,
+ KoColorConversionTransformation::Intent /*renderingIntent*/,
+ KoColorConversionTransformation::ConversionFlags /*conversionFlags*/) const
+{
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QImage img(width, height, QImage::Format_Indexed8);
+ return img;
+}
+
+KoColorSpace* KoAlphaF32ColorSpace::clone() const
+{
+ return new KoAlphaF32ColorSpace();
+}
+
+bool KoAlphaF32ColorSpace::preferCompositionInSourceColorSpace() const
+{
+ return true;
+}
diff --git a/libs/pigment/colorspaces/KoAlphaF32ColorSpace.h b/libs/pigment/colorspaces/KoAlphaF32ColorSpace.h
new file mode 100644
index 0000000000..06b622f59f
--- /dev/null
+++ b/libs/pigment/colorspaces/KoAlphaF32ColorSpace.h
@@ -0,0 +1,189 @@
+/*
+ * 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 Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef KOALPHAF32COLORSPACE_H
+#define KOALPHAF32COLORSPACE_H
+
+#include <QColor>
+
+#include "DebugPigment.h"
+#include "kritapigment_export.h"
+
+#include "KoColorSpaceAbstract.h"
+#include "KoColorSpaceTraits.h"
+
+#include "KoColorModelStandardIds.h"
+#include "KoSimpleColorSpaceFactory.h"
+
+typedef KoColorSpaceTrait<float, 1, 0> AlphaF32Traits;
+
+class QBitArray;
+
+/**
+ * The alpha mask is a special color strategy that treats all pixels as
+ * alpha value with a color common to the mask. The default color is white.
+ */
+class KRITAPIGMENT_EXPORT KoAlphaF32ColorSpace : public KoColorSpaceAbstract<AlphaF32Traits>
+{
+
+public:
+
+ KoAlphaF32ColorSpace();
+
+ virtual ~KoAlphaF32ColorSpace();
+
+ static QString colorSpaceId() { return "ALPHAF32"; }
+
+ virtual KoID colorModelId() const {
+ return AlphaColorModelID;
+ }
+
+ virtual KoID colorDepthId() const {
+ return Float32BitsColorDepthID;
+ }
+
+ virtual KoColorSpace* clone() const;
+
+ virtual bool willDegrade(ColorSpaceIndependence independence) const {
+ Q_UNUSED(independence);
+ return false;
+ }
+
+ virtual bool profileIsCompatible(const KoColorProfile* /*profile*/) const {
+ return false;
+ }
+
+ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
+
+ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
+
+ virtual quint8 difference(const quint8 *src1, const quint8 *src2) const;
+ virtual quint8 differenceA(const quint8 *src1, const quint8 *src2) const;
+
+ virtual quint32 colorChannelCount() const {
+ return 0;
+ }
+
+ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const;
+
+ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const;
+
+ virtual void convolveColors(quint8** colors, qreal* kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const;
+
+ virtual quint32 colorSpaceType() const {
+ return 0;
+ }
+
+ virtual bool hasHighDynamicRange() const {
+ return false;
+ }
+
+ virtual const KoColorProfile* profile() const {
+ return m_profile;
+ }
+
+ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height,
+ const KoColorProfile * dstProfile,
+ KoColorConversionTransformation::Intent renderingIntent,
+ KoColorConversionTransformation::ConversionFlags conversionFlags) const;
+
+ virtual void toLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ quint16* lab = reinterpret_cast<quint16*>(dst);
+ while (nPixels--) {
+ lab[3] = src[0];
+ src++;
+ lab += 4;
+ }
+ }
+ virtual void fromLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ const quint16* lab = reinterpret_cast<const quint16*>(src);
+ while (nPixels--) {
+ dst[0] = lab[3];
+ dst++;
+ lab += 4;
+ }
+ }
+
+ virtual void toRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ quint16* rgb = reinterpret_cast<quint16*>(dst);
+ while (nPixels--) {
+ rgb[3] = src[0];
+ src++;
+ rgb += 4;
+ }
+ }
+ virtual void fromRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ const quint16* rgb = reinterpret_cast<const quint16*>(src);
+ while (nPixels--) {
+ dst[0] = rgb[3];
+ dst++;
+ rgb += 4;
+ }
+ }
+ virtual KoColorTransformation* createBrightnessContrastAdjustment(const quint16* transferValues) const {
+ Q_UNUSED(transferValues);
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+
+ virtual KoColorTransformation* createPerChannelAdjustment(const quint16* const*) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+ virtual KoColorTransformation *createDarkenAdjustment(qint32 , bool , qreal) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+ virtual void invertColor(quint8*, qint32) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual void colorToXML(const quint8* , QDomDocument& , QDomElement&) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual void colorFromXML(quint8* , const QDomElement&) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual void toHSY(const QVector<double> &, qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual QVector <double> fromHSY(qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QVector <double> channelValues (1);
+ channelValues.fill(0.0);
+ return channelValues;
+ }
+ virtual void toYUV(const QVector<double> &, qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+ virtual QVector <double> fromYUV(qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QVector <double> channelValues (1);
+ channelValues.fill(0.0);
+ return channelValues;
+ }
+
+protected:
+ virtual bool preferCompositionInSourceColorSpace() const;
+
+private:
+ KoColorProfile* m_profile;
+ QList<KoCompositeOp*> m_compositeOps;
+};
+
+
+
+#endif
diff --git a/libs/pigment/colorspaces/KoAlphaU16ColorSpace.cpp b/libs/pigment/colorspaces/KoAlphaU16ColorSpace.cpp
new file mode 100644
index 0000000000..f7ca740764
--- /dev/null
+++ b/libs/pigment/colorspaces/KoAlphaU16ColorSpace.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
+ * Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
+ * Copyright (c) 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "KoAlphaU16ColorSpace.h"
+
+#include <limits.h>
+#include <stdlib.h>
+
+#include <QImage>
+#include <QBitArray>
+
+#include <klocalizedstring.h>
+
+#include "KoChannelInfo.h"
+#include "KoID.h"
+#include "KoIntegerMaths.h"
+#include "KoCompositeOpOver.h"
+#include "KoCompositeOpErase.h"
+#include "KoCompositeOpCopy2.h"
+#include "KoCompositeOpAlphaDarken.h"
+#include <colorprofiles/KoDummyColorProfile.h>
+
+
+KoAlphaU16ColorSpace::KoAlphaU16ColorSpace() : KoColorSpaceAbstract<AlphaU16Traits>("ALPHAU16", i18n("16 bits integer alpha mask"))
+{
+ addChannel(new KoChannelInfo(i18n("Alpha"), 0, 0, KoChannelInfo::ALPHA, KoChannelInfo::UINT16));
+
+ m_compositeOps << new KoCompositeOpOver<AlphaU16Traits>(this)
+ << new KoCompositeOpErase<AlphaU16Traits>(this)
+ << new KoCompositeOpCopy2<AlphaU16Traits>(this)
+ << new KoCompositeOpAlphaDarken<AlphaU16Traits>(this);
+
+ Q_FOREACH (KoCompositeOp *op, m_compositeOps) {
+ addCompositeOp(op);
+ }
+ m_profile = new KoDummyColorProfile;
+}
+
+KoAlphaU16ColorSpace::~KoAlphaU16ColorSpace()
+{
+ qDeleteAll(m_compositeOps);
+ delete m_profile;
+ m_profile = 0;
+}
+
+void KoAlphaU16ColorSpace::fromQColor(const QColor& c, quint8 *dst, const KoColorProfile * /*profile*/) const
+{
+ dst[0] = c.alpha();
+}
+
+void KoAlphaU16ColorSpace::toQColor(const quint8 * src, QColor *c, const KoColorProfile * /*profile*/) const
+{
+ c->setRgba(qRgba(255, 255, 255, src[0]));
+}
+
+quint8 KoAlphaU16ColorSpace::difference(const quint8 *src1, const quint8 *src2) const
+{
+ // Arithmetic operands smaller than int are converted to int automatically
+ return qAbs(src2[0] - src1[0]);
+}
+
+quint8 KoAlphaU16ColorSpace::differenceA(const quint8 *src1, const quint8 *src2) const
+{
+ return difference(src1, src2);
+}
+
+QString KoAlphaU16ColorSpace::channelValueText(const quint8 *pixel, quint32 channelIndex) const
+{
+ Q_ASSERT(channelIndex < channelCount());
+ quint32 channelPosition = channels()[channelIndex]->pos();
+
+ return QString().setNum(pixel[channelPosition]);
+}
+
+QString KoAlphaU16ColorSpace::normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const
+{
+ Q_ASSERT(channelIndex < channelCount());
+ quint32 channelPosition = channels()[channelIndex]->pos();
+
+ return QString().setNum(static_cast<float>(pixel[channelPosition]) / UINT16_MAX);
+}
+
+void KoAlphaU16ColorSpace::convolveColors(quint8** colors, qreal * kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const
+{
+ warnPigment << i18n("Undefined operation in the alpha color space");
+}
+
+
+QImage KoAlphaU16ColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
+ const KoColorProfile * /*dstProfile*/,
+ KoColorConversionTransformation::Intent /*renderingIntent*/,
+ KoColorConversionTransformation::ConversionFlags /*conversionFlags*/) const
+{
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QImage img(width, height, QImage::Format_Indexed8);
+ return img;
+}
+
+KoColorSpace* KoAlphaU16ColorSpace::clone() const
+{
+ return new KoAlphaU16ColorSpace();
+}
+
+bool KoAlphaU16ColorSpace::preferCompositionInSourceColorSpace() const
+{
+ return true;
+}
diff --git a/libs/pigment/colorspaces/KoAlphaU16ColorSpace.h b/libs/pigment/colorspaces/KoAlphaU16ColorSpace.h
new file mode 100644
index 0000000000..7ed996d078
--- /dev/null
+++ b/libs/pigment/colorspaces/KoAlphaU16ColorSpace.h
@@ -0,0 +1,195 @@
+/*
+ * 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 Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef KOALPHAU16COLORSPACE_H
+#define KOALPHAU16COLORSPACE_H
+
+#include <QColor>
+
+#include "DebugPigment.h"
+#include "kritapigment_export.h"
+
+#include "KoColorSpaceAbstract.h"
+#include "KoColorSpaceTraits.h"
+
+#include "KoColorModelStandardIds.h"
+#include "KoSimpleColorSpaceFactory.h"
+
+typedef KoColorSpaceTrait<quint16, 1, 0> AlphaU16Traits;
+
+class QBitArray;
+
+/**
+ * The alpha mask is a special color strategy that treats all pixels as
+ * alpha value with a color common to the mask. The default color is white.
+ */
+class KRITAPIGMENT_EXPORT KoAlphaU16ColorSpace : public KoColorSpaceAbstract<AlphaU16Traits>
+{
+
+public:
+
+ KoAlphaU16ColorSpace();
+
+ virtual ~KoAlphaU16ColorSpace();
+
+ static QString colorSpaceId() { return "ALPHAU16"; }
+
+ virtual KoID colorModelId() const {
+ return AlphaColorModelID;
+ }
+
+ virtual KoID colorDepthId() const {
+ return Integer16BitsColorDepthID;
+ }
+
+ virtual KoColorSpace* clone() const;
+
+ virtual bool willDegrade(ColorSpaceIndependence independence) const {
+ Q_UNUSED(independence);
+ return false;
+ }
+
+ virtual bool profileIsCompatible(const KoColorProfile* /*profile*/) const {
+ return false;
+ }
+
+ virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
+
+ virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
+
+ virtual quint8 difference(const quint8 *src1, const quint8 *src2) const;
+ virtual quint8 differenceA(const quint8 *src1, const quint8 *src2) const;
+
+ virtual quint32 colorChannelCount() const {
+ return 0;
+ }
+
+ virtual QString channelValueText(const quint8 *pixel, quint32 channelIndex) const;
+
+ virtual QString normalisedChannelValueText(const quint8 *pixel, quint32 channelIndex) const;
+
+ virtual void convolveColors(quint8** colors, qreal* kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nColors, const QBitArray & channelFlags) const;
+
+ virtual quint32 colorSpaceType() const {
+ return 0;
+ }
+
+ virtual bool hasHighDynamicRange() const {
+ return false;
+ }
+
+ virtual const KoColorProfile* profile() const {
+ return m_profile;
+ }
+
+ virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height,
+ const KoColorProfile * dstProfile,
+ KoColorConversionTransformation::Intent renderingIntent,
+ KoColorConversionTransformation::ConversionFlags conversionFlags) const;
+
+ virtual void toLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ quint16* lab = reinterpret_cast<quint16*>(dst);
+ while (nPixels--) {
+ lab[3] = src[0];
+ src++;
+ lab += 4;
+ }
+ }
+ virtual void fromLabA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ const quint16* lab = reinterpret_cast<const quint16*>(src);
+ while (nPixels--) {
+ dst[0] = lab[3];
+ dst++;
+ lab += 4;
+ }
+ }
+
+ virtual void toRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ quint16* rgb = reinterpret_cast<quint16*>(dst);
+ while (nPixels--) {
+ rgb[3] = src[0];
+ src++;
+ rgb += 4;
+ }
+ }
+ virtual void fromRgbA16(const quint8* src, quint8* dst, quint32 nPixels) const {
+ const quint16* rgb = reinterpret_cast<const quint16*>(src);
+ while (nPixels--) {
+ dst[0] = rgb[3];
+ dst++;
+ rgb += 4;
+ }
+ }
+ virtual KoColorTransformation* createBrightnessContrastAdjustment(const quint16* transferValues) const {
+ Q_UNUSED(transferValues);
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+
+ virtual KoColorTransformation* createPerChannelAdjustment(const quint16* const*) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+
+ virtual KoColorTransformation *createDarkenAdjustment(qint32 , bool , qreal) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ return 0;
+ }
+
+ virtual void invertColor(quint8*, qint32) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+
+ virtual void colorToXML(const quint8* , QDomDocument& , QDomElement&) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+
+ virtual void colorFromXML(quint8* , const QDomElement&) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+
+ virtual void toHSY(const QVector<double> &, qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+
+ virtual QVector <double> fromHSY(qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QVector <double> channelValues (1);
+ channelValues.fill(0.0);
+ return channelValues;
+ }
+
+ virtual void toYUV(const QVector<double> &, qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ }
+
+ virtual QVector <double> fromYUV(qreal *, qreal *, qreal *) const {
+ warnPigment << i18n("Undefined operation in the alpha color space");
+ QVector <double> channelValues (1);
+ channelValues.fill(0.0);
+ return channelValues;
+ }
+
+protected:
+ virtual bool preferCompositionInSourceColorSpace() const;
+
+private:
+ KoColorProfile* m_profile;
+ QList<KoCompositeOp*> m_compositeOps;
+};
+
+#endif
diff --git a/libs/pigment/colorspaces/KoLabColorSpace.h b/libs/pigment/colorspaces/KoLabColorSpace.h
index 86bc5ef963..6135102992 100644
--- a/libs/pigment/colorspaces/KoLabColorSpace.h
+++ b/libs/pigment/colorspaces/KoLabColorSpace.h
@@ -1,88 +1,87 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KOLABCOLORSPACE_H
#define KOLABCOLORSPACE_H
#include <QColor>
#include "KoSimpleColorSpace.h"
#include "KoSimpleColorSpaceFactory.h"
#include "KoColorModelStandardIds.h"
struct KoLabU16Traits;
/**
* Basic and simple implementation of the LAB colorspace
*/
class KoLabColorSpace : public KoSimpleColorSpace<KoLabU16Traits>
{
public:
KoLabColorSpace();
virtual ~KoLabColorSpace();
static QString colorSpaceId();
virtual KoColorSpace* clone() const;
virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
virtual void toHSY(const QVector<double> &channelValues, qreal *hue, qreal *sat, qreal *luma) const;
virtual QVector <double> fromHSY(qreal *hue, qreal *sat, qreal *luma) const;
virtual void toYUV(const QVector<double> &channelValues, qreal *y, qreal *u, qreal *v) const;
virtual QVector <double> fromYUV(qreal *y, qreal *u, qreal *v) const;
private:
static const quint32 CHANNEL_L = 0;
static const quint32 CHANNEL_A = 1;
static const quint32 CHANNEL_B = 2;
static const quint32 CHANNEL_ALPHA = 3;
static const quint32 MAX_CHANNEL_L = 0xff00;
static const quint32 MAX_CHANNEL_AB = 0xffff;
static const quint32 CHANNEL_AB_ZERO_OFFSET = 0x8000;
};
class KoLabColorSpaceFactory : public KoSimpleColorSpaceFactory
{
public:
KoLabColorSpaceFactory()
: KoSimpleColorSpaceFactory("LABA",
i18n("L*a*b* (16-bit integer/channel, unmanaged)"),
true,
LABAColorModelID,
- Integer16BitsColorDepthID,
- 16) {
+ Integer16BitsColorDepthID) {
}
virtual KoColorSpace *createColorSpace(const KoColorProfile *) const {
return new KoLabColorSpace();
}
};
#endif
diff --git a/libs/pigment/colorspaces/KoRgbU16ColorSpace.h b/libs/pigment/colorspaces/KoRgbU16ColorSpace.h
index 6a866dcb52..4585cc04b2 100644
--- a/libs/pigment/colorspaces/KoRgbU16ColorSpace.h
+++ b/libs/pigment/colorspaces/KoRgbU16ColorSpace.h
@@ -1,77 +1,76 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KORGBU16COLORSPACE_H
#define KORGBU16COLORSPACE_H
#include <QColor>
#include "KoSimpleColorSpace.h"
#include "KoSimpleColorSpaceFactory.h"
#include "KoColorModelStandardIds.h"
struct KoBgrU16Traits;
/**
* The alpha mask is a special color strategy that treats all pixels as
* alpha value with a color common to the mask. The default color is white.
*/
class KoRgbU16ColorSpace : public KoSimpleColorSpace<KoBgrU16Traits>
{
public:
KoRgbU16ColorSpace();
virtual ~KoRgbU16ColorSpace();
static QString colorSpaceId();
virtual KoColorSpace* clone() const;
virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
virtual void toHSY(const QVector<double> &channelValues, qreal *hue, qreal *sat, qreal *luma) const;
virtual QVector <double> fromHSY(qreal *hue, qreal *sat, qreal *luma) const;
virtual void toYUV(const QVector<double> &channelValues, qreal *y, qreal *u, qreal *v) const;
virtual QVector <double> fromYUV(qreal *y, qreal *u, qreal *v) const;
};
class KoRgbU16ColorSpaceFactory : public KoSimpleColorSpaceFactory
{
public:
KoRgbU16ColorSpaceFactory()
: KoSimpleColorSpaceFactory(KoRgbU16ColorSpace::colorSpaceId(),
i18n("RGB (16-bit integer/channel, unmanaged)"),
true,
RGBAColorModelID,
- Integer16BitsColorDepthID,
- 16) {
+ Integer16BitsColorDepthID) {
}
virtual KoColorSpace *createColorSpace(const KoColorProfile *) const {
return new KoRgbU16ColorSpace();
}
};
#endif
diff --git a/libs/pigment/colorspaces/KoRgbU8ColorSpace.h b/libs/pigment/colorspaces/KoRgbU8ColorSpace.h
index d5157d4adb..e1f4abefb0 100644
--- a/libs/pigment/colorspaces/KoRgbU8ColorSpace.h
+++ b/libs/pigment/colorspaces/KoRgbU8ColorSpace.h
@@ -1,77 +1,76 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2006 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KORGBU8COLORSPACE_H
#define KORGBU8COLORSPACE_H
#include <QColor>
#include "KoSimpleColorSpace.h"
#include "KoSimpleColorSpaceFactory.h"
#include "KoColorModelStandardIds.h"
struct KoBgrU8Traits;
/**
* The alpha mask is a special color strategy that treats all pixels as
* alpha value with a color common to the mask. The default color is white.
*/
class KoRgbU8ColorSpace : public KoSimpleColorSpace<KoBgrU8Traits>
{
public:
KoRgbU8ColorSpace();
virtual ~KoRgbU8ColorSpace();
static QString colorSpaceId();
virtual KoColorSpace* clone() const;
virtual void fromQColor(const QColor& color, quint8 *dst, const KoColorProfile * profile = 0) const;
virtual void toQColor(const quint8 *src, QColor *c, const KoColorProfile * profile = 0) const;
virtual void toHSY(const QVector<double> &channelValues, qreal *hue, qreal *sat, qreal *luma) const;
virtual QVector <double> fromHSY(qreal *hue, qreal *sat, qreal *luma) const;
virtual void toYUV(const QVector<double> &channelValues, qreal *y, qreal *u, qreal *v) const;
virtual QVector <double> fromYUV(qreal *y, qreal *u, qreal *v) const;
};
class KoRgbU8ColorSpaceFactory : public KoSimpleColorSpaceFactory
{
public:
KoRgbU8ColorSpaceFactory()
: KoSimpleColorSpaceFactory("RGBA",
i18n("RGB (8-bit integer/channel, unmanaged)"),
true,
RGBAColorModelID,
- Integer8BitsColorDepthID,
- 8) {
+ Integer8BitsColorDepthID) {
}
virtual KoColorSpace *createColorSpace(const KoColorProfile *) const {
return new KoRgbU8ColorSpace();
}
};
#endif
diff --git a/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.h b/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.h
index 11d6e0af3a..08bf8e7f1a 100644
--- a/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.h
+++ b/libs/pigment/colorspaces/KoSimpleColorSpaceEngine.h
@@ -1,38 +1,39 @@
/*
* Copyright (c) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_SIMPLE_COLOR_SPACE_ENGINE_H_
#define _KO_SIMPLE_COLOR_SPACE_ENGINE_H_
#include <KoColorSpaceEngine.h>
class KoSimpleColorSpaceEngine : public KoColorSpaceEngine
{
public:
KoSimpleColorSpaceEngine();
virtual ~KoSimpleColorSpaceEngine();
virtual KoColorConversionTransformation* createColorTransformation(const KoColorSpace* srcColorSpace,
const KoColorSpace* dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
- virtual void addProfile(const QString &profile ) { Q_UNUSED(profile); }
+ virtual const KoColorProfile* addProfile(const QString &profile ) { Q_UNUSED(profile); return 0; }
+ virtual const KoColorProfile* addProfile(const QByteArray &data) { Q_UNUSED(data); return 0; }
virtual void removeProfile(const QString &profile ) { Q_UNUSED(profile); }
};
#endif
diff --git a/libs/pigment/colorspaces/KoSimpleColorSpaceFactory.h b/libs/pigment/colorspaces/KoSimpleColorSpaceFactory.h
index 886fdacfd6..0627086a90 100644
--- a/libs/pigment/colorspaces/KoSimpleColorSpaceFactory.h
+++ b/libs/pigment/colorspaces/KoSimpleColorSpaceFactory.h
@@ -1,107 +1,125 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2005-2006 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2004,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 KOSIMPLECOLORSPACEFACTORY_H_
#define KOSIMPLECOLORSPACEFACTORY_H_
#include "KoColorConversionTransformationFactory.h"
#include <colorprofiles/KoDummyColorProfile.h>
+#include <KoColorModelStandardIds.h>
+
class KoSimpleColorSpaceFactory : public KoColorSpaceFactory
{
public:
KoSimpleColorSpaceFactory(const QString& id,
const QString& name,
bool userVisible,
const KoID& colorModelId,
const KoID& colorDepthId,
- int referenceDepth)
- : m_id(id)
- , m_name(name)
- , m_userVisible(userVisible)
- , m_colorModelId(colorModelId)
- , m_colorDepthId(colorDepthId)
- , m_referenceDepth(referenceDepth) {
+ int referenceDepth = -1)
+ : m_id(id)
+ , m_name(name)
+ , m_userVisible(userVisible)
+ , m_colorModelId(colorModelId)
+ , m_colorDepthId(colorDepthId)
+ , m_referenceDepth(referenceDepth)
+ {
+ if (colorDepthId == Integer8BitsColorDepthID) {
+ m_referenceDepth = 1 * 8;
+ }
+ else if (colorDepthId == Integer16BitsColorDepthID) {
+ m_referenceDepth = 2 * 8;
+ }
+ if (colorDepthId == Float16BitsColorDepthID) {
+ m_referenceDepth = 2 * 8;
+ }
+ if (colorDepthId == Float32BitsColorDepthID) {
+ m_referenceDepth = 4 * 8;
+ }
+ if (colorDepthId == Float64BitsColorDepthID) {
+ m_referenceDepth = 8 * 8;
+ }
}
virtual QString id() const {
return m_id;
}
virtual QString name() const {
return m_name;
}
virtual bool userVisible() const {
return m_userVisible;
}
virtual KoID colorModelId() const {
return m_colorModelId;
}
virtual KoID colorDepthId() const {
return m_colorDepthId;
}
virtual bool profileIsCompatible(const KoColorProfile* profile) const {
return dynamic_cast<const KoDummyColorProfile*>(profile);
}
virtual QString colorSpaceEngine() const {
return "simple";
}
virtual bool isHdr() const {
return false;
}
virtual int referenceDepth() const {
return m_referenceDepth;
}
virtual QList<KoColorConversionTransformationFactory*> colorConversionLinks() const {
return QList<KoColorConversionTransformationFactory*>();
}
virtual QString defaultProfile() const {
return QString("default");
}
protected:
virtual KoColorProfile* createColorProfile(const QByteArray& /*rawData*/) const {
return 0;
}
private:
QString m_id;
QString m_name;
bool m_userVisible;
KoID m_colorModelId;
KoID m_colorDepthId;
int m_referenceDepth;
};
#endif
diff --git a/libs/pigment/resources/KoStopGradient.cpp b/libs/pigment/resources/KoStopGradient.cpp
index d006769d12..79ccf6dd28 100644
--- a/libs/pigment/resources/KoStopGradient.cpp
+++ b/libs/pigment/resources/KoStopGradient.cpp
@@ -1,680 +1,686 @@
/*
Copyright (C) 2005 Tim Beaulen <tbscope@gmail.org>
Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 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/KoStopGradient.h>
#include <cfloat>
#include <QColor>
#include <QFile>
#include <QDomDocument>
#include <QBuffer>
#include <klocalizedstring.h>
#include <DebugPigment.h>
#include "KoColorSpaceRegistry.h"
#include "KoMixColorsOp.h"
#include <math.h>
#include <KoColorModelStandardIds.h>
KoStopGradient::KoStopGradient(const QString& filename)
: KoAbstractGradient(filename)
{
}
KoStopGradient::~KoStopGradient()
{
}
KoAbstractGradient* KoStopGradient::clone() const
{
return new KoStopGradient(*this);
}
bool KoStopGradient::load()
{
QFile f(filename());
if (!f.open(QIODevice::ReadOnly)) {
warnPigment << "Can't open file " << filename();
return false;
}
bool res = loadFromDevice(&f);
f.close();
return res;
}
bool KoStopGradient::loadFromDevice(QIODevice *dev)
{
QString strExt;
const int result = filename().lastIndexOf('.');
if (result >= 0) {
strExt = filename().mid(result).toLower();
}
QByteArray ba = dev->readAll();
QBuffer buf(&ba);
if (strExt == ".kgr") {
loadKarbonGradient(&buf);
}
else if (strExt == ".svg") {
loadSvgGradient(&buf);
}
if (m_stops.count() >= 2) {
setValid(true);
}
updatePreview();
return true;
}
bool KoStopGradient::save()
{
QFile fileOut(filename());
if (! fileOut.open(QIODevice::WriteOnly))
return false;
bool retval = saveToDevice(&fileOut);
fileOut.close();
return retval;
}
QGradient* KoStopGradient::toQGradient() const
{
QGradient* gradient;
switch (type()) {
case QGradient::LinearGradient: {
gradient = new QLinearGradient(m_start, m_stop);
break;
}
case QGradient::RadialGradient: {
QPointF diff = m_stop - m_start;
qreal radius = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
gradient = new QRadialGradient(m_start, radius, m_focalPoint);
break;
}
case QGradient::ConicalGradient: {
qreal angle = atan2(m_start.y(), m_start.x()) * 180.0 / M_PI;
if (angle < 0.0)
angle += 360.0;
gradient = new QConicalGradient(m_start, angle);
break;
}
default:
return 0;
}
QColor color;
for (QList<KoGradientStop>::const_iterator i = m_stops.begin(); i != m_stops.end(); ++i) {
i->second.toQColor(&color);
gradient->setColorAt(i->first , color);
}
+
+ gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
+ gradient->setSpread(this->spread());
+
return gradient;
}
void KoStopGradient::colorAt(KoColor& dst, qreal t) const
{
KoColor buffer;
if (! m_stops.count())
return;
if (t <= m_stops.first().first || m_stops.count() == 1) {
// we have only one stop or t is before the first stop
// -> use the color of the first stop
dst.fromKoColor(m_stops.first().second);
} else if (t >= m_stops.last().first) {
// t is after the last stop
// -> use the color of the last stop
dst.fromKoColor(m_stops.last().second);
} else {
// we have at least two color stops
// -> find the two stops which frame our t
QList<KoGradientStop>::const_iterator stop = m_stops.begin();
QList<KoGradientStop>::const_iterator lastStop = m_stops.end();
// we already checked the first stop, so we start at the second
for (++stop; stop != lastStop; ++stop) {
// we break at the stop which is just after our t
if (stop->first > t)
break;
}
//if ( *buffer.colorSpace() != *colorSpace()) {
// buffer = KoColor(colorSpace());
//}
//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());
const KoGradientStop& leftStop = *(stop - 1);
const KoGradientStop& rightStop = *(stop);
KoColor startDummy, endDummy;
if (mixSpace){
startDummy = KoColor(leftStop.second, mixSpace);
endDummy = KoColor(rightStop.second, mixSpace);
} else {
startDummy = leftStop.second;
endDummy = rightStop.second;
}
const quint8 *colors[2];
colors[0] = startDummy.data();
colors[1] = endDummy.data();
qreal localT;
qreal stopDistance = rightStop.first - leftStop.first;
if (stopDistance < DBL_EPSILON) {
localT = 0.5;
} else {
localT = (t - leftStop.first) / stopDistance;
}
qint16 colorWeights[2];
colorWeights[0] = static_cast<quint8>((1.0 - localT) * 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(colorSpace());
colorSpace()->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
}
dst.fromKoColor(buffer);
}
}
-KoStopGradient * KoStopGradient::fromQGradient(QGradient * gradient)
+KoStopGradient * KoStopGradient::fromQGradient(const QGradient * gradient)
{
if (! gradient)
return 0;
KoStopGradient * newGradient = new KoStopGradient(QString());
newGradient->setType(gradient->type());
newGradient->setSpread(gradient->spread());
switch (gradient->type()) {
case QGradient::LinearGradient: {
- QLinearGradient * g = static_cast<QLinearGradient*>(gradient);
+ const QLinearGradient * g = static_cast<const QLinearGradient*>(gradient);
newGradient->m_start = g->start();
newGradient->m_stop = g->finalStop();
newGradient->m_focalPoint = g->start();
break;
}
case QGradient::RadialGradient: {
- QRadialGradient * g = static_cast<QRadialGradient*>(gradient);
+ const QRadialGradient * g = static_cast<const QRadialGradient*>(gradient);
newGradient->m_start = g->center();
newGradient->m_stop = g->center() + QPointF(g->radius(), 0);
newGradient->m_focalPoint = g->focalPoint();
break;
}
case QGradient::ConicalGradient: {
- QConicalGradient * g = static_cast<QConicalGradient*>(gradient);
+ const QConicalGradient * g = static_cast<const QConicalGradient*>(gradient);
qreal radian = g->angle() * M_PI / 180.0;
newGradient->m_start = g->center();
newGradient->m_stop = QPointF(100.0 * cos(radian), 100.0 * sin(radian));
newGradient->m_focalPoint = g->center();
break;
}
default:
delete newGradient;
return 0;
}
Q_FOREACH (const QGradientStop & stop, gradient->stops()) {
KoColor color(newGradient->colorSpace());
color.fromQColor(stop.second);
newGradient->m_stops.append(KoGradientStop(stop.first, color));
}
+ newGradient->setValid(true);
+
return newGradient;
}
void KoStopGradient::setStops(QList< KoGradientStop > stops)
{
m_stops.clear();
KoColor color;
Q_FOREACH (const KoGradientStop & stop, stops) {
color = stop.second;
color.convertTo(colorSpace());
m_stops.append(KoGradientStop(stop.first, color));
}
updatePreview();
}
QList<KoGradientStop> KoStopGradient::stops() const
{
return m_stops;
}
void KoStopGradient::loadKarbonGradient(QIODevice *file)
{
QDomDocument doc;
if (!(doc.setContent(file))) {
file->close();
setValid(false);
return;
}
QDomElement e;
QDomNode n = doc.documentElement().firstChild();
if (!n.isNull()) {
e = n.toElement();
if (!e.isNull() && e.tagName() == "GRADIENT") {
parseKarbonGradient(e);
}
}
}
void KoStopGradient::loadSvgGradient(QIODevice *file)
{
QDomDocument doc;
if (!(doc.setContent(file)))
file->close();
else {
for (QDomNode n = doc.documentElement().firstChild(); !n.isNull(); n = n.nextSibling()) {
QDomElement e = n.toElement();
if (e.isNull()) continue;
if (e.tagName() == "linearGradient" || e.tagName() == "radialGradient") {
parseSvgGradient(e);
return;
}
// Inkscape gradients are in another defs
if (e.tagName() == "defs") {
for (QDomNode defnode = e.firstChild(); !defnode.isNull(); defnode = defnode.nextSibling()) {
QDomElement defelement = defnode.toElement();
if (defelement.isNull()) continue;
if (defelement.tagName() == "linearGradient" || defelement.tagName() == "radialGradient") {
parseSvgGradient(defelement);
return;
}
}
}
}
}
}
void KoStopGradient::parseKarbonGradient(const QDomElement& element)
{
m_start = QPointF(element.attribute("originX", "0.0").toDouble(), element.attribute("originY", "0.0").toDouble());
m_focalPoint = QPointF(element.attribute("focalX", "0.0").toDouble(), element.attribute("focalY", "0.0").toDouble());
m_stop = QPointF(element.attribute("vectorX", "0.0").toDouble(), element.attribute("vectorY", "0.0").toDouble());
setType((QGradient::Type)element.attribute("type", 0).toInt());
setSpread((QGradient::Spread)element.attribute("repeatMethod", 0).toInt());
m_stops.clear();
qreal color1, color2, color3, color4, opacity;
KoColor color;
// load stops
QDomNodeList list = element.childNodes();
for (int i = 0; i < list.count(); ++i) {
if (list.item(i).isElement()) {
QDomElement colorstop = list.item(i).toElement();
if (colorstop.tagName() == "COLORSTOP") {
QDomElement e = colorstop.firstChild().toElement();
opacity = e.attribute("opacity", "1.0").toFloat();
QColor tmpColor;
const KoColorSpace* stopColorSpace;
switch (e.attribute("colorSpace").toUShort()) {
case 1: // cmyk
color1 = e.attribute("v1", "0.0").toFloat();
color2 = e.attribute("v2", "0.0").toFloat();
color3 = e.attribute("v3", "0.0").toFloat();
color4 = e.attribute("v4", "0.0").toFloat();
stopColorSpace = KoColorSpaceRegistry::instance()->colorSpace( CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString());
if (stopColorSpace) {
quint8 data[5];
data[0] = static_cast<quint8>(color1 * 255 + 0.5);
data[1] = static_cast<quint8>(color2 * 255 + 0.5);
data[2] = static_cast<quint8>(color3 * 255 + 0.5);
data[3] = static_cast<quint8>(color4 * 255 + 0.5);
data[4] = static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5);
color.setColor(data, stopColorSpace);
} else {
// cmyk colorspace not found fallback to rgb
color.convertTo(KoColorSpaceRegistry::instance()->rgb8());
tmpColor.setCmykF(color1, color2, color3, color4);
tmpColor.setAlpha(static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5));
color.fromQColor(tmpColor);
}
break;
case 2: // hsv
color1 = e.attribute("v1", "0.0").toFloat();
color2 = e.attribute("v2", "0.0").toFloat();
color3 = e.attribute("v3", "0.0").toFloat();
color.convertTo(KoColorSpaceRegistry::instance()->rgb8());
tmpColor.setHsvF(color1, color2, color3);
tmpColor.setAlpha(static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5));
color.fromQColor(tmpColor);
break;
case 3: // gray
color1 = e.attribute("v1", "0.0").toFloat();
stopColorSpace = KoColorSpaceRegistry::instance()->colorSpace( GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), QString());
if (stopColorSpace) {
quint8 data[2];
data[0] = static_cast<quint8>(color1 * 255 + 0.5);
data[1] = static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5);
color.setColor(data, stopColorSpace);
} else {
// gray colorspace not found fallback to rgb
color.convertTo(KoColorSpaceRegistry::instance()->rgb8());
tmpColor.setRgbF(color1, color1, color1);
tmpColor.setAlpha(static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5));
color.fromQColor(tmpColor);
}
break;
default: // rgb
color1 = e.attribute("v1", "0.0").toFloat();
color2 = e.attribute("v2", "0.0").toFloat();
color3 = e.attribute("v3", "0.0").toFloat();
stopColorSpace = KoColorSpaceRegistry::instance()->rgb8();
quint8 data[4];
data[2] = static_cast<quint8>(color1 * 255 + 0.5);
data[1] = static_cast<quint8>(color2 * 255 + 0.5);
data[0] = static_cast<quint8>(color3 * 255 + 0.5);
data[3] = static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5);
color.setColor(data, stopColorSpace);
}
qreal offset = colorstop.attribute("ramppoint", "0.0").toFloat();
// midpoint = colorstop.attribute("midpoint", "0.5").toFloat();
m_stops.append(KoGradientStop(offset, color));
}
}
}
}
void KoStopGradient::parseSvgGradient(const QDomElement& element)
{
m_stops.clear();
setSpread(QGradient::PadSpread);
/*QString href = e.attribute( "xlink:href" ).mid( 1 );
if( !href.isEmpty() )
{
}*/
setName(element.attribute("id", i18n("SVG Gradient")));
const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8();
bool bbox = element.attribute("gradientUnits") != "userSpaceOnUse";
if (element.tagName() == "linearGradient") {
if (bbox) {
QString s;
s = element.attribute("x1", "0%");
qreal xOrigin;
if (s.endsWith('%'))
xOrigin = s.remove('%').toDouble();
else
xOrigin = s.toDouble() * 100.0;
s = element.attribute("y1", "0%");
qreal yOrigin;
if (s.endsWith('%'))
yOrigin = s.remove('%').toDouble();
else
yOrigin = s.toDouble() * 100.0;
s = element.attribute("x2", "100%");
qreal xVector;
if (s.endsWith('%'))
xVector = s.remove('%').toDouble();
else
xVector = s.toDouble() * 100.0;
s = element.attribute("y2", "0%");
qreal yVector;
if (s.endsWith('%'))
yVector = s.remove('%').toDouble();
else
yVector = s.toDouble() * 100.0;
m_start = QPointF(xOrigin, yOrigin);
m_stop = QPointF(xVector, yVector);
} else {
m_start = QPointF(element.attribute("x1").toDouble(), element.attribute("y1").toDouble());
m_stop = QPointF(element.attribute("x2").toDouble(), element.attribute("y2").toDouble());
}
setType(QGradient::LinearGradient);
} else {
if (bbox) {
QString s;
s = element.attribute("cx", "50%");
qreal xOrigin;
if (s.endsWith('%'))
xOrigin = s.remove('%').toDouble();
else
xOrigin = s.toDouble() * 100.0;
s = element.attribute("cy", "50%");
qreal yOrigin;
if (s.endsWith('%'))
yOrigin = s.remove('%').toDouble();
else
yOrigin = s.toDouble() * 100.0;
s = element.attribute("cx", "50%");
qreal xVector;
if (s.endsWith('%'))
xVector = s.remove('%').toDouble();
else
xVector = s.toDouble() * 100.0;
s = element.attribute("r", "50%");
if (s.endsWith('%'))
xVector += s.remove('%').toDouble();
else
xVector += s.toDouble() * 100.0;
s = element.attribute("cy", "50%");
qreal yVector;
if (s.endsWith('%'))
yVector = s.remove('%').toDouble();
else
yVector = s.toDouble() * 100.0;
s = element.attribute("fx", "50%");
qreal xFocal;
if (s.endsWith('%'))
xFocal = s.remove('%').toDouble();
else
xFocal = s.toDouble() * 100.0;
s = element.attribute("fy", "50%");
qreal yFocal;
if (s.endsWith('%'))
yFocal = s.remove('%').toDouble();
else
yFocal = s.toDouble() * 100.0;
m_start = QPointF(xOrigin, yOrigin);
m_stop = QPointF(xVector, yVector);
m_focalPoint = QPointF(xFocal, yFocal);
} else {
m_start = QPointF(element.attribute("cx").toDouble(), element.attribute("cy").toDouble());
m_stop = QPointF(element.attribute("cx").toDouble() + element.attribute("r").toDouble(),
element.attribute("cy").toDouble());
m_focalPoint = QPointF(element.attribute("fx").toDouble(), element.attribute("fy").toDouble());
}
setType(QGradient::RadialGradient);
}
// handle spread method
QString spreadMethod = element.attribute("spreadMethod");
if (!spreadMethod.isEmpty()) {
if (spreadMethod == "reflect")
setSpread(QGradient::ReflectSpread);
else if (spreadMethod == "repeat")
setSpread(QGradient::RepeatSpread);
}
for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling()) {
QDomElement colorstop = n.toElement();
if (colorstop.tagName() == "stop") {
qreal opacity = 0.0;
QColor c;
float off;
QString temp = colorstop.attribute("offset");
if (temp.contains('%')) {
temp = temp.left(temp.length() - 1);
off = temp.toFloat() / 100.0;
} else
off = temp.toFloat();
if (!colorstop.attribute("stop-color").isEmpty())
parseSvgColor(c, colorstop.attribute("stop-color"));
else {
// try style attr
QString style = colorstop.attribute("style").simplified();
QStringList substyles = style.split(';', QString::SkipEmptyParts);
Q_FOREACH (const QString & s, substyles) {
QStringList substyle = s.split(':');
QString command = substyle[0].trimmed();
QString params = substyle[1].trimmed();
if (command == "stop-color")
parseSvgColor(c, params);
if (command == "stop-opacity")
opacity = params.toDouble();
}
}
if (!colorstop.attribute("stop-opacity").isEmpty())
opacity = colorstop.attribute("stop-opacity").toDouble();
KoColor color(rgbColorSpace);
color.fromQColor(c);
color.setOpacity(static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5));
//According to the SVG spec each gradient offset has to be equal to or greater than the previous one
//if not it needs to be adjusted to be equal
if (m_stops.count() > 0 && m_stops.last().first >= off) {
off = m_stops.last().first;
}
m_stops.append(KoGradientStop(off, color));
}
}
}
void KoStopGradient::parseSvgColor(QColor &color, const QString &s)
{
if (s.startsWith("rgb(")) {
QString parse = s.trimmed();
QStringList colors = parse.split(',');
QString r = colors[0].right((colors[0].length() - 4));
QString g = colors[1];
QString b = colors[2].left((colors[2].length() - 1));
if (r.contains('%')) {
r = r.left(r.length() - 1);
r = QString::number(int((qreal(255 * r.toDouble()) / 100.0)));
}
if (g.contains('%')) {
g = g.left(g.length() - 1);
g = QString::number(int((qreal(255 * g.toDouble()) / 100.0)));
}
if (b.contains('%')) {
b = b.left(b.length() - 1);
b = QString::number(int((qreal(255 * b.toDouble()) / 100.0)));
}
color = QColor(r.toInt(), g.toInt(), b.toInt());
} else {
QString rgbColor = s.trimmed();
QColor c;
if (rgbColor.startsWith('#'))
c.setNamedColor(rgbColor);
else {
c = QColor(rgbColor);
}
color = c;
}
}
QString KoStopGradient::defaultFileExtension() const
{
return QString(".svg");
}
bool KoStopGradient::saveToDevice(QIODevice *dev) const
{
QTextStream stream(dev);
const QString spreadMethod[3] = {
QString("spreadMethod=\"pad\" "),
QString("spreadMethod=\"reflect\" "),
QString("spreadMethod=\"repeat\" ")
};
const QString indent = " ";
stream << "<svg>" << endl;
stream << indent;
stream << "<linearGradient id=\"" << name() << "\" ";
stream << "gradientUnits=\"objectBoundingBox\" ";
stream << spreadMethod[spread()];
stream << ">" << endl;
QColor color;
// color stops
Q_FOREACH (const KoGradientStop & stop, m_stops) {
stop.second.toQColor(&color);
stream << indent << indent;
stream << "<stop stop-color=\"";
stream << color.name();
stream << "\" offset=\"" << QString().setNum(stop.first);
stream << "\" stop-opacity=\"" << static_cast<float>(color.alpha()) / 255.0f << "\"" << " />" << endl;
}
stream << indent;
stream << "</linearGradient>" << endl;
stream << "</svg>" << endl;
KoResource::saveToDevice(dev);
return true;
}
diff --git a/libs/pigment/resources/KoStopGradient.h b/libs/pigment/resources/KoStopGradient.h
index 609235f817..de4aa419cf 100644
--- a/libs/pigment/resources/KoStopGradient.h
+++ b/libs/pigment/resources/KoStopGradient.h
@@ -1,83 +1,82 @@
/*
Copyright (c) 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
*/
#ifndef KOSTOPGRADIENT_H
#define KOSTOPGRADIENT_H
#include <QPair>
#include <QGradient>
#include "KoColor.h"
#include <resources/KoAbstractGradient.h>
#include <resources/KoResource.h>
#include <kritapigment_export.h>
-
typedef QPair<qreal, KoColor> KoGradientStop;
/**
* Resource for colorstop based gradients like Karbon gradients and SVG gradients
*/
class KRITAPIGMENT_EXPORT KoStopGradient : public KoAbstractGradient
{
public:
explicit KoStopGradient(const QString &filename = QString());
virtual ~KoStopGradient();
KoAbstractGradient* clone() const;
virtual bool load();
virtual bool loadFromDevice(QIODevice *dev);
virtual bool save();
virtual bool saveToDevice(QIODevice* dev) const;
/// reimplemented
virtual QGradient* toQGradient() const;
/// reimplemented
void colorAt(KoColor&, qreal t) const;
/// Creates KoStopGradient from a QGradient
- static KoStopGradient * fromQGradient(QGradient * gradient);
+ static KoStopGradient * fromQGradient(const QGradient * gradient);
/// Sets the gradient stops
void setStops(QList<KoGradientStop> stops);
QList<KoGradientStop> stops() const;
/// reimplemented
QString defaultFileExtension() const;
protected:
QList<KoGradientStop> m_stops;
QPointF m_start;
QPointF m_stop;
QPointF m_focalPoint;
private:
void loadKarbonGradient(QIODevice *file);
void parseKarbonGradient(const QDomElement& element);
void loadSvgGradient(QIODevice *file);
void parseSvgGradient(const QDomElement& element);
void parseSvgColor(QColor &color, const QString &s);
};
#endif // KOSTOPGRADIENT_H
diff --git a/libs/pigment/tests/TestKoColorSpaceRegistry.cpp b/libs/pigment/tests/TestKoColorSpaceRegistry.cpp
index 9a6fcb9436..6ced82e135 100644
--- a/libs/pigment/tests/TestKoColorSpaceRegistry.cpp
+++ b/libs/pigment/tests/TestKoColorSpaceRegistry.cpp
@@ -1,55 +1,68 @@
/*
* 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 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 "TestKoColorSpaceRegistry.h"
#include <QTest>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
+#include <KoColorProfile.h>
TestColorSpaceRegistry::TestColorSpaceRegistry()
{
}
void TestColorSpaceRegistry::testLab16()
{
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->lab16();
QCOMPARE(cs->colorModelId().id(), LABAColorModelID.id());
QCOMPARE(cs->colorDepthId().id(), Integer16BitsColorDepthID.id());
QVERIFY(*cs == *KoColorSpaceRegistry::instance()->colorSpace(LABAColorModelID.id(), Integer16BitsColorDepthID.id(), 0));
}
void TestColorSpaceRegistry::testRgb8()
{
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
QCOMPARE(cs->colorModelId().id(), RGBAColorModelID.id());
QCOMPARE(cs->colorDepthId().id(), Integer8BitsColorDepthID.id());
QVERIFY(*cs == *KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0));
}
void TestColorSpaceRegistry::testRgb16()
{
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb16();
QCOMPARE(cs->colorModelId().id(), RGBAColorModelID.id());
QCOMPARE(cs->colorDepthId().id(), Integer16BitsColorDepthID.id());
QVERIFY(*cs == *KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), 0));
}
+void TestColorSpaceRegistry::testProfileByUniqueId()
+{
+ const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb16();
+ const KoColorProfile *profile = cs->profile();
+ QVERIFY(profile);
+
+ const KoColorProfile *fetchedProfile =
+ KoColorSpaceRegistry::instance()->profileByUniqueId(profile->uniqueId());
+
+ QCOMPARE(*fetchedProfile, *profile);
+}
+
QTEST_GUILESS_MAIN(TestColorSpaceRegistry)
diff --git a/libs/pigment/tests/TestKoColorSpaceRegistry.h b/libs/pigment/tests/TestKoColorSpaceRegistry.h
index 32da58f509..debad8e3e2 100644
--- a/libs/pigment/tests/TestKoColorSpaceRegistry.h
+++ b/libs/pigment/tests/TestKoColorSpaceRegistry.h
@@ -1,36 +1,37 @@
/*
* 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 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 _TEST_KO_COLOR_SPACE_REGISTRY_H_
#define _TEST_KO_COLOR_SPACE_REGISTRY_H_
#include <QObject>
class TestColorSpaceRegistry : public QObject
{
Q_OBJECT
public:
TestColorSpaceRegistry();
private Q_SLOTS:
void testLab16();
void testRgb8();
void testRgb16();
+ void testProfileByUniqueId();
};
#endif
diff --git a/libs/store/CMakeLists.txt b/libs/store/CMakeLists.txt
index b20d442ad1..08d6323c34 100644
--- a/libs/store/CMakeLists.txt
+++ b/libs/store/CMakeLists.txt
@@ -1,23 +1,23 @@
add_subdirectory(tests)
set(kritastore_LIB_SRCS
KoDirectoryStore.cpp
KoLZF.cpp
KoStore.cpp
KoXmlNS.cpp
KoXmlReader.cpp
KoXmlWriter.cpp
KoZipStore.cpp
StoreDebug.cpp
)
add_library(kritastore SHARED ${kritastore_LIB_SRCS})
generate_export_header(kritastore BASE_NAME kritastore)
-target_link_libraries(kritastore kritaversion Qt5::Xml Qt5::Gui KF5::Archive)
+target_link_libraries(kritastore kritaversion kritaglobal Qt5::Xml Qt5::Gui KF5::Archive)
set_target_properties(kritastore PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritastore ${INSTALL_TARGETS_DEFAULT_ARGS} )
diff --git a/libs/store/KoXmlNS.cpp b/libs/store/KoXmlNS.cpp
index 7294584ef2..ead3bac7f5 100644
--- a/libs/store/KoXmlNS.cpp
+++ b/libs/store/KoXmlNS.cpp
@@ -1,116 +1,118 @@
/* This file is part of the KDE project
Copyright (C) 2004 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoXmlNS.h"
#include <string.h>
const QString KoXmlNS::office("urn:oasis:names:tc:opendocument:xmlns:office:1.0");
const QString KoXmlNS::meta("urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
const QString KoXmlNS::config("urn:oasis:names:tc:opendocument:xmlns:config:1.0");
const QString KoXmlNS::text("urn:oasis:names:tc:opendocument:xmlns:text:1.0");
const QString KoXmlNS::table("urn:oasis:names:tc:opendocument:xmlns:table:1.0");
const QString KoXmlNS::draw("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
const QString KoXmlNS::presentation("urn:oasis:names:tc:opendocument:xmlns:presentation:1.0");
const QString KoXmlNS::dr3d("urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
const QString KoXmlNS::chart("urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
const QString KoXmlNS::form("urn:oasis:names:tc:opendocument:xmlns:form:1.0");
const QString KoXmlNS::script("urn:oasis:names:tc:opendocument:xmlns:script:1.0");
const QString KoXmlNS::style("urn:oasis:names:tc:opendocument:xmlns:style:1.0");
const QString KoXmlNS::number("urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
const QString KoXmlNS::manifest("urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
const QString KoXmlNS::anim("urn:oasis:names:tc:opendocument:xmlns:animation:1.0");
const QString KoXmlNS::math("http://www.w3.org/1998/Math/MathML");
const QString KoXmlNS::svg("urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
const QString KoXmlNS::fo("urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
const QString KoXmlNS::dc("http://purl.org/dc/elements/1.1/");
const QString KoXmlNS::xlink("http://www.w3.org/1999/xlink");
const QString KoXmlNS::VL("http://openoffice.org/2001/versions-list");
const QString KoXmlNS::smil("urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0");
const QString KoXmlNS::xhtml("http://www.w3.org/1999/xhtml");
const QString KoXmlNS::xml("http://www.w3.org/XML/1998/namespace");
+const QString KoXmlNS::sodipodi("http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd");
+const QString KoXmlNS::krita("http://krita.org/namespaces/svg/krita");
const QString KoXmlNS::calligra = "http://www.calligra.org/2005/";
const QString KoXmlNS::officeooo = "http://openoffice.org/2009/office";
const QString KoXmlNS::ooo = "http://openoffice.org/2004/office";
const QString KoXmlNS::delta("http://www.deltaxml.com/ns/track-changes/delta-namespace");
const QString KoXmlNS::split("http://www.deltaxml.com/ns/track-changes/split-namespace");
const QString KoXmlNS::ac("http://www.deltaxml.com/ns/track-changes/attribute-change-namespace");
const char* KoXmlNS::nsURI2NS(const QString &nsURI)
{
if (nsURI == KoXmlNS::office)
return "office";
else if (nsURI == KoXmlNS::meta)
return "meta";
else if (nsURI == KoXmlNS::config)
return "config";
else if (nsURI == KoXmlNS::text)
return "text";
else if (nsURI == KoXmlNS::table)
return "table";
else if (nsURI == KoXmlNS::draw)
return "draw";
else if (nsURI == KoXmlNS::presentation)
return "presentation";
else if (nsURI == KoXmlNS::dr3d)
return "dr3d";
else if (nsURI == KoXmlNS::chart)
return "chart";
else if (nsURI == KoXmlNS::form)
return "form";
else if (nsURI == KoXmlNS::script)
return "script";
else if (nsURI == KoXmlNS::style)
return "style";
else if (nsURI == KoXmlNS::number)
return "number";
else if (nsURI == KoXmlNS::manifest)
return "manifest";
else if (nsURI == KoXmlNS::anim)
return "anim";
else if (nsURI == KoXmlNS::math)
return "math";
else if (nsURI == KoXmlNS::svg)
return "svg";
else if (nsURI == KoXmlNS::fo)
return "fo";
else if (nsURI == KoXmlNS::dc)
return "dc";
else if (nsURI == KoXmlNS::xlink)
return "xlink";
else if (nsURI == KoXmlNS::VL)
return "VL";
else if (nsURI == KoXmlNS::smil)
return "smil";
else if (nsURI == KoXmlNS::xhtml)
return "xhtml";
else if (nsURI == KoXmlNS::calligra)
return "calligra";
else if (nsURI == KoXmlNS::officeooo)
return "officeooo";
else if (nsURI == KoXmlNS::xml)
return "xml";
// Shouldn't happen.
return "";
}
diff --git a/libs/store/KoXmlNS.h b/libs/store/KoXmlNS.h
index 309b075162..c120d32e18 100644
--- a/libs/store/KoXmlNS.h
+++ b/libs/store/KoXmlNS.h
@@ -1,74 +1,76 @@
/* This file is part of the KDE project
Copyright (C) 2004 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOXMLNS_H
#define KOXMLNS_H
#include <QString>
#include "kritastore_export.h"
/**
* Repository of XML namespaces used for ODF documents.
*
* Please make sure that you do not use the variables provided by this class in
* the destructor of a static object.
*/
class KRITASTORE_EXPORT KoXmlNS
{
public:
static const QString office;
static const QString meta;
static const QString config;
static const QString text;
static const QString table;
static const QString draw;
static const QString presentation;
static const QString dr3d;
static const QString chart;
static const QString form;
static const QString script;
static const QString style;
static const QString number;
static const QString manifest;
static const QString anim;
static const QString math;
static const QString svg;
static const QString fo;
static const QString dc;
static const QString xlink;
static const QString VL;
static const QString smil;
static const QString xhtml;
static const QString xml;
+ static const QString sodipodi;
+ static const QString krita;
static const QString calligra;
static const QString officeooo;
static const QString ooo;
static const char* nsURI2NS(const QString &nsURI);
static const QString delta;
static const QString split;
static const QString ac;
private:
KoXmlNS(); // don't create an instance of me :)
};
#endif /* KOXMLNS_H */
diff --git a/libs/store/KoXmlWriter.cpp b/libs/store/KoXmlWriter.cpp
index 2d41c73f1b..19e632903a 100644
--- a/libs/store/KoXmlWriter.cpp
+++ b/libs/store/KoXmlWriter.cpp
@@ -1,571 +1,569 @@
/* This file is part of the KDE project
Copyright (C) 2004 David Faure <faure@kde.org>
Copyright (C) 2007 Thomas Zander <zander@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoXmlWriter.h"
#include <StoreDebug.h>
#include <QByteArray>
#include <QStack>
#include <float.h>
+#include "../global/kis_dom_utils.h"
static const int s_indentBufferLength = 100;
static const int s_escapeBufferLen = 10000;
class Q_DECL_HIDDEN KoXmlWriter::Private
{
public:
Private(QIODevice* dev_, int indentLevel = 0) : dev(dev_), baseIndentLevel(indentLevel) {}
~Private() {
delete[] indentBuffer;
delete[] escapeBuffer;
//TODO: look at if we must delete "dev". For me we must delete it otherwise we will leak it
}
QIODevice* dev;
QStack<Tag> tags;
int baseIndentLevel;
char* indentBuffer; // maybe make it static, but then it needs a K_GLOBAL_STATIC
// and would eat 1K all the time... Maybe refcount it :)
char* escapeBuffer; // can't really be static if we want to be thread-safe
};
KoXmlWriter::KoXmlWriter(QIODevice* dev, int indentLevel)
: d(new Private(dev, indentLevel))
{
init();
}
void KoXmlWriter::init()
{
d->indentBuffer = new char[ s_indentBufferLength ];
memset(d->indentBuffer, ' ', s_indentBufferLength);
*d->indentBuffer = '\n'; // write newline before indentation, in one go
d->escapeBuffer = new char[s_escapeBufferLen];
if (!d->dev->isOpen())
d->dev->open(QIODevice::WriteOnly);
}
KoXmlWriter::~KoXmlWriter()
{
delete d;
}
void KoXmlWriter::startDocument(const char* rootElemName, const char* publicId, const char* systemId)
{
Q_ASSERT(d->tags.isEmpty());
writeCString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
// There isn't much point in a doctype if there's no DTD to refer to
// (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema)
if (publicId) {
writeCString("<!DOCTYPE ");
writeCString(rootElemName);
writeCString(" PUBLIC \"");
writeCString(publicId);
writeCString("\" \"");
writeCString(systemId);
writeCString("\"");
writeCString(">\n");
}
}
void KoXmlWriter::endDocument()
{
// just to do exactly like QDom does (newline at end of file).
writeChar('\n');
Q_ASSERT(d->tags.isEmpty());
}
// returns the value of indentInside of the parent
bool KoXmlWriter::prepareForChild()
{
if (!d->tags.isEmpty()) {
Tag& parent = d->tags.top();
if (!parent.hasChildren) {
closeStartElement(parent);
parent.hasChildren = true;
parent.lastChildIsText = false;
}
if (parent.indentInside) {
writeIndent();
}
return parent.indentInside;
}
return true;
}
void KoXmlWriter::prepareForTextNode()
{
if (d->tags.isEmpty())
return;
Tag& parent = d->tags.top();
if (!parent.hasChildren) {
closeStartElement(parent);
parent.hasChildren = true;
parent.lastChildIsText = true;
}
}
void KoXmlWriter::startElement(const char* tagName, bool indentInside)
{
Q_ASSERT(tagName != 0);
// Tell parent that it has children
bool parentIndent = prepareForChild();
d->tags.push(Tag(tagName, parentIndent && indentInside));
writeChar('<');
writeCString(tagName);
//kDebug(s_area) << tagName;
}
void KoXmlWriter::addCompleteElement(const char* cstr)
{
prepareForChild();
writeCString(cstr);
}
void KoXmlWriter::addCompleteElement(QIODevice* indev)
{
prepareForChild();
const bool wasOpen = indev->isOpen();
// Always (re)open the device in readonly mode, it might be
// already open but for writing, and we need to rewind.
const bool openOk = indev->open(QIODevice::ReadOnly);
Q_ASSERT(openOk);
if (!openOk) {
warnStore << "Failed to re-open the device! wasOpen=" << wasOpen;
return;
}
- static const int MAX_CHUNK_SIZE = 8 * 1024; // 8 KB
+ QString indentString;
+ indentString.fill((' '), indentLevel());
+ QByteArray indentBuf(indentString.toUtf8());
+
QByteArray buffer;
- buffer.resize(MAX_CHUNK_SIZE);
while (!indev->atEnd()) {
- qint64 len = indev->read(buffer.data(), buffer.size());
- if (len <= 0) // e.g. on error
- break;
- d->dev->write(buffer.data(), len);
+ buffer = indev->readLine();
+
+ d->dev->write(indentBuf);
+ d->dev->write(buffer);
}
+
if (!wasOpen) {
// Restore initial state
indev->close();
}
}
void KoXmlWriter::endElement()
{
if (d->tags.isEmpty())
warnStore << "EndElement() was called more times than startElement(). "
"The generated XML will be invalid! "
"Please report this bug (by saving the document to another format...)" << endl;
Tag tag = d->tags.pop();
if (!tag.hasChildren) {
writeCString("/>");
} else {
if (tag.indentInside && !tag.lastChildIsText) {
writeIndent();
}
writeCString("</");
Q_ASSERT(tag.tagName != 0);
writeCString(tag.tagName);
writeChar('>');
}
}
void KoXmlWriter::addTextNode(const QByteArray& cstr)
{
// Same as the const char* version below, but here we know the size
prepareForTextNode();
char* escaped = escapeForXML(cstr.constData(), cstr.size());
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
}
void KoXmlWriter::addTextNode(const char* cstr)
{
prepareForTextNode();
char* escaped = escapeForXML(cstr, -1);
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
}
void KoXmlWriter::addProcessingInstruction(const char* cstr)
{
prepareForTextNode();
writeCString("<?");
addTextNode(cstr);
writeCString("?>");
}
void KoXmlWriter::addAttribute(const char* attrName, const QByteArray& value)
{
// Same as the const char* one, but here we know the size
writeChar(' ');
writeCString(attrName);
writeCString("=\"");
char* escaped = escapeForXML(value.constData(), value.size());
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
writeChar('"');
}
void KoXmlWriter::addAttribute(const char* attrName, const char* value)
{
writeChar(' ');
writeCString(attrName);
writeCString("=\"");
char* escaped = escapeForXML(value, -1);
writeCString(escaped);
if (escaped != d->escapeBuffer)
delete[] escaped;
writeChar('"');
}
void KoXmlWriter::addAttribute(const char* attrName, double value)
{
- QByteArray str;
- str.setNum(value, 'f', 11);
- addAttribute(attrName, str.data());
+ addAttribute(attrName, KisDomUtils::toString(value));
}
void KoXmlWriter::addAttribute(const char* attrName, float value)
{
- QByteArray str;
- str.setNum(value, 'f', FLT_DIG);
- addAttribute(attrName, str.data());
+ addAttribute(attrName, KisDomUtils::toString(value));
}
void KoXmlWriter::addAttributePt(const char* attrName, double value)
{
- QByteArray str;
- str.setNum(value, 'f', 11);
- str += "pt";
- addAttribute(attrName, str.data());
+ // WARNING: we don't write 'pt' into SVG anymore! We just use
+ // viewBox to align coordinates system with 'pt'.
+ addAttribute(attrName, KisDomUtils::toString(value));
}
void KoXmlWriter::addAttributePt(const char* attrName, float value)
{
- QByteArray str;
- str.setNum(value, 'f', FLT_DIG);
- str += "pt";
- addAttribute(attrName, str.data());
+ // WARNING: we don't write 'pt' into SVG anymore! We just use
+ // viewBox to align coordinates system with 'pt'.
+ addAttribute(attrName, KisDomUtils::toString(value));
}
void KoXmlWriter::writeIndent()
{
// +1 because of the leading '\n'
d->dev->write(d->indentBuffer, qMin(indentLevel() + 1,
s_indentBufferLength));
}
void KoXmlWriter::writeString(const QString& str)
{
// cachegrind says .utf8() is where most of the time is spent
const QByteArray cstr = str.toUtf8();
d->dev->write(cstr);
}
// In case of a reallocation (ret value != d->buffer), the caller owns the return value,
// it must delete it (with [])
char* KoXmlWriter::escapeForXML(const char* source, int length = -1) const
{
// we're going to be pessimistic on char length; so lets make the outputLength less
// the amount one char can take: 6
char* destBoundary = d->escapeBuffer + s_escapeBufferLen - 6;
char* destination = d->escapeBuffer;
char* output = d->escapeBuffer;
const char* src = source; // src moves, source remains
for (;;) {
if (destination >= destBoundary) {
// When we come to realize that our escaped string is going to
// be bigger than the escape buffer (this shouldn't happen very often...),
// we drop the idea of using it, and we allocate a bigger buffer.
// Note that this if() can only be hit once per call to the method.
if (length == -1)
length = qstrlen(source); // expensive...
uint newLength = length * 6 + 1; // worst case. 6 is due to &quot; and &apos;
char* buffer = new char[ newLength ];
destBoundary = buffer + newLength;
uint amountOfCharsAlreadyCopied = destination - d->escapeBuffer;
memcpy(buffer, d->escapeBuffer, amountOfCharsAlreadyCopied);
output = buffer;
destination = buffer + amountOfCharsAlreadyCopied;
}
switch (*src) {
case 60: // <
memcpy(destination, "&lt;", 4);
destination += 4;
break;
case 62: // >
memcpy(destination, "&gt;", 4);
destination += 4;
break;
case 34: // "
memcpy(destination, "&quot;", 6);
destination += 6;
break;
#if 0 // needed?
case 39: // '
memcpy(destination, "&apos;", 6);
destination += 6;
break;
#endif
case 38: // &
memcpy(destination, "&amp;", 5);
destination += 5;
break;
case 0:
*destination = '\0';
return output;
// Control codes accepted in XML 1.0 documents.
case 9:
case 10:
case 13:
*destination++ = *src++;
continue;
default:
// Don't add control codes not accepted in XML 1.0 documents.
if (*src > 0 && *src < 32) {
++src;
} else {
*destination++ = *src++;
}
continue;
}
++src;
}
// NOTREACHED (see case 0)
return output;
}
void KoXmlWriter::addManifestEntry(const QString& fullPath, const QString& mediaType)
{
startElement("manifest:file-entry");
addAttribute("manifest:media-type", mediaType);
addAttribute("manifest:full-path", fullPath);
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, const QString& value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "string");
addTextNode(value);
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, bool value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "boolean");
addTextNode(value ? "true" : "false");
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, int value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "int");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, double value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "double");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, float value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "double");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, long value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "long");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addConfigItem(const QString & configName, short value)
{
startElement("config:config-item");
addAttribute("config:name", configName);
addAttribute("config:type", "short");
addTextNode(QString::number(value));
endElement();
}
void KoXmlWriter::addTextSpan(const QString& text)
{
QMap<int, int> tabCache;
addTextSpan(text, tabCache);
}
void KoXmlWriter::addTextSpan(const QString& text, const QMap<int, int>& tabCache)
{
int len = text.length();
int nrSpaces = 0; // number of consecutive spaces
bool leadingSpace = false;
QString str;
str.reserve(len);
// Accumulate chars either in str or in nrSpaces (for spaces).
// Flush str when writing a subelement (for spaces or for another reason)
// Flush nrSpaces when encountering two or more consecutive spaces
for (int i = 0; i < len ; ++i) {
QChar ch = text[i];
ushort unicode = ch.unicode();
if (unicode == ' ') {
if (i == 0)
leadingSpace = true;
++nrSpaces;
} else {
if (nrSpaces > 0) {
// For the first space we use ' '.
// "it is good practice to use (text:s) for the second and all following SPACE
// characters in a sequence." (per the ODF spec)
// however, per the HTML spec, "authors should not rely on user agents to render
// white space immediately after a start tag or immediately before an end tag"
// (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...)
if (!leadingSpace) {
str += ' ';
--nrSpaces;
}
if (nrSpaces > 0) { // there are more spaces
if (!str.isEmpty())
addTextNode(str);
str.clear();
startElement("text:s");
if (nrSpaces > 1) // it's 1 by default
addAttribute("text:c", nrSpaces);
endElement();
}
}
nrSpaces = 0;
leadingSpace = false;
switch (unicode) {
case '\t':
if (!str.isEmpty())
addTextNode(str);
str.clear();
startElement("text:tab");
if (tabCache.contains(i))
addAttribute("text:tab-ref", tabCache[i] + 1);
endElement();
break;
// gracefully handle \f form feed in text input.
// otherwise the xml will not be valid.
// \f can be added e.g. in ascii import filter.
case '\f':
case '\n':
case QChar::LineSeparator:
if (!str.isEmpty())
addTextNode(str);
str.clear();
startElement("text:line-break");
endElement();
break;
default:
// don't add stuff that is not allowed in xml. The stuff we need we have already handled above
if (ch.unicode() >= 0x20) {
str += text[i];
}
break;
}
}
}
// either we still have text in str or we have spaces in nrSpaces
if (!str.isEmpty()) {
addTextNode(str);
}
if (nrSpaces > 0) { // there are more spaces
startElement("text:s");
if (nrSpaces > 1) // it's 1 by default
addAttribute("text:c", nrSpaces);
endElement();
}
}
QIODevice *KoXmlWriter::device() const
{
return d->dev;
}
int KoXmlWriter::indentLevel() const
{
return d->tags.size() + d->baseIndentLevel;
}
QList<const char*> KoXmlWriter::tagHierarchy() const
{
QList<const char*> answer;
Q_FOREACH (const Tag & tag, d->tags)
answer.append(tag.tagName);
return answer;
}
QString KoXmlWriter::toString() const
{
Q_ASSERT(!d->dev->isSequential());
if (d->dev->isSequential())
return QString();
bool wasOpen = d->dev->isOpen();
qint64 oldPos = -1;
if (wasOpen) {
oldPos = d->dev->pos();
if (oldPos > 0)
d->dev->seek(0);
} else {
const bool openOk = d->dev->open(QIODevice::ReadOnly);
Q_ASSERT(openOk);
if (!openOk)
return QString();
}
QString s = QString::fromUtf8(d->dev->readAll());
if (wasOpen)
d->dev->seek(oldPos);
else
d->dev->close();
return s;
}
diff --git a/libs/store/KoZipStore.cpp b/libs/store/KoZipStore.cpp
index 8446d9c9d0..68b867af34 100644
--- a/libs/store/KoZipStore.cpp
+++ b/libs/store/KoZipStore.cpp
@@ -1,242 +1,273 @@
/* This file is part of the KDE project
Copyright (C) 2000-2002 David Faure <faure@kde.org>
Copyright (C) 2010 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 "KoZipStore.h"
#include "KoStore_p.h"
#include <QBuffer>
#include <QByteArray>
#include <QTemporaryFile>
#include <kzip.h>
#include <StoreDebug.h>
#include <QUrl>
+class SaveZip : public KZip {
+public:
+ SaveZip(const QString &filename) : KZip(filename) {}
+ SaveZip(QIODevice *dev) : KZip(dev) {}
+ virtual ~SaveZip() {}
+ void resetDevice() {
+ closeArchive();
+ setDevice(0);
+ }
+};
+
KoZipStore::KoZipStore(const QString & _filename, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
- debugStore << "KoZipStore Constructor filename =" << _filename
- << " mode = " << int(mode)
- << " mimetype = " << appIdentification << endl;
+// qDebug() << "KoZipStore Constructor filename =" << _filename
+// << " mode = " << int(mode)
+// << " mimetype = " << appIdentification;
Q_D(KoStore);
d->localFileName = _filename;
- m_pZip = new KZip(_filename);
+ m_pZip = new SaveZip(_filename);
init(appIdentification); // open the zip file and init some vars
}
KoZipStore::KoZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype)
: KoStore(mode, writeMimetype)
{
- m_pZip = new KZip(dev);
+// qDebug() << "KoZipStore Constructor device =" << dev
+// << " mode = " << int(mode)
+// << " mimetype = " << appIdentification;
+
+ m_pZip = new SaveZip(dev);
init(appIdentification);
}
KoZipStore::KoZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode mode,
const QByteArray & appIdentification, bool writeMimetype)
: KoStore(mode, writeMimetype)
{
debugStore << "KoZipStore Constructor url" << _url.url(QUrl::PreferLocalFile)
<< " filename = " << _filename
<< " mode = " << int(mode)
- << " mimetype = " << appIdentification << endl;
+ << " mimetype = " << appIdentification;
Q_D(KoStore);
d->url = _url;
d->window = window;
if (mode == KoStore::Read) {
d->localFileName = _filename;
} else {
QTemporaryFile f("kozip");
f.open();
d->localFileName = f.fileName();
f.close();
}
- m_pZip = new KZip(d->localFileName);
+ m_pZip = new SaveZip(d->localFileName);
init(appIdentification); // open the zip file and init some vars
}
KoZipStore::~KoZipStore()
{
Q_D(KoStore);
- debugStore << "KoZipStore::~KoZipStore";
- if (!d->finalized)
- finalize(); // ### no error checking when the app forgot to call finalize itself
+ bool sf = false;
+ if (m_pZip && m_pZip->device()) {
+ sf = true;
+ }
+
+// qDebug() << "KoZipStore::~KoZipStore" << d->localFileName << m_pZip << m_pZip->device() << "savefile" << sf;
+ if (m_pZip->device() && m_pZip->device()->inherits("QSaveFile")) {
+ m_pZip->resetDevice(); // otherwise, kzip's destructor will call close(), which aborts on a qsavefile
+ }
+ else {
+ if (!d->finalized) {
+ finalize(); // ### no error checking when the app forgot to call finalize itself
+ }
+ }
delete m_pZip;
// When writing, we write to a temp file that then gets copied over the original filename
if (d->mode == Write && (!d->localFileName.isEmpty() && !d->url.isEmpty())) {
QFile f(d->localFileName);
if (f.copy(d->url.toLocalFile())) {
f.remove();
}
}
}
void KoZipStore::init(const QByteArray& appIdentification)
{
Q_D(KoStore);
m_currentDir = 0;
d->good = m_pZip->open(d->mode == Write ? QIODevice::WriteOnly : QIODevice::ReadOnly);
if (!d->good)
return;
if (d->mode == Write) {
//debugStore <<"KoZipStore::init writing mimetype" << appIdentification;
m_pZip->setCompression(KZip::NoCompression);
m_pZip->setExtraField(KZip::NoExtraField);
// Write identification
if (d->writeMimetype) {
(void)m_pZip->writeFile(QLatin1String("mimetype"), appIdentification);
}
m_pZip->setCompression(KZip::DeflateCompression);
// We don't need the extra field in Krita - so we leave it as "no extra field".
} else {
d->good = m_pZip->directory() != 0;
}
}
void KoZipStore::setCompressionEnabled(bool e)
{
if (e) {
m_pZip->setCompression(KZip::DeflateCompression);
} else {
m_pZip->setCompression(KZip::NoCompression);
}
}
bool KoZipStore::doFinalize()
{
- return m_pZip->close();
+ if (m_pZip && m_pZip->device() && !m_pZip->device()->inherits("QSaveFile")) {
+ return m_pZip->close();
+ }
+ else {
+ return true;
+ }
}
bool KoZipStore::openWrite(const QString& name)
{
Q_D(KoStore);
d->stream = 0; // Don't use!
return m_pZip->prepareWriting(name, "", "" /*m_pZip->rootDir()->user(), m_pZip->rootDir()->group()*/, 0);
}
bool KoZipStore::openRead(const QString& name)
{
Q_D(KoStore);
const KArchiveEntry * entry = m_pZip->directory()->entry(name);
if (entry == 0) {
return false;
}
if (entry->isDirectory()) {
warnStore << name << " is a directory !";
return false;
}
// Must cast to KZipFileEntry, not only KArchiveFile, because device() isn't virtual!
const KZipFileEntry * f = static_cast<const KZipFileEntry *>(entry);
delete d->stream;
d->stream = f->createDevice();
d->size = f->size();
return true;
}
qint64 KoZipStore::write(const char* _data, qint64 _len)
{
Q_D(KoStore);
if (_len == 0) return 0;
//debugStore <<"KoZipStore::write" << _len;
if (!d->isOpen) {
errorStore << "KoStore: You must open before writing" << endl;
return 0;
}
if (d->mode != Write) {
errorStore << "KoStore: Can not write to store that is opened for reading" << endl;
return 0;
}
d->size += _len;
if (m_pZip->writeData(_data, _len)) // writeData returns a bool!
return _len;
return 0;
}
QStringList KoZipStore::directoryList() const
{
QStringList retval;
const KArchiveDirectory *directory = m_pZip->directory();
Q_FOREACH (const QString &name, directory->entries()) {
const KArchiveEntry* fileArchiveEntry = m_pZip->directory()->entry(name);
if (fileArchiveEntry->isDirectory()) {
retval << name;
}
}
return retval;
}
bool KoZipStore::closeWrite()
{
Q_D(KoStore);
debugStore << "Wrote file" << d->fileName << " into ZIP archive. size" << d->size;
return m_pZip->finishWriting(d->size);
}
bool KoZipStore::enterRelativeDirectory(const QString& dirName)
{
Q_D(KoStore);
if (d->mode == Read) {
if (!m_currentDir) {
m_currentDir = m_pZip->directory(); // initialize
Q_ASSERT(d->currentPath.isEmpty());
}
const KArchiveEntry *entry = m_currentDir->entry(dirName);
if (entry && entry->isDirectory()) {
m_currentDir = dynamic_cast<const KArchiveDirectory*>(entry);
return m_currentDir != 0;
}
return false;
} else // Write, no checking here
return true;
}
bool KoZipStore::enterAbsoluteDirectory(const QString& path)
{
if (path.isEmpty()) {
m_currentDir = 0;
return true;
}
m_currentDir = dynamic_cast<const KArchiveDirectory*>(m_pZip->directory()->entry(path));
Q_ASSERT(m_currentDir);
return m_currentDir != 0;
}
bool KoZipStore::fileExists(const QString& absPath) const
{
const KArchiveEntry *entry = m_pZip->directory()->entry(absPath);
return entry && entry->isFile();
}
diff --git a/libs/store/KoZipStore.h b/libs/store/KoZipStore.h
index 43579abd10..b45964d139 100644
--- a/libs/store/KoZipStore.h
+++ b/libs/store/KoZipStore.h
@@ -1,74 +1,73 @@
/* This file is part of the KDE project
Copyright (C) 2002 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef koZipStore_h
#define koZipStore_h
#include "KoStore.h"
-class KZip;
+class SaveZip;
class KArchiveDirectory;
class QUrl;
class KoZipStore : public KoStore
{
public:
KoZipStore(const QString & _filename, Mode _mode, const QByteArray & appIdentification,
bool writeMimetype = true);
KoZipStore(QIODevice *dev, Mode mode, const QByteArray & appIdentification,
bool writeMimetype = true);
/**
* QUrl-constructor
* @todo saving not completely implemented (fixed temporary file)
*/
KoZipStore(QWidget* window, const QUrl &_url, const QString & _filename, Mode _mode,
const QByteArray & appIdentification, bool writeMimetype = true);
~KoZipStore();
virtual void setCompressionEnabled(bool e);
virtual qint64 write(const char* _data, qint64 _len);
virtual QStringList directoryList() const;
protected:
void init(const QByteArray& appIdentification);
virtual bool doFinalize();
virtual bool openWrite(const QString& name);
virtual bool openRead(const QString& name);
virtual bool closeWrite();
virtual bool closeRead() {
return true;
}
virtual bool enterRelativeDirectory(const QString& dirName);
virtual bool enterAbsoluteDirectory(const QString& path);
virtual bool fileExists(const QString& absPath) const;
private:
- /// The archive
- KZip * m_pZip;
+ // The archive
+ SaveZip * m_pZip;
- /** In "Read" mode this pointer is pointing to the
- current directory in the archive to speed up the verification process */
+ // In "Read" mode this pointer is pointing to the current directory in the archive to speed up the verification process
const KArchiveDirectory* m_currentDir;
Q_DECLARE_PRIVATE(KoStore)
};
#endif
diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt
index 07dce4a185..a72ba71e6c 100644
--- a/libs/ui/CMakeLists.txt
+++ b/libs/ui/CMakeLists.txt
@@ -1,538 +1,550 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile
${EXIV2_INCLUDE_DIR}
)
include_directories(SYSTEM
${EIGEN3_INCLUDE_DIR}
${OCIO_INCLUDE_DIR}
${Boost_INCLUDE_DIRS}
)
add_subdirectory( tests )
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
endif ()
set(kritaui_LIB_SRCS
canvas/kis_canvas_widget_base.cpp
canvas/kis_canvas2.cpp
canvas/kis_canvas_updates_compressor.cpp
canvas/kis_canvas_controller.cpp
canvas/kis_paintop_transformation_connector.cpp
canvas/kis_display_color_converter.cpp
canvas/kis_display_filter.cpp
canvas/kis_exposure_gamma_correction_interface.cpp
canvas/kis_tool_proxy.cpp
canvas/kis_canvas_decoration.cc
canvas/kis_coordinates_converter.cpp
canvas/kis_grid_manager.cpp
canvas/kis_grid_decoration.cpp
canvas/kis_grid_config.cpp
canvas/kis_prescaled_projection.cpp
canvas/kis_qpainter_canvas.cpp
canvas/kis_projection_backend.cpp
canvas/kis_update_info.cpp
canvas/kis_image_patch.cpp
canvas/kis_image_pyramid.cpp
canvas/kis_infinity_manager.cpp
canvas/kis_change_guides_command.cpp
canvas/kis_guides_decoration.cpp
canvas/kis_guides_manager.cpp
canvas/kis_guides_config.cpp
canvas/kis_snap_config.cpp
canvas/kis_snap_line_strategy.cpp
+ canvas/KisSnapPointStrategy.cpp
dialogs/kis_about_application.cpp
dialogs/kis_dlg_adj_layer_props.cc
dialogs/kis_dlg_adjustment_layer.cc
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_generator_layer.cpp
dialogs/kis_dlg_file_layer.cpp
dialogs/kis_dlg_filter.cpp
dialogs/kis_dlg_stroke_selection_properties.cpp
dialogs/kis_dlg_image_properties.cc
dialogs/kis_dlg_layer_properties.cc
dialogs/kis_dlg_preferences.cc
dialogs/slider_and_spin_box_sync.cpp
dialogs/kis_dlg_blacklist_cleanup.cpp
dialogs/kis_dlg_layer_style.cpp
dialogs/kis_dlg_png_import.cpp
dialogs/kis_dlg_import_image_sequence.cpp
dialogs/kis_delayed_save_dialog.cpp
dialogs/kis_dlg_internal_color_selector.cpp
flake/kis_node_dummies_graph.cpp
flake/kis_dummies_facade_base.cpp
flake/kis_dummies_facade.cpp
flake/kis_node_shapes_graph.cpp
flake/kis_node_shape.cpp
flake/kis_shape_controller.cpp
flake/kis_shape_layer.cc
flake/kis_shape_layer_canvas.cpp
flake/kis_shape_selection.cpp
flake/kis_shape_selection_canvas.cpp
flake/kis_shape_selection_model.cpp
flake/kis_take_all_shapes_command.cpp
brushhud/kis_uniform_paintop_property_widget.cpp
brushhud/kis_brush_hud.cpp
brushhud/kis_round_hud_button.cpp
brushhud/kis_dlg_brush_hud_config.cpp
brushhud/kis_brush_hud_properties_list.cpp
brushhud/kis_brush_hud_properties_config.cpp
kis_aspect_ratio_locker.cpp
kis_autogradient.cc
kis_bookmarked_configurations_editor.cc
kis_bookmarked_configurations_model.cc
kis_bookmarked_filter_configurations_model.cc
kis_base_option.cpp
kis_canvas_resource_provider.cpp
kis_derived_resources.cpp
kis_categories_mapper.cpp
kis_categorized_list_model.cpp
kis_categorized_item_delegate.cpp
kis_clipboard.cc
kis_config.cc
kis_config_notifier.cpp
kis_control_frame.cpp
kis_composite_ops_model.cc
kis_paint_ops_model.cpp
kis_cursor.cc
kis_cursor_cache.cpp
kis_custom_pattern.cc
kis_file_layer.cpp
kis_safe_document_loader.cpp
kis_splash_screen.cpp
kis_filter_manager.cc
kis_filters_model.cc
kis_histogram_view.cc
kis_image_manager.cc
kis_image_view_converter.cpp
kis_import_catcher.cc
kis_layer_manager.cc
kis_mask_manager.cc
kis_mimedata.cpp
kis_node_commands_adapter.cpp
kis_node_manager.cpp
kis_node_juggler_compressed.cpp
kis_node_selection_adapter.cpp
kis_node_insertion_adapter.cpp
kis_node_model.cpp
kis_node_filter_proxy_model.cpp
kis_model_index_converter_base.cpp
kis_model_index_converter.cpp
kis_model_index_converter_show_all.cpp
kis_painting_assistant.cc
kis_painting_assistants_decoration.cpp
kis_painting_assistants_manager.cpp
kis_paintop_box.cc
kis_paintop_option.cpp
kis_paintop_options_model.cpp
kis_paintop_settings_widget.cpp
kis_popup_palette.cpp
kis_png_converter.cpp
kis_preference_set_registry.cpp
+ kis_script_manager.cpp
kis_resource_server_provider.cpp
+ KisSelectedShapesProxy.cpp
kis_selection_decoration.cc
kis_selection_manager.cc
kis_statusbar.cc
kis_zoom_manager.cc
kis_favorite_resource_manager.cpp
kis_workspace_resource.cpp
kis_action.cpp
kis_action_manager.cpp
kis_view_plugin.cpp
kis_canvas_controls_manager.cpp
kis_tooltip_manager.cpp
kis_multinode_property.cpp
kis_stopgradient_editor.cpp
kisexiv2/kis_exif_io.cpp
kisexiv2/kis_exiv2.cpp
kisexiv2/kis_iptc_io.cpp
kisexiv2/kis_xmp_io.cpp
opengl/kis_opengl.cpp
opengl/kis_opengl_canvas2.cpp
opengl/kis_opengl_canvas_debugger.cpp
opengl/kis_opengl_image_textures.cpp
opengl/kis_texture_tile.cpp
opengl/kis_opengl_shader_loader.cpp
kis_fps_decoration.cpp
recorder/kis_node_query_path_editor.cc
recorder/kis_recorded_action_creator.cc
recorder/kis_recorded_action_creator_factory.cc
recorder/kis_recorded_action_creator_factory_registry.cc
recorder/kis_recorded_action_editor_factory.cc
recorder/kis_recorded_action_editor_factory_registry.cc
recorder/kis_recorded_filter_action_editor.cc
recorder/kis_recorded_filter_action_creator.cpp
recorder/kis_recorded_paint_action_editor.cc
tool/kis_selection_tool_helper.cpp
tool/kis_selection_tool_config_widget_helper.cpp
tool/kis_rectangle_constraint_widget.cpp
tool/kis_shape_tool_helper.cpp
tool/kis_tool.cc
tool/kis_delegated_tool_policies.cpp
tool/kis_tool_freehand.cc
tool/kis_speed_smoother.cpp
tool/kis_painting_information_builder.cpp
tool/kis_stabilized_events_sampler.cpp
tool/kis_tool_freehand_helper.cpp
tool/kis_tool_multihand_helper.cpp
tool/kis_figure_painting_tool_helper.cpp
tool/kis_recording_adapter.cpp
tool/kis_tool_paint.cc
tool/kis_tool_shape.cc
tool/kis_tool_ellipse_base.cpp
tool/kis_tool_rectangle_base.cpp
tool/kis_tool_polyline_base.cpp
tool/kis_tool_utils.cpp
tool/kis_resources_snapshot.cpp
tool/kis_smoothing_options.cpp
tool/KisStabilizerDelayedPaintHelper.cpp
tool/strokes/freehand_stroke.cpp
tool/strokes/kis_painter_based_stroke_strategy.cpp
tool/strokes/kis_filter_stroke_strategy.cpp
tool/strokes/kis_color_picker_stroke_strategy.cpp
widgets/kis_cmb_composite.cc
widgets/kis_cmb_contour.cpp
widgets/kis_cmb_gradient.cpp
widgets/kis_paintop_list_widget.cpp
widgets/kis_cmb_idlist.cc
widgets/kis_color_space_selector.cc
widgets/kis_advanced_color_space_selector.cc
widgets/kis_cie_tongue_widget.cpp
widgets/kis_tone_curve_widget.cpp
widgets/kis_curve_widget.cpp
widgets/kis_custom_image_widget.cc
widgets/kis_image_from_clipboard_widget.cpp
widgets/kis_double_widget.cc
widgets/kis_filter_selector_widget.cc
widgets/kis_gradient_chooser.cc
widgets/kis_gradient_slider_widget.cc
widgets/kis_gradient_slider.cpp
widgets/kis_iconwidget.cc
widgets/kis_mask_widgets.cpp
widgets/kis_meta_data_merge_strategy_chooser_widget.cc
widgets/kis_multi_bool_filter_widget.cc
widgets/kis_multi_double_filter_widget.cc
widgets/kis_multi_integer_filter_widget.cc
widgets/kis_multipliers_double_slider_spinbox.cpp
widgets/kis_paintop_presets_popup.cpp
widgets/kis_tool_options_popup.cpp
widgets/kis_paintop_presets_chooser_popup.cpp
widgets/kis_pattern_chooser.cc
widgets/kis_popup_button.cc
widgets/kis_preset_chooser.cpp
widgets/kis_progress_widget.cpp
widgets/kis_selection_options.cc
widgets/kis_scratch_pad.cpp
widgets/kis_scratch_pad_event_filter.cpp
widgets/kis_preset_selector_strip.cpp
widgets/kis_slider_spin_box.cpp
widgets/kis_size_group.cpp
widgets/kis_size_group_p.cpp
widgets/kis_wdg_generator.cpp
widgets/kis_workspace_chooser.cpp
widgets/squeezedcombobox.cpp
widgets/kis_categorized_list_view.cpp
widgets/kis_widget_chooser.cpp
widgets/kis_tool_button.cpp
widgets/kis_floating_message.cpp
widgets/kis_lod_availability_widget.cpp
widgets/kis_color_label_selector_widget.cpp
widgets/kis_color_filter_combo.cpp
widgets/kis_elided_label.cpp
widgets/kis_stopgradient_slider_widget.cpp
widgets/kis_spinbox_color_selector.cpp
widgets/kis_screen_color_picker.cpp
widgets/KoDualColorButton.cpp
widgets/kis_color_input.cpp
widgets/kis_color_button.cpp
widgets/KisVisualColorSelector.cpp
widgets/KisVisualColorSelectorShape.cpp
widgets/KisVisualEllipticalSelectorShape.cpp
widgets/KisVisualRectangleSelectorShape.cpp
widgets/KisVisualTriangleSelectorShape.cpp
+ widgets/KoStrokeConfigWidget.cpp
+ widgets/KoFillConfigWidget.cpp
+ widgets/KoShapeFillWrapper.cpp
+
+ utils/kis_document_aware_spin_box_unit_manager.cpp
input/kis_input_manager.cpp
input/kis_input_manager_p.cpp
input/kis_extended_modifiers_mapper.cpp
input/kis_abstract_input_action.cpp
input/kis_tool_invocation_action.cpp
input/kis_pan_action.cpp
input/kis_alternate_invocation_action.cpp
input/kis_rotate_canvas_action.cpp
input/kis_zoom_action.cpp
input/kis_change_frame_action.cpp
input/kis_gamma_exposure_action.cpp
input/kis_show_palette_action.cpp
input/kis_change_primary_setting_action.cpp
input/kis_abstract_shortcut.cpp
input/kis_single_action_shortcut.cpp
input/kis_stroke_shortcut.cpp
input/kis_shortcut_matcher.cpp
input/kis_select_layer_action.cpp
operations/kis_operation.cpp
operations/kis_operation_configuration.cpp
operations/kis_operation_registry.cpp
operations/kis_operation_ui_factory.cpp
operations/kis_operation_ui_widget.cpp
operations/kis_filter_selection_operation.cpp
actions/kis_selection_action_factories.cpp
+ actions/KisPasteActionFactory.cpp
input/kis_touch_shortcut.cpp
kis_document_undo_store.cpp
kis_transaction_based_command.cpp
kis_gui_context_command.cpp
kis_gui_context_command_p.cpp
input/kis_tablet_debugger.cpp
input/kis_input_profile_manager.cpp
input/kis_input_profile.cpp
input/kis_shortcut_configuration.cpp
input/config/kis_input_configuration_page.cpp
input/config/kis_edit_profiles_dialog.cpp
input/config/kis_input_profile_model.cpp
input/config/kis_input_configuration_page_item.cpp
input/config/kis_action_shortcuts_model.cpp
input/config/kis_input_type_delegate.cpp
input/config/kis_input_mode_delegate.cpp
input/config/kis_input_button.cpp
input/config/kis_input_editor_delegate.cpp
input/config/kis_mouse_input_editor.cpp
input/config/kis_wheel_input_editor.cpp
input/config/kis_key_input_editor.cpp
processing/fill_processing_visitor.cpp
kis_asl_layer_style_serializer.cpp
kis_psd_layer_style_resource.cpp
canvas/kis_mirror_axis.cpp
kis_abstract_perspective_grid.cpp
KisApplication.cpp
KisAutoSaveRecoveryDialog.cpp
KisDetailsPane.cpp
KisDocument.cpp
KisNodeDelegate.cpp
kis_node_view_visibility_delegate.cpp
KisNodeToolTip.cpp
KisNodeView.cpp
kis_node_view_color_scheme.cpp
KisImportExportFilter.cpp
KisFilterEntry.cpp
KisImportExportManager.cpp
KisMainWindow.cpp
KisOpenPane.cpp
KisPart.cpp
KisPrintJob.cpp
KisTemplate.cpp
KisTemplateCreateDia.cpp
KisTemplateGroup.cpp
KisTemplates.cpp
KisTemplatesPane.cpp
KisTemplateTree.cpp
KisUndoStackAction.cpp
KisView.cpp
thememanager.cpp
kis_mainwindow_observer.cpp
KisViewManager.cpp
kis_mirror_manager.cpp
qtlockedfile/qtlockedfile.cpp
qtsingleapplication/qtlocalpeer.cpp
qtsingleapplication/qtsingleapplication.cpp
KisResourceBundle.cpp
KisResourceBundleManifest.cpp
kis_md5_generator.cpp
KisApplicationArguments.cpp
KisNetworkAccessManager.cpp
KisMultiFeedRSSModel.cpp
KisRemoteFileFetcher.cpp
KisPaletteModel.cpp
kis_palette_delegate.cpp
kis_palette_view.cpp
KisColorsetChooser.cpp
KisSaveGroupVisitor.cpp
)
if(WIN32)
if (NOT Qt5Gui_PRIVATE_INCLUDE_DIRS)
message(FATAL_ERROR "Qt5Gui Private header are missing!")
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support_win.cpp
input/wintab/kis_screen_size_choice_dialog.cpp
qtlockedfile/qtlockedfile_win.cpp
)
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
kis_animation_frame_cache.cpp
kis_animation_cache_populator.cpp
+ KisAnimationCacheRegenerator.cpp
+ dialogs/KisAnimationCacheUpdateProgressDialog.cpp
canvas/kis_animation_player.cpp
kis_animation_exporter.cpp
kis_animation_importer.cpp
KisSyncedAudioPlayback.cpp
)
if(UNIX)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/kis_tablet_event.cpp
input/wintab/kis_tablet_support.cpp
qtlockedfile/qtlockedfile_unix.cpp
)
if(NOT APPLE)
set(kritaui_LIB_SRCS
${kritaui_LIB_SRCS}
input/wintab/kis_tablet_support_x11.cpp
input/wintab/qxcbconnection_xi2.cpp
input/wintab/qxcbconnection.cpp
input/wintab/kis_xi2_event_filter.cpp
)
endif()
endif()
ki18n_wrap_ui(kritaui_LIB_SRCS
+ widgets/KoFillConfigWidget.ui
forms/wdgdlgpngimport.ui
forms/wdgfullscreensettings.ui
forms/wdgautogradient.ui
forms/wdggeneralsettings.ui
forms/wdgperformancesettings.ui
forms/wdggenerators.ui
forms/wdgbookmarkedconfigurationseditor.ui
forms/wdgapplyprofile.ui
forms/wdgcustompattern.ui
forms/wdglayerproperties.ui
forms/wdgcolorsettings.ui
forms/wdgtabletsettings.ui
forms/wdgcolorspaceselector.ui
forms/wdgcolorspaceselectoradvanced.ui
forms/wdgdisplaysettings.ui
forms/kis_previewwidgetbase.ui
forms/kis_matrix_widget.ui
forms/wdgselectionoptions.ui
forms/wdggeometryoptions.ui
forms/wdgnewimage.ui
forms/wdgimageproperties.ui
forms/wdgmaskfromselection.ui
forms/wdgmasksource.ui
forms/wdgfilterdialog.ui
forms/wdgmetadatamergestrategychooser.ui
forms/wdgpaintoppresets.ui
forms/wdgpaintopsettings.ui
forms/wdgdlggeneratorlayer.ui
forms/wdgdlgfilelayer.ui
forms/wdgfilterselector.ui
forms/wdgfilternodecreation.ui
forms/wdgpaintactioneditor.ui
forms/wdgmultipliersdoublesliderspinbox.ui
forms/wdgnodequerypatheditor.ui
forms/wdgpresetselectorstrip.ui
forms/wdgdlgblacklistcleanup.ui
forms/wdgrectangleconstraints.ui
forms/wdgimportimagesequence.ui
forms/wdgstrokeselectionproperties.ui
forms/KisDetailsPaneBase.ui
forms/KisOpenPaneBase.ui
forms/wdgstopgradienteditor.ui
brushhud/kis_dlg_brush_hud_config.ui
forms/wdgdlginternalcolorselector.ui
dialogs/kis_delayed_save_dialog.ui
input/config/kis_input_configuration_page.ui
input/config/kis_edit_profiles_dialog.ui
input/config/kis_input_configuration_page_item.ui
input/config/kis_mouse_input_editor.ui
input/config/kis_wheel_input_editor.ui
input/config/kis_key_input_editor.ui
layerstyles/wdgBevelAndEmboss.ui
layerstyles/wdgblendingoptions.ui
layerstyles/WdgColorOverlay.ui
layerstyles/wdgContour.ui
layerstyles/wdgdropshadow.ui
layerstyles/WdgGradientOverlay.ui
layerstyles/wdgInnerGlow.ui
layerstyles/wdglayerstyles.ui
layerstyles/WdgPatternOverlay.ui
layerstyles/WdgSatin.ui
layerstyles/WdgStroke.ui
layerstyles/wdgstylesselector.ui
layerstyles/wdgTexture.ui
wdgsplash.ui
input/wintab/kis_screen_size_choice_dialog.ui
)
QT5_WRAP_CPP(kritaui_HEADERS_MOC KisNodePropertyAction_p.h)
add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} )
generate_export_header(kritaui BASE_NAME kritaui)
target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network
kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} ${EXIV2_LIBRARIES}
)
if (HAVE_QT_MULTIMEDIA)
target_link_libraries(kritaui Qt5::Multimedia)
endif()
if (HAVE_KIO)
target_link_libraries(kritaui KF5::KIOCore)
endif()
if (NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${X11_X11_LIB}
${X11_Xinput_LIB}
${XCB_LIBRARIES})
endif()
if(APPLE)
target_link_libraries(kritaui ${FOUNDATION_LIBRARY})
endif ()
target_link_libraries(kritaui ${OPENEXR_LIBRARIES})
# Add VSync disable workaround
if(NOT WIN32 AND NOT APPLE)
target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras)
endif()
if(X11_FOUND)
target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES})
endif()
target_include_directories(kritaui
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/canvas>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/flake>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/ora>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/tool>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/utils>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/widgets>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/input/wintab>
)
set_target_properties(kritaui
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS})
if (APPLE)
install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita)
endif ()
diff --git a/libs/ui/KisAnimationCacheRegenerator.cpp b/libs/ui/KisAnimationCacheRegenerator.cpp
new file mode 100644
index 0000000000..c40844d997
--- /dev/null
+++ b/libs/ui/KisAnimationCacheRegenerator.cpp
@@ -0,0 +1,224 @@
+/*
+ * 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 "KisAnimationCacheRegenerator.h"
+
+#include "kis_assert.h"
+#include <QtConcurrent>
+#include <QTimer>
+#include <functional>
+
+#include "kis_image.h"
+#include "kis_image_animation_interface.h"
+#include "kis_animation_frame_cache.h"
+#include "kis_update_info.h"
+#include "kis_signal_auto_connection.h"
+#include "kis_time_range.h"
+
+
+struct Q_DECL_HIDDEN KisAnimationCacheRegenerator::Private
+{
+ int requestedFrame;
+ KisAnimationFrameCacheSP requestCache;
+ KisOpenGLUpdateInfoSP requestInfo;
+ KisSignalAutoConnectionsStore imageRequestConnections;
+ QTimer regenerationTimeout;
+
+ QFutureWatcher<void> infoConversionWatcher;
+
+ static const int WAITING_FOR_FRAME_TIMEOUT = 10000;
+};
+
+KisAnimationCacheRegenerator::KisAnimationCacheRegenerator(QObject *parent)
+ : QObject(parent),
+ m_d(new Private)
+{
+ connect(&m_d->regenerationTimeout, SIGNAL(timeout()), SLOT(slotFrameRegenerationCancelled()));
+ connect(this, SIGNAL(sigInternalStartFrameConversion()), SLOT(slotFrameStartConversion()));
+ connect(&m_d->infoConversionWatcher, SIGNAL(finished()), SLOT(slotFrameConverted()));
+
+ m_d->regenerationTimeout.setSingleShot(true);
+ m_d->regenerationTimeout.setInterval(Private::WAITING_FOR_FRAME_TIMEOUT);
+}
+
+KisAnimationCacheRegenerator::~KisAnimationCacheRegenerator()
+{
+}
+
+int KisAnimationCacheRegenerator::calcFirstDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange, const KisTimeRange &skipRange)
+{
+ int result = -1;
+
+ KisImageSP image = cache->image();
+ if (!image) return result;
+
+ KisImageAnimationInterface *animation = image->animationInterface();
+ if (!animation->hasAnimation()) return result;
+
+ if (playbackRange.isValid()) {
+ KIS_ASSERT_RECOVER_RETURN_VALUE(!playbackRange.isInfinite(), result);
+
+ // TODO: optimize check for fully-cached case
+ for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) {
+ if (skipRange.contains(frame)) {
+ if (skipRange.isInfinite()) {
+ break;
+ } else {
+ frame = skipRange.end();
+ continue;
+ }
+ }
+
+ if (cache->frameStatus(frame) != KisAnimationFrameCache::Cached) {
+ result = frame;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+int KisAnimationCacheRegenerator::calcNumberOfDirtyFrame(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange)
+{
+ int result = 0;
+
+ KisImageSP image = cache->image();
+ if (!image) return result;
+
+ KisImageAnimationInterface *animation = image->animationInterface();
+ if (!animation->hasAnimation()) return result;
+
+ if (playbackRange.isValid()) {
+ KIS_ASSERT_RECOVER_RETURN_VALUE(!playbackRange.isInfinite(), result);
+
+ // TODO: optimize check for fully-cached case
+ for (int frame = playbackRange.start(); frame <= playbackRange.end(); frame++) {
+ KisTimeRange stillFrameRange = KisTimeRange::infinite(0);
+ KisTimeRange::calculateTimeRangeRecursive(image->root(), frame, stillFrameRange, true);
+
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(stillFrameRange.isValid(), 0);
+
+ if (cache->frameStatus(stillFrameRange.start()) == KisAnimationFrameCache::Uncached) {
+ result++;
+ }
+
+ if (stillFrameRange.isInfinite()) {
+ break;
+ } else {
+ frame = stillFrameRange.end();
+ }
+ }
+ }
+
+ return result;
+}
+
+void KisAnimationCacheRegenerator::startFrameRegeneration(int frame, KisAnimationFrameCacheSP cache)
+{
+ KIS_ASSERT_RECOVER_NOOP(QThread::currentThread() == this->thread());
+
+ KisImageSP image = cache->image();
+
+ m_d->requestCache = cache;
+ m_d->requestedFrame = frame;
+
+ m_d->imageRequestConnections.clear();
+ m_d->imageRequestConnections.addConnection(
+ image->animationInterface(), SIGNAL(sigFrameReady(int)),
+ this, SLOT(slotFrameRegenerationFinished(int)),
+ Qt::DirectConnection);
+
+ m_d->imageRequestConnections.addConnection(
+ image->animationInterface(), SIGNAL(sigFrameCancelled()),
+ this, SLOT(slotFrameRegenerationCancelled()),
+ Qt::AutoConnection);
+
+ m_d->regenerationTimeout.start();
+ image->animationInterface()->requestFrameRegeneration(frame, image->bounds());
+}
+
+void KisAnimationCacheRegenerator::cancelCurrentFrameRegeneration()
+{
+ m_d->imageRequestConnections.clear();
+ m_d->requestCache = 0;
+ m_d->requestedFrame = -1;
+ m_d->requestInfo = 0;
+ m_d->regenerationTimeout.stop();
+}
+
+void KisAnimationCacheRegenerator::slotFrameRegenerationCancelled()
+{
+ // the timeout can arrive in async way
+ if (!m_d->requestCache) return;
+
+ cancelCurrentFrameRegeneration();
+ emit sigFrameCancelled();
+}
+
+void KisAnimationCacheRegenerator::slotFrameRegenerationFinished(int frame)
+{
+ // WARNING: executed in the context of image worker thread!
+
+ KisAnimationFrameCacheSP cache = m_d->requestCache;
+ if (!cache) return;
+
+ // probably a bit too strict...
+ KIS_SAFE_ASSERT_RECOVER_RETURN(frame == m_d->requestedFrame);
+
+ m_d->imageRequestConnections.clear();
+ m_d->requestInfo = cache->fetchFrameData(frame);
+
+ emit sigInternalStartFrameConversion();
+}
+
+namespace {
+static void processFrameInfo(KisOpenGLUpdateInfoSP info)
+{
+ if (info->needsConversion()) {
+ info->convertColorSpace();
+ }
+}
+}
+
+void KisAnimationCacheRegenerator::slotFrameStartConversion()
+{
+ if (!m_d->requestInfo) return;
+
+ m_d->regenerationTimeout.stop();
+
+ QFuture<void> requestFuture =
+ QtConcurrent::run(
+ std::bind(&processFrameInfo, m_d->requestInfo));
+
+ m_d->infoConversionWatcher.setFuture(requestFuture);
+}
+
+void KisAnimationCacheRegenerator::slotFrameConverted()
+{
+ if (!m_d->requestInfo || !m_d->requestCache) return;
+
+ m_d->requestCache->addConvertedFrameData(m_d->requestInfo, m_d->requestedFrame);
+
+ m_d->requestCache = 0;
+ m_d->requestedFrame = -1;
+ m_d->requestInfo = 0;
+
+ emit sigFrameFinished();
+}
+
diff --git a/libs/ui/KisAnimationCacheRegenerator.h b/libs/ui/KisAnimationCacheRegenerator.h
new file mode 100644
index 0000000000..5b7fd7bc3c
--- /dev/null
+++ b/libs/ui/KisAnimationCacheRegenerator.h
@@ -0,0 +1,66 @@
+/*
+ * 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 KISANIMATIONCACHEREGENERATOR_H
+#define KISANIMATIONCACHEREGENERATOR_H
+
+#include <QObject>
+#include <QScopedPointer>
+#include "kritaui_export.h"
+#include "kis_types.h"
+
+class KisTimeRange;
+
+
+class KRITAUI_EXPORT KisAnimationCacheRegenerator : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisAnimationCacheRegenerator(QObject *parent = 0);
+ ~KisAnimationCacheRegenerator();
+
+ static int calcFirstDirtyFrame(KisAnimationFrameCacheSP cache,
+ const KisTimeRange &playbackRange,
+ const KisTimeRange &skipRange);
+ static int calcNumberOfDirtyFrame(KisAnimationFrameCacheSP cache,
+ const KisTimeRange &playbackRange);
+
+
+public Q_SLOTS:
+ void startFrameRegeneration(int frame, KisAnimationFrameCacheSP cache);
+ void cancelCurrentFrameRegeneration();
+
+Q_SIGNALS:
+ void sigFrameCancelled();
+ void sigFrameFinished();
+
+ void sigInternalStartFrameConversion();
+
+private Q_SLOTS:
+ void slotFrameRegenerationCancelled();
+ void slotFrameRegenerationFinished(int frame);
+ void slotFrameStartConversion();
+ void slotFrameConverted();
+
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISANIMATIONCACHEREGENERATOR_H
diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp
index 8cb1b6a641..90c594db24 100644
--- a/libs/ui/KisApplication.cpp
+++ b/libs/ui/KisApplication.cpp
@@ -1,761 +1,769 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2009 Thomas Zander <zander@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
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QDir>
#include <QFile>
#include <QLocale>
#include <QMessageBox>
#include <QMessageBox>
#include <QProcessEnvironment>
#include <QSettings>
#include <QStandardPaths>
#include <QStringList>
#include <QStyle>
#include <QStyleFactory>
#include <QSysInfo>
#include <QTimer>
#include <QWidget>
#include <klocalizedstring.h>
#include <kdesktopfile.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <KoColorSpaceRegistry.h>
#include <KoPluginLoader.h>
#include <KoShapeRegistry.h>
#include <KoDpi.h>
#include "KoGlobal.h"
#include "KoConfig.h"
#include <resources/KoHashGeneratorProvider.h>
#include <KoResourcePaths.h>
#include <KisMimeDatabase.h>
#include "thememanager.h"
#include "KisPrintJob.h"
#include "KisDocument.h"
#include "KisMainWindow.h"
#include "KisAutoSaveRecoveryDialog.h"
#include "KisPart.h"
#include <kis_icon.h>
#include "kis_md5_generator.h"
#include "kis_splash_screen.h"
#include "kis_config.h"
#include "flake/kis_shape_selection.h"
#include <filter/kis_filter.h>
#include <filter/kis_filter_registry.h>
#include <filter/kis_filter_configuration.h>
#include <generator/kis_generator_registry.h>
#include <generator/kis_generator.h>
#include <brushengine/kis_paintop_registry.h>
#include <metadata/kis_meta_data_io_backend.h>
#include "kisexiv2/kis_exiv2.h"
#include "KisApplicationArguments.h"
#include <kis_debug.h>
#include "kis_action_registry.h"
#include <kis_brush_server.h>
#include <kis_resource_server_provider.h>
#include <KoResourceServerProvider.h>
#include "kis_image_barrier_locker.h"
#include "opengl/kis_opengl.h"
+#include "kis_spin_box_unit_manager.h"
+#include "kis_document_aware_spin_box_unit_manager.h"
#include <KritaVersionWrapper.h>
namespace {
const QTime appStartTime(QTime::currentTime());
}
class KisApplicationPrivate
{
public:
KisApplicationPrivate()
: splashScreen(0)
{}
QPointer<KisSplashScreen> splashScreen;
};
class KisApplication::ResetStarting
{
public:
- ResetStarting(KisSplashScreen *splash = 0)
+ ResetStarting(KisSplashScreen *splash, int fileCount)
: m_splash(splash)
+ , m_fileCount(fileCount)
{
}
~ResetStarting() {
if (m_splash) {
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false);
-
- if (hideSplash) {
+ if (m_fileCount > 0 || hideSplash) {
m_splash->hide();
}
else {
m_splash->setWindowFlags(Qt::Dialog);
QRect r(QPoint(), m_splash->size());
m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center());
m_splash->setWindowTitle(qAppName());
m_splash->setParent(0);
Q_FOREACH (QObject *o, m_splash->children()) {
QWidget *w = qobject_cast<QWidget*>(o);
if (w && w->isHidden()) {
w->setVisible(true);
}
}
m_splash->show();
m_splash->activateWindow();
}
}
}
QPointer<KisSplashScreen> m_splash;
+ int m_fileCount;
};
-
KisApplication::KisApplication(const QString &key, int &argc, char **argv)
: QtSingleApplication(key, argc, argv)
, d(new KisApplicationPrivate)
, m_autosaveDialog(0)
, m_mainWindow(0)
, m_batchRun(false)
{
QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath());
setApplicationDisplayName("Krita");
setApplicationName("krita");
// Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird.
// setOrganizationName("krita");
setOrganizationDomain("krita.org");
QString version = KritaVersionWrapper::versionString(true);
setApplicationVersion(version);
setWindowIcon(KisIconUtils::loadIcon("calligrakrita"));
if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) {
QStringList styles = QStringList() << "breeze" << "fusion" << "plastique";
if (!styles.contains(style()->objectName().toLower())) {
Q_FOREACH (const QString & style, styles) {
if (!setStyle(style)) {
qDebug() << "No" << style << "available.";
}
else {
qDebug() << "Set style" << style;
break;
}
}
}
}
else {
qDebug() << "Style override disabled, using" << style()->objectName();
}
KisOpenGL::initialize();
qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL();
}
#if defined(Q_OS_WIN) && defined(ENV32BIT)
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
LPFN_ISWOW64PROCESS fnIsWow64Process;
BOOL isWow64()
{
BOOL bIsWow64 = FALSE;
//IsWow64Process is not available on all supported versions of Windows.
//Use GetModuleHandle to get a handle to the DLL that contains the function
//and GetProcAddress to get a pointer to the function if available.
fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
GetModuleHandle(TEXT("kernel32")),"IsWow64Process");
if(0 != fnIsWow64Process)
{
if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
{
//handle error
}
}
return bIsWow64;
}
#endif
-void initializeGlobals(const KisApplicationArguments &args)
+void KisApplication::initializeGlobals(const KisApplicationArguments &args)
{
int dpiX = args.dpiX();
int dpiY = args.dpiY();
if (dpiX > 0 && dpiY > 0) {
KoDpi::setDPI(dpiX, dpiY);
}
}
-void addResourceTypes()
+void KisApplication::addResourceTypes()
{
// All Krita's resource types
KoResourcePaths::addResourceType("kis_pics", "data", "/pics/");
KoResourcePaths::addResourceType("kis_images", "data", "/images/");
KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/");
KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/");
KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/");
KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/");
KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/");
KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/");
KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/");
KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/");
KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl");
KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true);
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/");
KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true);
KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true);
KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/");
KoResourcePaths::addResourceType("kis_actions", "data", "/actions");
KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc");
KoResourcePaths::addResourceType("ko_effects", "data", "/effects/");
KoResourcePaths::addResourceType("tags", "data", "/tags/");
KoResourcePaths::addResourceType("templates", "data", "/templates");
+ KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita");
// // Extra directories to look for create resources. (Does anyone actually use that anymore?)
// KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp");
// KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp"));
// KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp");
// KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp"));
// KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp");
// KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp"));
// KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches");
// KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches"));
// Make directories for all resources we can save, and tags
QDir d;
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/");
d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/");
}
void KisApplication::loadResources()
{
setSplashScreenLoadingText(i18n("Loading Gradients..."));
processEvents();
KoResourceServerProvider::instance()->gradientServer(true);
// Load base resources
setSplashScreenLoadingText(i18n("Loading Patterns..."));
processEvents();
KoResourceServerProvider::instance()->patternServer(true);
setSplashScreenLoadingText(i18n("Loading Palettes..."));
processEvents();
KoResourceServerProvider::instance()->paletteServer(false);
setSplashScreenLoadingText(i18n("Loading Brushes..."));
processEvents();
KisBrushServer::instance()->brushServer(true);
// load paintop presets
setSplashScreenLoadingText(i18n("Loading Paint Operations..."));
processEvents();
KisResourceServerProvider::instance()->paintOpPresetServer(true);
setSplashScreenLoadingText(i18n("Loading Resource Bundles..."));
processEvents();
KisResourceServerProvider::instance()->resourceBundleServer();
}
void KisApplication::loadPlugins()
{
KoShapeRegistry* r = KoShapeRegistry::instance();
r->add(new KisShapeSelectionFactory());
KisActionRegistry::instance();
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
KisPaintOpRegistry::instance();
KoColorSpaceRegistry::instance();
// Load the krita-specific tools
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool..."));
processEvents();
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// Load dockers
setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock..."));
processEvents();
KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"),
QString::fromLatin1("[X-Krita-Version] == 28"));
// XXX_EXIV: make the exiv io backends real plugins
setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO..."));
processEvents();
KisExiv2::initialize();
}
bool KisApplication::start(const KisApplicationArguments &args)
{
KisConfig cfg;
#if defined(Q_OS_WIN)
#ifdef ENV32BIT
if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) {
QMessageBox::information(0,
i18nc("@title:window", "Krita: Warning"),
i18n("You are running a 32 bits build on a 64 bits Windows.\n"
"This is not recommended.\n"
"Please download and install the x64 build instead."));
cfg.writeEntry("WarnedAbout32Bits", true);
}
#endif
#endif
QString opengl = cfg.canvasState();
if (opengl == "OPENGL_NOT_TRIED" ) {
cfg.setCanvasState("TRY_OPENGL");
}
else if (opengl != "OPENGL_SUCCESS") {
cfg.setCanvasState("OPENGL_FAILED");
}
setSplashScreenLoadingText(i18n("Initializing Globals"));
processEvents();
initializeGlobals(args);
const bool doTemplate = args.doTemplate();
const bool print = args.print();
const bool exportAs = args.exportAs();
const bool exportAsPdf = args.exportAsPdf();
const QString exportFileName = args.exportFileName();
m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty());
// print & exportAsPdf do user interaction ATM
const bool needsMainWindow = !exportAs;
// only show the mainWindow when no command-line mode option is passed
// TODO: fix print & exportAsPdf to work without mainwindow shown
const bool showmainWindow = !exportAs; // would be !batchRun;
const bool showSplashScreen = !m_batchRun && qEnvironmentVariableIsEmpty("NOSPLASH");// && qgetenv("XDG_CURRENT_DESKTOP") != "GNOME";
if (showSplashScreen && d->splashScreen) {
d->splashScreen->show();
d->splashScreen->repaint();
processEvents();
}
KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
// Initialize all Krita directories etc.
KoGlobal::initialize();
KConfigGroup group(KSharedConfig::openConfig(), "theme");
Digikam::ThemeManager themeManager;
themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark"));
- ResetStarting resetStarting(d->splashScreen); // remove the splash when done
+
+ ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done
Q_UNUSED(resetStarting);
// Make sure we can save resources and tags
setSplashScreenLoadingText(i18n("Adding resource types"));
processEvents();
addResourceTypes();
// Load all resources and tags before the plugins do that
loadResources();
// Load the plugins
loadPlugins();
if (needsMainWindow) {
// show a mainWindow asap, if we want that
setSplashScreenLoadingText(i18n("Loading Main Window..."));
processEvents();
m_mainWindow = KisPart::instance()->createMainWindow();
if (showmainWindow) {
m_mainWindow->initializeGeometry();
m_mainWindow->show();
}
}
short int numberOfOpenDocuments = 0; // number of documents open
// Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf)
if (!m_batchRun) {
checkAutosaveFiles();
}
setSplashScreenLoadingText(QString()); // done loading, so clear out label
processEvents();
+ //configure the unit manager
+ KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder());
+ connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave.
+ //the new syntax slot syntax allow to connect to a non q_object static method.
+
// Get the command line arguments which we have to parse
int argsCount = args.filenames().count();
if (argsCount > 0) {
-
// Loop through arguments
short int nPrinted = 0;
for (int argNumber = 0; argNumber < argsCount; argNumber++) {
QString fileName = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
// called in mix with batch options? ignore and silently skip
if (m_batchRun) {
continue;
}
if (createNewDocFromTemplate(fileName, m_mainWindow)) {
++numberOfOpenDocuments;
}
// now try to load
}
else {
if (exportAs) {
QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName);
if (outputMimetype == "application/octetstream") {
dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl;
return 1;
}
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
doc->openUrl(QUrl::fromLocalFile(fileName));
qApp->processEvents(); // For vector layers to be updated
doc->setFileBatchMode(true);
doc->setOutputMimeType(outputMimetype.toLatin1());
if (!doc->exportDocument(QUrl::fromLocalFile(exportFileName))) {
dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage();
}
nPrinted++;
QTimer::singleShot(0, this, SLOT(quit()));
}
else if (m_mainWindow) {
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
if (m_mainWindow->openDocumentInternal(QUrl::fromLocalFile(fileName), doc)) {
if (print) {
m_mainWindow->slotFilePrint();
nPrinted++;
// TODO: trigger closing of app once printing is done
}
else if (exportAsPdf) {
KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName);
if (job)
connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow,
SLOT(slotFileQuit()), Qt::QueuedConnection);
nPrinted++;
} else {
// Normal case, success
numberOfOpenDocuments++;
}
} else {
// .... if failed
// delete doc; done by openDocument
}
}
}
}
if (m_batchRun) {
return nPrinted > 0;
}
}
// fixes BUG:369308 - Krita crashing on splash screen when loading.
// trying to open a file before Krita has loaded can cause it to hang and crash
if (d->splashScreen) {
d->splashScreen->displayLinks();
d->splashScreen->displayRecentFiles();
}
// not calling this before since the program will quit there.
return true;
}
KisApplication::~KisApplication()
{
delete d;
}
void KisApplication::setSplashScreen(QWidget *splashScreen)
{
d->splashScreen = qobject_cast<KisSplashScreen*>(splashScreen);
}
void KisApplication::setSplashScreenLoadingText(QString textToLoad)
{
if (d->splashScreen) {
d->splashScreen->loadingLabel->setText(textToLoad);
d->splashScreen->repaint();
}
}
void KisApplication::hideSplashScreen()
{
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
}
bool KisApplication::notify(QObject *receiver, QEvent *event)
{
try {
return QApplication::notify(receiver, event);
} catch (std::exception &e) {
qWarning("Error %s sending event %i to object %s",
e.what(), event->type(), qPrintable(receiver->objectName()));
} catch (...) {
qWarning("Error <unknown> sending event %i to object %s",
event->type(), qPrintable(receiver->objectName()));
}
return false;
}
void KisApplication::remoteArguments(QByteArray message, QObject *socket)
{
Q_UNUSED(socket);
// check if we have any mainwindow
KisMainWindow *mw = qobject_cast<KisMainWindow*>(qApp->activeWindow());
if (!mw) {
mw = KisPart::instance()->mainWindows().first();
}
if (!mw) {
return;
}
KisApplicationArguments args = KisApplicationArguments::deserialize(message);
const bool doTemplate = args.doTemplate();
const int argsCount = args.filenames().count();
if (argsCount > 0) {
// Loop through arguments
for (int argNumber = 0; argNumber < argsCount; ++argNumber) {
QString filename = args.filenames().at(argNumber);
// are we just trying to open a template?
if (doTemplate) {
createNewDocFromTemplate(filename, mw);
}
else if (QFile(filename).exists()) {
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
mw->openDocumentInternal(QUrl::fromLocalFile(filename), doc);
}
}
}
}
void KisApplication::fileOpenRequested(const QString &url)
{
KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first();
if (mainWindow) {
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
mainWindow->openDocumentInternal(QUrl::fromLocalFile(url), doc);
}
}
void KisApplication::checkAutosaveFiles()
{
if (m_batchRun) return;
// Check for autosave files from a previous run. There can be several, and
// we want to offer a restore for every one. Including a nice thumbnail!
QStringList filters;
filters << QString(".krita-*-*-autosave.kra");
#ifdef Q_OS_WIN
QDir dir = QDir::temp();
#else
QDir dir = QDir::home();
#endif
// all autosave files for our application
m_autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden);
// Allow the user to make their selection
if (m_autosaveFiles.size() > 0) {
if (d->splashScreen) {
// hide the splashscreen to see the dialog
d->splashScreen->hide();
}
m_autosaveDialog = new KisAutoSaveRecoveryDialog(m_autosaveFiles, activeWindow());
QDialog::DialogCode result = (QDialog::DialogCode) m_autosaveDialog->exec();
if (result == QDialog::Accepted) {
QStringList filesToRecover = m_autosaveDialog->recoverableFiles();
Q_FOREACH (const QString &autosaveFile, m_autosaveFiles) {
if (!filesToRecover.contains(autosaveFile)) {
QFile::remove(dir.absolutePath() + "/" + autosaveFile);
}
}
m_autosaveFiles = filesToRecover;
} else {
m_autosaveFiles.clear();
}
if (m_autosaveFiles.size() > 0) {
QList<QUrl> autosaveUrls;
Q_FOREACH (const QString &autoSaveFile, m_autosaveFiles) {
const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile);
autosaveUrls << url;
}
if (m_mainWindow) {
Q_FOREACH (const QUrl &url, autosaveUrls) {
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
m_mainWindow->openDocumentInternal(url, doc);
}
}
}
// cleanup
delete m_autosaveDialog;
m_autosaveDialog = nullptr;
}
}
bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow)
{
QString templatePath;
const QUrl templateUrl = QUrl::fromLocalFile(fileName);
if (QFile::exists(fileName)) {
templatePath = templateUrl.toLocalFile();
dbgUI << "using full path...";
}
else {
QString desktopName(fileName);
const QString templatesResourcePath = QStringLiteral("templates/");
QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName);
if (paths.isEmpty()) {
paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName);
}
if (paths.isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("No template found for: %1", desktopName));
} else if (paths.count() > 1) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"),
i18n("Too many templates found for: %1", desktopName));
} else {
templatePath = paths.at(0);
}
}
if (!templatePath.isEmpty()) {
QUrl templateBase;
templateBase.setPath(templatePath);
KDesktopFile templateInfo(templatePath);
QString templateName = templateInfo.readUrl();
QUrl templateURL;
templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName);
KisDocument *doc = KisPart::instance()->createDocument();
doc->setFileBatchMode(m_batchRun);
if (mainWindow->openDocumentInternal(templateURL, doc)) {
doc->resetURL();
doc->setTitleModified();
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 8d90bc5a5c..2ee65c1657 100644
--- a/libs/ui/KisApplication.h
+++ b/libs/ui/KisApplication.h
@@ -1,121 +1,124 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_APPLICATION_H
#define KIS_APPLICATION_H
#include <QPointer>
#include <qtsingleapplication/qtsingleapplication.h>
#include "kritaui_export.h"
#include <KisAutoSaveRecoveryDialog.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.
*/
virtual ~KisApplication();
/**
* 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);
+ void addResourceTypes();
+ void loadResources();
+ void loadPlugins();
+ void initializeGlobals(const KisApplicationArguments &args);
+
public Q_SLOTS:
void remoteArguments(QByteArray message, QObject*socket);
void fileOpenRequested(const QString & url);
private:
/// @return the number of autosavefiles opened
void checkAutosaveFiles();
bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow);
void clearConfig();
- void loadResources();
- void loadPlugins();
private:
KisApplicationPrivate * const d;
class ResetStarting;
friend class ResetStarting;
KisAutoSaveRecoveryDialog *m_autosaveDialog;
QStringList m_autosaveFiles;
QPointer<KisMainWindow> m_mainWindow; // The first mainwindow we create on startup
bool m_batchRun;
};
#endif
diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp
index b0993f5c86..792bf771e1 100644
--- a/libs/ui/KisDocument.cpp
+++ b/libs/ui/KisDocument.cpp
@@ -1,1777 +1,1737 @@
-/* This file is part of the Krita project
+/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt <boud@kogmbh.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h" // XXX: remove
#include <QMessageBox> // XXX: remove
#include <KisMimeDatabase.h>
#include <KoCanvasBase.h>
#include <KoColor.h>
#include <KoColorProfile.h>
#include <KoColorSpaceEngine.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoDocumentInfoDlg.h>
#include <KoDocumentInfo.h>
#include <KoDpi.h>
#include <KoUnit.h>
#include <KoFileDialog.h>
#include <KoID.h>
#include <KoOdfReadStore.h>
#include <KoProgressProxy.h>
#include <KoProgressUpdater.h>
#include <KoSelection.h>
#include <KoShape.h>
#include <KoShapeController.h>
#include <KoStore.h>
#include <KoUpdater.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoStoreDevice.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kdesktopfile.h>
#include <kconfiggroup.h>
#include <QTemporaryFile>
#include <kbackup.h>
#include <QTextBrowser>
#include <QApplication>
#include <QBuffer>
#include <QDesktopServices>
#include <QDir>
#include <QDomDocument>
#include <QDomElement>
#include <QFileInfo>
#include <QImage>
#include <QList>
#include <QPainter>
#include <QRect>
#include <QScopedPointer>
#include <QSize>
#include <QStringList>
#include <QtGlobal>
#include <QTimer>
#include <QWidget>
// Krita Image
#include <kis_config.h>
#include <flake/kis_shape_layer.h>
#include <kis_debug.h>
#include <kis_group_layer.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_name_server.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <kis_selection.h>
#include <kis_fill_painter.h>
#include <kis_document_undo_store.h>
#include <kis_painting_assistants_decoration.h>
#include <kis_idle_watcher.h>
#include <kis_signal_auto_connection.h>
#include <kis_debug.h>
#include <kis_canvas_widget_base.h>
// Local
#include "KisViewManager.h"
#include "kis_clipboard.h"
#include "widgets/kis_custom_image_widget.h"
#include "canvas/kis_canvas2.h"
#include "flake/kis_shape_controller.h"
#include "kis_statusbar.h"
#include "widgets/kis_progress_widget.h"
#include "kis_canvas_resource_provider.h"
#include "kis_resource_server_provider.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisImportExportManager.h"
#include "KisPart.h"
#include "KisView.h"
#include "kis_grid_config.h"
#include "kis_guides_config.h"
#include "kis_image_barrier_lock_adapter.h"
#include <mutex>
// Define the protocol used here for embedded documents' URL
// This used to "store" but QUrl didn't like it,
// so let's simply make it "tar" !
#define STORE_PROTOCOL "tar"
// The internal path is a hack to make QUrl happy and for document children
#define INTERNAL_PROTOCOL "intern"
#define INTERNAL_PREFIX "intern:/"
// Warning, keep it sync in koStore.cc
#include <unistd.h>
using namespace std;
/**********************************************************
*
* KisDocument
*
**********************************************************/
namespace {
class DocumentProgressProxy : public KoProgressProxy {
public:
KisMainWindow *m_mainWindow;
DocumentProgressProxy(KisMainWindow *mainWindow)
: m_mainWindow(mainWindow)
{
}
~DocumentProgressProxy() override {
// signal that the job is done
setValue(-1);
}
int maximum() const override {
return 100;
}
void setValue(int value) override {
if (m_mainWindow) {
m_mainWindow->slotProgress(value);
}
}
void setRange(int /*minimum*/, int /*maximum*/) override {
}
void setFormat(const QString &/*format*/) override {
}
};
}
//static
QString KisDocument::newObjectName()
{
static int s_docIFNumber = 0;
QString name; name.setNum(s_docIFNumber++); name.prepend("document_");
return name;
}
class UndoStack : public KUndo2Stack
{
public:
UndoStack(KisDocument *doc)
: m_doc(doc)
{
}
void setIndex(int idx) override {
KisImageWSP image = this->image();
image->requestStrokeCancellation();
if(image->tryBarrierLock()) {
KUndo2Stack::setIndex(idx);
image->unlock();
}
}
void notifySetIndexChangedOneCommand() override {
KisImageWSP image = this->image();
image->unlock();
/**
* Some very weird commands may emit blocking signals to
* the GUI (e.g. KisGuiContextCommand). Here is the best thing
* we can do to avoid the deadlock
*/
while(!image->tryBarrierLock()) {
QApplication::processEvents();
}
}
void undo() override {
KisImageWSP image = this->image();
image->requestUndoDuringStroke();
if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) {
return;
}
if(image->tryBarrierLock()) {
KUndo2Stack::undo();
image->unlock();
}
}
void redo() override {
KisImageWSP image = this->image();
if(image->tryBarrierLock()) {
KUndo2Stack::redo();
image->unlock();
}
}
private:
KisImageWSP image() {
KisImageWSP currentImage = m_doc->image();
Q_ASSERT(currentImage);
return currentImage;
}
private:
KisDocument *m_doc;
};
class Q_DECL_HIDDEN KisDocument::Private
{
public:
Private() :
docInfo(0),
progressUpdater(0),
progressProxy(0),
importExportManager(0),
isImporting(false),
isExporting(false),
password(QString()),
modifiedAfterAutosave(false),
isAutosaving(false),
backupFile(true),
doNotSaveExtDoc(false),
undoStack(0),
m_saveOk(false),
m_waitForSave(false),
m_duringSaveAs(false),
m_bAutoDetectedMime(false),
modified(false),
readwrite(true),
disregardAutosaveFailure(false),
nserver(0),
macroNestDepth(0),
imageIdleWatcher(2000 /*ms*/),
suppressProgress(false),
fileProgressProxy(0),
savingLock(&savingMutex)
{
if (QLocale().measurementSystem() == QLocale::ImperialSystem) {
unit = KoUnit::Inch;
} else {
unit = KoUnit::Centimeter;
}
}
~Private() {
// Don't delete m_d->shapeController because it's in a QObject hierarchy.
delete nserver;
}
KoDocumentInfo *docInfo;
KoProgressUpdater *progressUpdater;
KoProgressProxy *progressProxy;
KoUnit unit;
KisImportExportManager *importExportManager; // The filter-manager to use when loading/saving [for the options]
QByteArray mimeType; // The actual mimetype of the document
QByteArray outputMimeType; // The mimetype to use when saving
bool isImporting;
bool isExporting; // File --> Import/Export vs File --> Open/Save
QString password; // The password used to encrypt an encrypted document
QTimer autoSaveTimer;
QString lastErrorMessage; // see openFile()
QString lastWarningMessage;
int autoSaveDelay {300}; // in seconds, 0 to disable.
bool modifiedAfterAutosave;
bool isAutosaving;
bool backupFile;
bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents
KUndo2Stack *undoStack;
KisGuidesConfig guidesConfig;
QUrl m_originalURL; // for saveAs
QString m_originalFilePath; // for saveAs
bool m_saveOk;
bool m_waitForSave;
bool m_duringSaveAs;
bool m_bAutoDetectedMime; // whether the mimetype in the arguments was detected by the part itself
QUrl m_url; // local url - the one displayed to the user.
QString m_file; // Local file - the only one the part implementation should deal with.
QEventLoop m_eventLoop;
QMutex savingMutex;
bool modified;
bool readwrite;
QDateTime firstMod;
QDateTime lastMod;
bool disregardAutosaveFailure;
KisNameServer *nserver;
qint32 macroNestDepth;
KisImageSP image;
KisImageSP savingImage;
KisNodeSP preActivatedNode;
KisShapeController* shapeController;
KoShapeController* koShapeController;
KisIdleWatcher imageIdleWatcher;
QScopedPointer<KisSignalAutoConnection> imageIdleConnection;
bool suppressProgress;
KoProgressProxy* fileProgressProxy;
QList<KisPaintingAssistantSP> assistants;
KisGridConfig gridConfig;
StdLockableWrapper<QMutex> savingLock;
void setImageAndInitIdleWatcher(KisImageSP _image) {
image = _image;
imageIdleWatcher.setTrackedImage(image);
if (image) {
imageIdleConnection.reset(
new KisSignalAutoConnection(
&imageIdleWatcher, SIGNAL(startedIdleMode()),
image.data(), SLOT(explicitRegenerateLevelOfDetail())));
}
}
class SafeSavingLocker;
};
class KisDocument::Private::SafeSavingLocker {
public:
SafeSavingLocker(KisDocument::Private *_d, KisDocument *document)
: d(_d)
, m_document(document)
, m_locked(false)
, m_imageLock(d->image, true)
{
const int realAutoSaveInterval = KisConfig().autoSaveInterval();
const int emergencyAutoSaveInterval = 10; // sec
/**
* Initial try to lock both objects. Locking the image guards
* us from any image composition threads running in the
* background, while savingMutex guards us from entering the
* saving code twice by autosave and main threads.
*
* Since we are trying to lock multiple objects, so we should
* do it in a safe manner.
*/
m_locked = std::try_lock(m_imageLock, d->savingLock) < 0;
if (!m_locked) {
if (d->isAutosaving) {
d->disregardAutosaveFailure = true;
if (realAutoSaveInterval) {
m_document->setAutoSaveDelay(emergencyAutoSaveInterval);
}
} else {
d->image->requestStrokeEnd();
QApplication::processEvents();
// one more try...
m_locked = std::try_lock(m_imageLock, d->savingLock) < 0;
}
}
if (m_locked) {
d->disregardAutosaveFailure = false;
}
}
~SafeSavingLocker() {
if (m_locked) {
m_imageLock.unlock();
d->savingLock.unlock();
const int realAutoSaveInterval = KisConfig().autoSaveInterval();
m_document->setAutoSaveDelay(realAutoSaveInterval);
}
}
bool successfullyLocked() const {
return m_locked;
}
private:
KisDocument::Private *d;
KisDocument *m_document;
bool m_locked;
KisImageBarrierLockAdapter m_imageLock;
};
KisDocument::KisDocument()
: d(new Private())
{
d->undoStack = new UndoStack(this);
d->undoStack->setParent(this);
d->importExportManager = new KisImportExportManager(this);
d->importExportManager->setProgresUpdater(d->progressUpdater);
connect(&d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
KisConfig cfg;
setAutoSaveDelay(cfg.autoSaveInterval());
setObjectName(newObjectName());
d->docInfo = new KoDocumentInfo(this);
d->firstMod = QDateTime::currentDateTime();
d->lastMod = QDateTime::currentDateTime();
// preload the krita resources
KisResourceServerProvider::instance();
d->nserver = new KisNameServer(1);
d->shapeController = new KisShapeController(this, d->nserver);
d->koShapeController = new KoShapeController(0, d->shapeController);
undoStack()->setUndoLimit(KisConfig().undoStackLimit());
connect(d->undoStack, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int)));
setBackupFile(KisConfig().backupFile());
}
KisDocument::~KisDocument()
{
/**
* Push a timebomb, which will try to release the memory after
* the document has been deleted
*/
KisPaintDevice::createMemoryReleaseObject()->deleteLater();
d->autoSaveTimer.disconnect(this);
d->autoSaveTimer.stop();
delete d->importExportManager;
// Despite being QObject they needs to be deleted before the image
delete d->shapeController;
delete d->koShapeController;
if (d->image) {
d->image->notifyAboutToBeDeleted();
/**
* WARNING: We should wait for all the internal image jobs to
* finish before entering KisImage's destructor. The problem is,
* while execution of KisImage::~KisImage, all the weak shared
* pointers pointing to the image enter an inconsistent
* state(!). The shared counter is already zero and destruction
* has started, but the weak reference doesn't know about it,
* because KisShared::~KisShared hasn't been executed yet. So all
* the threads running in background and having weak pointers will
* enter the KisImage's destructor as well.
*/
d->image->requestStrokeCancellation();
d->image->waitForDone();
// clear undo commands that can still point to the image
d->undoStack->clear();
d->image->waitForDone();
KisImageWSP sanityCheckPointer = d->image;
Q_UNUSED(sanityCheckPointer);
// The following line trigger the deletion of the image
d->image.clear();
// check if the image has actually been deleted
KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid());
}
delete d;
}
bool KisDocument::reload()
{
// XXX: reimplement!
return false;
}
bool KisDocument::exportDocument(const QUrl &_url, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "exportDocument" << _url.toDisplayString() << "is autosaving" << d->isAutosaving;
bool ret;
d->isExporting = true;
//
// Preserve a lot of state here because we need to restore it in order to
// be able to fake a File --> Export. Can't do this in saveFile() because,
// for a start, KParts has already set url and m_file and because we need
// to restore the modified flag etc. and don't want to put a load on anyone
// reimplementing saveFile() (Note: importDocument() and exportDocument()
// will remain non-virtual).
//
QUrl oldURL = url();
QString oldFile = localFilePath();
//qDebug() << "\toldUrl" << oldURL << "oldFile" << oldFile << "export url" << _url;
bool wasModified = isModified();
// save...
ret = saveAs(_url, exportConfiguration);
//
// This is sooooo hacky :(
// Hopefully we will restore enough state.
//
dbgUI << "Restoring KisDocument state to before export";
// always restore url & m_file regardless of failure or success
//qDebug() << "\tafter saveAs: url" << url() << "local file path" << localFilePath();
setUrl(oldURL);
setLocalFilePath(oldFile);
//qDebug() << "\tafter restoring: url" << url() << "local file path" << localFilePath();
// on successful export we need to restore modified etc. too
// on failed export, mimetype/modified hasn't changed anyway
if (ret) {
setModified(wasModified);
}
d->isExporting = false;
return ret;
}
bool KisDocument::saveAs(const QUrl &url, KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "saveAs" << url;
if (!url.isValid() || !url.isLocalFile()) {
errKrita << "saveAs: Malformed URL " << url.url() << endl;
return false;
}
d->m_duringSaveAs = true;
d->m_originalURL = d->m_url;
d->m_originalFilePath = d->m_file;
d->m_url = url; // Store where to upload in saveToURL
d->m_file = d->m_url.toLocalFile();
bool result = save(exportConfiguration); // Save local file and upload local file
if (!result) {
d->m_url = d->m_originalURL;
d->m_file = d->m_originalFilePath;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
}
return result;
}
bool KisDocument::save(KisPropertiesConfigurationSP exportConfiguration)
{
//qDebug() << "save" << d->m_file << d->m_url << url() << localFilePath();
d->m_saveOk = false;
+
if (d->m_file.isEmpty()) { // document was created empty
d->m_file = d->m_url.toLocalFile();
}
updateEditingTime(true);
setFileProgressProxy();
setUrl(url());
bool ok = saveFile(localFilePath(), exportConfiguration);
clearFileProgressProxy();
if (ok) {
setModified( false );
emit completed();
d->m_saveOk = true;
d->m_duringSaveAs = false;
d->m_originalURL = QUrl();
d->m_originalFilePath.clear();
return true; // Nothing to do
}
else {
emit canceled(QString());
}
return false;
}
bool KisDocument::saveFile(const QString &filePath, KisPropertiesConfigurationSP exportConfiguration)
{
if (!prepareLocksForSaving()) {
return false;
}
// Unset the error message
setErrorMessage("");
// Save it to be able to restore it after a failed save
const bool wasModified = isModified();
+ bool ret = false;
+ bool suppressErrorDialog = fileBatchMode();
+ KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
- // The output format is set by KisMainWindow, and by openFile
- QByteArray outputMimeType = d->outputMimeType;
+ //qDebug() << "saveFile" << localFilePath() << QFileInfo(localFilePath()).exists() << !QFileInfo(localFilePath()).isWritable();
- if (outputMimeType.isEmpty()) {
- outputMimeType = d->outputMimeType = nativeFormatMimeType();
+ if (QFileInfo(localFilePath()).exists() && !QFileInfo(localFilePath()).isWritable()) {
+ setErrorMessage(i18n("%1 cannot be written to. Please save under a different name.", localFilePath()));
}
+ else {
- //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType;
-
+ // The output format is set by KisMainWindow, and by openFile
+ QByteArray outputMimeType = d->outputMimeType;
- if (d->backupFile) {
- Q_ASSERT(url().isLocalFile());
- KBackup::backupFile(url().toLocalFile());
- }
+ if (outputMimeType.isEmpty()) {
+ outputMimeType = d->outputMimeType = nativeFormatMimeType();
+ }
- qApp->processEvents();
+ //qDebug() << "saveFile. Is Autosaving?" << isAutosaving() << "url" << filePath << d->outputMimeType;
- bool ret = false;
- bool suppressErrorDialog = false;
- KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK;
- setFileProgressUpdater(i18n("Saving Document"));
-
- QFileInfo fi(filePath);
- QString tempororaryFileName;
- {
- QTemporaryFile tf(QDir::tempPath() + "/XXXXXX" + fi.baseName() + "." + fi.completeSuffix());
- tf.open();
- tempororaryFileName = tf.fileName();
- }
- Q_ASSERT(!tempororaryFileName.isEmpty());
+ if (d->backupFile) {
+ Q_ASSERT(url().isLocalFile());
+ KBackup::backupFile(url().toLocalFile());
+ }
- //qDebug() << "saving to tempory file" << tempororaryFileName;
- status = d->importExportManager->exportDocument(tempororaryFileName, filePath, outputMimeType, !d->isExporting , exportConfiguration);
+ qApp->processEvents();
- ret = (status == KisImportExportFilter::OK);
- suppressErrorDialog = (isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph);
- //qDebug() << "Export status was" << status;
+ setFileProgressUpdater(i18n("Saving Document"));
- if (ret) {
+ //qDebug() << "saving to tempory file" << tempororaryFileName;
+ status = d->importExportManager->exportDocument(localFilePath(), filePath, outputMimeType, !d->isExporting , exportConfiguration);
- //qDebug() << "copying temporary file" << tempororaryFileName << "to" << filePath;
+ ret = (status == KisImportExportFilter::OK);
+ suppressErrorDialog = (fileBatchMode() || isAutosaving() || status == KisImportExportFilter::UserCancelled || status == KisImportExportFilter::BadConversionGraph);
+ //qDebug() << "Export status was" << status;
- if (!d->isAutosaving && !d->suppressProgress) {
- QPointer<KoUpdater> updater = d->progressUpdater->startSubtask(1, "clear undo stack");
- updater->setProgress(0);
- d->undoStack->setClean();
- updater->setProgress(100);
- } else {
- d->undoStack->setClean();
- }
+ if (ret) {
- QFile tempFile(tempororaryFileName);
- QString s = filePath;
- QFile dstFile(s);
- while (QFileInfo(s).exists()) {
- s.append("_");
- }
- bool r;
- if (s != filePath) {
- r = dstFile.rename(s);
- if (!r) {
- setErrorMessage(i18n("Could not rename original file to %1: %2", dstFile.fileName(), dstFile. errorString()));
- ret = false;
+ if (!d->isAutosaving && !d->suppressProgress) {
+ QPointer<KoUpdater> updater = d->progressUpdater->startSubtask(1, "clear undo stack");
+ updater->setProgress(0);
+ d->undoStack->setClean();
+ updater->setProgress(100);
+ } else {
+ d->undoStack->setClean();
}
- }
- if (tempFile.exists()) {
- r = tempFile.copy(filePath);
- if (!r) {
- setErrorMessage(i18n("Copying the temporary file failed: %1 to %2: %3", tempFile.fileName(), dstFile.fileName(), tempFile.errorString()));
- ret = false;
+ if (errorMessage().isEmpty()) {
+ if (!isAutosaving()) {
+ removeAutoSaveFiles();
+ }
}
else {
- r = tempFile.remove();
- if (!r) {
- setErrorMessage(i18n("Could not remove temporary file %1: %2", tempFile.fileName(), tempFile.errorString()));
- ret = false;
- }
- else if (s != filePath) {
- r = dstFile.remove();
- if (!r) {
- setErrorMessage(i18n("Could not remove saved original file: %1", dstFile.errorString()));
- ret = false;
- }
- }
+ ret = false;
+ qWarning() << "Error while saving:" << errorMessage();
}
- }
- else {
- setErrorMessage(i18n("The temporary file %1 is gone before we could copy it!", tempFile.fileName()));
- ret = false;
- }
-
- if (errorMessage().isEmpty()) {
+ // Restart the autosave timer
+ // (we don't want to autosave again 2 seconds after a real save)
if (!isAutosaving()) {
- removeAutoSaveFiles();
+ setAutoSaveDelay(d->autoSaveDelay);
}
- }
- else {
- ret = false;
- qWarning() << "Error while saving:" << errorMessage();
- }
- // Restart the autosave timer
- // (we don't want to autosave again 2 seconds after a real save)
- if (!isAutosaving()) {
- setAutoSaveDelay(d->autoSaveDelay);
- }
- d->mimeType = outputMimeType;
+ d->mimeType = outputMimeType;
+ }
}
+
if (!ret) {
if (!suppressErrorDialog) {
if (errorMessage().isEmpty()) {
setErrorMessage(KisImportExportFilter::conversionStatusString(status));
}
if (errorMessage().isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save\n%1", filePath));
} else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, errorMessage()));
}
}
// couldn't save file so this new URL is invalid
// FIXME: we should restore the current document's true URL instead of
// setting it to nothing otherwise anything that depends on the URL
// being correct will not work (i.e. the document will be called
// "Untitled" which may not be true)
//
// Update: now the URL is restored in KisMainWindow but really, this
// should still be fixed in KisDocument/KParts (ditto for file).
// We still resetURL() here since we may or may not have been called
// by KisMainWindow - Clarence
resetURL();
// As we did not save, restore the "was modified" status
setModified(wasModified);
}
emit sigSavingFinished();
clearFileProgressUpdater();
unlockAfterSaving();
return ret;
}
QByteArray KisDocument::mimeType() const
{
return d->mimeType;
}
void KisDocument::setMimeType(const QByteArray & mimeType)
{
d->mimeType = mimeType;
}
void KisDocument::setOutputMimeType(const QByteArray & mimeType)
{
d->outputMimeType = mimeType;
}
QByteArray KisDocument::outputMimeType() const
{
return d->outputMimeType;
}
bool KisDocument::fileBatchMode() const
{
return d->importExportManager->batchMode();
}
void KisDocument::setFileBatchMode(const bool batchMode)
{
d->importExportManager->setBatchMode(batchMode);
}
bool KisDocument::isImporting() const
{
return d->isImporting;
}
bool KisDocument::isExporting() const
{
return d->isExporting;
}
void KisDocument::slotAutoSave()
{
//qDebug() << "slotAutoSave. Modified:" << d->modified << "modifiedAfterAutosave" << d->modified << "url" << url() << localFilePath();
if (!d->isAutosaving && d->modified && d->modifiedAfterAutosave) {
connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
emit statusBarMessage(i18n("Autosaving..."));
d->isAutosaving = true;
QString autoSaveFileName = generateAutoSaveFileName(localFilePath());
QByteArray mimetype = d->outputMimeType;
d->outputMimeType = nativeFormatMimeType();
bool ret = exportDocument(QUrl::fromLocalFile(autoSaveFileName));
d->outputMimeType = mimetype;
if (ret) {
d->modifiedAfterAutosave = false;
d->autoSaveTimer.stop(); // until the next change
}
d->isAutosaving = false;
emit clearStatusBarMessage();
disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
if (!ret && !d->disregardAutosaveFailure) {
emit statusBarMessage(i18n("Error during autosave! Partition full?"));
}
}
}
void KisDocument::setReadWrite(bool readwrite)
{
d->readwrite = readwrite;
setAutoSaveDelay(d->autoSaveDelay);
Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) {
mainWindow->setReadWrite(readwrite);
}
}
void KisDocument::setAutoSaveDelay(int delay)
{
//qDebug() << "setting autosave delay from" << d->autoSaveDelay << "to" << delay;
d->autoSaveDelay = delay;
if (isReadWrite() && d->autoSaveDelay > 0) {
d->autoSaveTimer.start(d->autoSaveDelay * 1000);
}
else {
d->autoSaveTimer.stop();
}
}
KoDocumentInfo *KisDocument::documentInfo() const
{
return d->docInfo;
}
bool KisDocument::isModified() const
{
return d->modified;
}
QPixmap KisDocument::generatePreview(const QSize& size)
{
KisImageSP image = d->image;
if (d->savingImage) image = d->savingImage;
if (image) {
QRect bounds = image->bounds();
QSize newSize = bounds.size();
newSize.scale(size, Qt::KeepAspectRatio);
QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0));
if (px.size() == QSize(0,0)) {
px = QPixmap(newSize);
QPainter gc(&px);
QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5));
gc.fillRect(px.rect(), checkBrush);
gc.end();
}
return px;
}
return QPixmap(size);
}
QString KisDocument::generateAutoSaveFileName(const QString & path) const
{
QString retval;
// Using the extension allows to avoid relying on the mime magic when opening
const QString extension (".kra");
if (path.isEmpty()) {
// Never saved?
#ifdef Q_OS_WIN
// On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#else
// On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file
retval = QString("%1%2.%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension);
#endif
} else {
QFileInfo fi(path);
QString dir = fi.absolutePath();
QString filename = fi.fileName();
retval = QString("%1%2.%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension);
}
//qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval;
return retval;
}
bool KisDocument::importDocument(const QUrl &_url)
{
bool ret;
dbgUI << "url=" << _url.url();
d->isImporting = true;
// open...
ret = openUrl(_url);
// reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a
// File --> Import
if (ret) {
dbgUI << "success, resetting url";
resetURL();
setTitleModified();
}
d->isImporting = false;
return ret;
}
bool KisDocument::openUrl(const QUrl &_url, KisDocument::OpenUrlFlags flags)
{
if (!_url.isLocalFile()) {
return false;
}
dbgUI << "url=" << _url.url();
d->lastErrorMessage.clear();
// Reimplemented, to add a check for autosave files and to improve error reporting
if (!_url.isValid()) {
d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ?
return false;
}
QUrl url(_url);
bool autosaveOpened = false;
if (url.isLocalFile() && !fileBatchMode()) {
QString file = url.toLocalFile();
QString asf = generateAutoSaveFileName(file);
if (QFile::exists(asf)) {
KisApplication *kisApp = static_cast<KisApplication*>(qApp);
kisApp->hideSplashScreen();
//dbgUI <<"asf=" << asf;
// ## TODO compare timestamps ?
int res = QMessageBox::warning(0,
i18nc("@title:window", "Krita"),
- i18n("An autosaved file exists for this document.\nDo you want to open it instead?"),
+ i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes :
url.setPath(asf);
autosaveOpened = true;
break;
case QMessageBox::No :
QFile::remove(asf);
break;
default: // Cancel
return false;
}
}
}
bool ret = openUrlInternal(url);
if (autosaveOpened) {
resetURL(); // Force save to act like 'Save As'
setReadWrite(true); // enable save button
setModified(true);
}
else {
if( !(flags & OPEN_URL_FLAG_DO_NOT_ADD_TO_RECENT_FILES) ) {
KisPart::instance()->addRecentURLToAllMainWindows(_url);
}
if (ret) {
// Detect readonly local-files; remote files are assumed to be writable
QFileInfo fi(url.toLocalFile());
setReadWrite(fi.isWritable());
}
}
return ret;
}
class DlgLoadMessages : public KoDialog {
public:
DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) {
setWindowTitle(title);
setWindowIcon(KisIconUtils::loadIcon("dialog-warning"));
QWidget *page = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(page);
QHBoxLayout *hlayout = new QHBoxLayout();
QLabel *labelWarning= new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-warning").pixmap(32, 32));
hlayout->addWidget(labelWarning);
hlayout->addWidget(new QLabel(message));
layout->addLayout(hlayout);
QTextBrowser *browser = new QTextBrowser();
QString warning = "<html><body><p><b>";
if (warnings.size() == 1) {
warning += "</b> Reason:</p>";
}
else {
warning += "</b> Reasons:</p>";
}
warning += "<p/><ul>";
Q_FOREACH(const QString &w, warnings) {
warning += "\n<li>" + w + "</li>";
}
warning += "</ul>";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
setFileProgressUpdater(i18n("Opening Document"));
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
clearFileProgressUpdater();
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
if (!d->suppressProgress && d->progressUpdater) {
QPointer<KoUpdater> updater = d->progressUpdater->startSubtask(1, "clear undo stack");
updater->setProgress(0);
undoStack()->clear();
updater->setProgress(100);
clearFileProgressUpdater();
} else {
undoStack()->clear();
}
return true;
}
KoProgressUpdater *KisDocument::progressUpdater() const
{
return d->progressUpdater;
}
void KisDocument::setProgressProxy(KoProgressProxy *progressProxy)
{
d->progressProxy = progressProxy;
}
KoProgressProxy* KisDocument::progressProxy() const
{
if (!d->progressProxy) {
KisMainWindow *mainWindow = 0;
if (KisPart::instance()->mainwindowCount() > 0) {
mainWindow = KisPart::instance()->mainWindows()[0];
}
d->progressProxy = new DocumentProgressProxy(mainWindow);
}
return d->progressProxy;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified()
{
d->modified = true;
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<<mod<<" MParts mod="<<KisParts::ReadWritePart::isModified()<<" isModified="<<isModified();
if (mod && !d->modifiedAfterAutosave) {
// First change since last autosave -> start the autosave timer
setAutoSaveDelay(d->autoSaveDelay);
}
d->modifiedAfterAutosave = mod;
if (mod == isModified())
return;
d->modified = mod;
if (mod) {
documentInfo()->updateParameters();
}
// This influences the title
setTitleModified();
emit modified(mod);
}
void KisDocument::updateEditingTime(bool forceStoreElapsed)
{
QDateTime now = QDateTime::currentDateTime();
int firstModDelta = d->firstMod.secsTo(now);
int lastModDelta = d->lastMod.secsTo(now);
if (lastModDelta > 30) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
if (documentInfo()) {
c = documentInfo()->aboutInfo("title");
}
const QString _url(url().fileName());
if (!c.isEmpty() && !_url.isEmpty()) {
c = QString("%1 - %2").arg(c).arg(_url);
}
else if (c.isEmpty()) {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
void KisDocument::setBackupFile(bool saveBackup)
{
d->backupFile = saveBackup;
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackIndexChanged(int idx)
{
// even if the document was already modified, call setModified to re-start autosave timer
setModified(idx != d->undoStack->cleanIndex());
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setFileProgressProxy();
setUrl(d->m_url);
ret = openFile();
clearFileProgressProxy();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisConfig cfg;
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
if (name != i18n("Unnamed") && !name.isEmpty()) {
setUrl(QUrl::fromLocalFile(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation) + '/' + name + ".kra"));
}
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
vKisNodeSP KisDocument::activeNodes() const
{
vKisNodeSP nodes;
Q_FOREACH (KisView *v, KisPart::instance()->views()) {
if (v->document() == this && v->viewManager()) {
KisNodeSP activeNode = v->viewManager()->activeNode();
if (activeNode && !nodes.contains(activeNode)) {
if (activeNode->inherits("KisMask")) {
activeNode = activeNode->parent();
}
nodes.append(activeNode);
}
}
}
return nodes;
}
QList<KisPaintingAssistantSP> KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList<KisPaintingAssistantSP> value)
{
d->assistants = value;
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
void KisDocument::setFileProgressUpdater(const QString &text)
{
d->suppressProgress = d->importExportManager->batchMode();
if (!d->suppressProgress) {
d->progressUpdater = new KoProgressUpdater(d->progressProxy, KoProgressUpdater::Unthreaded);
d->progressUpdater->start(100, text);
d->importExportManager->setProgresUpdater(d->progressUpdater);
-
- connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
- connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
+ if (KisPart::instance()->currentMainwindow()) {
+ connect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
+ connect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
+ }
}
}
void KisDocument::clearFileProgressUpdater()
{
if (!d->suppressProgress && d->progressUpdater) {
- disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
- disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
+ if (KisPart::instance()->currentMainwindow()) {
+ disconnect(KisPart::instance()->currentMainwindow(), SIGNAL(sigProgressCanceled()), this, SIGNAL(sigProgressCanceled()));
+ disconnect(this, SIGNAL(sigProgress(int)), KisPart::instance()->currentMainwindow(), SLOT(slotProgress(int)));
+ }
delete d->progressUpdater;
d->importExportManager->setProgresUpdater(0);
d->progressUpdater = 0;
}
}
void KisDocument::setFileProgressProxy()
{
if (!d->progressProxy && !d->importExportManager->batchMode()) {
d->fileProgressProxy = progressProxy();
} else {
d->fileProgressProxy = 0;
}
}
void KisDocument::clearFileProgressProxy()
{
if (d->fileProgressProxy) {
setProgressProxy(0);
delete d->fileProgressProxy;
d->fileProgressProxy = 0;
}
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
d->image->initialRefreshGraph();
setAutoSaveDelay(KisConfig().autoSaveInterval());
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
bool KisDocument::prepareLocksForSaving()
{
KisImageSP copiedImage;
// XXX: Restore this when
// a) cloning works correctly and
// b) doesn't take ages because it needs to refresh its entire graph and finally,
// c) we do use the saving image to save in the background.
{
Private::SafeSavingLocker locker(d, this);
if (locker.successfullyLocked()) {
copiedImage = d->image; //->clone(true);
}
else if (!isAutosaving()) {
// even though it is a recovery operation, we should ensure we do not enter saving twice!
std::unique_lock<StdLockableWrapper<QMutex>> l(d->savingLock, std::try_to_lock);
if (l.owns_lock()) {
d->lastErrorMessage = i18n("The image was still busy while saving. Your saved image might be incomplete.");
d->image->lock();
copiedImage = d->image; //->clone(true);
//copiedImage->initialRefreshGraph();
d->image->unlock();
}
}
}
bool result = false;
// ensure we do not enter saving twice
if (copiedImage && d->savingMutex.tryLock()) {
d->savingImage = copiedImage;
result = true;
} else {
qWarning() << "Could not lock the document for saving!";
d->lastErrorMessage = i18n("Could not lock the image for saving.");
}
return result;
}
void KisDocument::unlockAfterSaving()
{
d->savingImage = 0;
d->savingMutex.unlock();
}
diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h
index 2cf439499c..0c163f7662 100644
--- a/libs/ui/KisImportExportFilter.h
+++ b/libs/ui/KisImportExportFilter.h
@@ -1,176 +1,179 @@
/* This file is part of the Calligra libraries
Copyright (C) 2001 Werner Trobin <trobin@kde.org>
2002 Werner Trobin <trobin@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMPORT_EXPORT_FILTER_H
#define KIS_IMPORT_EXPORT_FILTER_H
#include <QObject>
#include <QIODevice>
#include <QMap>
#include <QPointer>
#include <QString>
#include <QPair>
#include <QList>
#include <KoID.h>
#include <QSharedPointer>
#include <kis_properties_configuration.h>
#include <kis_types.h>
#include <KisExportCheckBase.h>
class KoUpdater;
class KisDocument;
class KisConfigWidget;
#include "kritaui_export.h"
/**
* @brief The base class for import and export filters.
*
* Derive your filter class from this base class and implement
* the @ref convert() method. Don't forget to specify the Q_OBJECT
* macro in your class even if you don't use signals or slots.
* This is needed as filters are created on the fly.
*
* @note Take care: The m_chain pointer is invalid while the constructor
* runs due to the implementation -- @em don't use it in the constructor.
* After the constructor, when running the @ref convert() method it's
* guaranteed to be valid, so no need to check against 0.
*
* @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS
* environment variable to any value disables deletion of temporary files while
* importing/exporting. This is useful for testing purposes.
*
* @author Werner Trobin <trobin@kde.org>
* @todo the class has no constructor and therefore cannot initialize its private class
*/
class KRITAUI_EXPORT KisImportExportFilter : public QObject
{
Q_OBJECT
public:
/**
* This enum is used to signal the return state of your filter.
* Return OK in @ref convert() in case everything worked as expected.
* Feel free to add some more error conditions @em before the last item
* if it's needed.
*/
enum ConversionStatus { OK,
UsageError,
CreationError,
FileNotFound,
StorageCreationError,
BadMimeType,
BadConversionGraph,
WrongFormat,
NotImplemented,
ParsingError,
InternalError,
UserCancelled,
InvalidFormat,
FilterCreationError,
ProgressCancelled,
UnsupportedVersion,
JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255
};
virtual ~KisImportExportFilter();
void setBatchMode(bool batchmode);
void setFilename(const QString &filename);
void setRealFilename(const QString &filename);
void setMimeType(const QString &mime);
void setUpdater(QPointer<KoUpdater> updater);
/**
* The filter chain calls this method to perform the actual conversion.
* The passed mimetypes should be a pair of those you specified in your
* .desktop file.
* You @em have to implement this method to make the filter work.
*
* @return The error status, see the @ref #ConversionStatus enum.
* KisImportExportFilter::OK means that everything is alright.
*/
virtual ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0;
/**
* Get the text version of the status value
*/
static QString conversionStatusString(ConversionStatus status);
/**
* @brief defaultConfiguration defines the default settings for the given import export filter
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
* @return a serializable KisPropertiesConfiguration object
*/
virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
/**
* @brief lastSavedConfiguration return the last saved configuration for this filter
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
* @return a serializable KisPropertiesConfiguration object
*/
virtual KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
/**
* @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter
* @param parent the ownder of the widget; the caller is responsible for deleting
* @param from The mimetype of the source file/document
* @param to The mimetype of the destination file/document
*
* @return the widget
*/
virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
/**
* @brief generate and return the list of capabilities of this export filter. The list
* @return returns the list of capabilities of this export filter
*/
virtual QMap<QString, KisExportCheckBase*> exportChecks();
+ /// Override and return false for the filters that use a library that cannot handle file handles, only file names.
+ virtual bool supportsIO() const { return true; }
+
protected:
/**
* This is the constructor your filter has to call, obviously.
*/
KisImportExportFilter(QObject *parent = 0);
QString filename() const;
QString realFilename() const;
bool batchMode() const;
QByteArray mimeType() const;
void setProgress(int value);
virtual void initializeCapabilities();
void addCapability(KisExportCheckBase *capability);
void addSupportedColorModels(QList<QPair<KoID, KoID> > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY);
private:
KisImportExportFilter(const KisImportExportFilter& rhs);
KisImportExportFilter& operator=(const KisImportExportFilter& rhs);
class Private;
Private *const d;
};
#endif
diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp
index 7fe674d770..983ab2cf9e 100644
--- a/libs/ui/KisImportExportManager.cpp
+++ b/libs/ui/KisImportExportManager.cpp
@@ -1,457 +1,479 @@
/*
* 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 <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 <klocalizedstring.h>
#include <ksqueezedtextlabel.h>
#include <kpluginfactory.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 <KisMimeDatabase.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_guides_config.h"
#include "kis_grid_config.h"
#include "kis_popup_button.h"
#include <kis_iterator_ng.h>
// static cache for import and export mimetypes
QStringList KisImportExportManager::m_importMimeTypes;
QStringList KisImportExportManager::m_exportMimeTypes;
class Q_DECL_HIDDEN KisImportExportManager::Private
{
public:
bool batchMode {false};
QPointer<KoProgressUpdater> progressUpdater {0};
};
KisImportExportManager::KisImportExportManager(KisDocument* document)
: m_document(document)
, d(new Private)
{
}
KisImportExportManager::~KisImportExportManager()
{
delete d;
}
KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType)
{
return convert(Import, location, location, mimeType, false, 0);
}
KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
return convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration);
}
// The static method to figure out to which parts of the
// graph this mimetype has a connection to.
QStringList KisImportExportManager::mimeFilter(Direction direction)
{
// Find the right mimetype by the extension
QSet<QString> mimeTypes;
// mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster";
if (direction == KisImportExportManager::Import) {
if (m_importMimeTypes.isEmpty()) {
KoJsonTrader trader;
QList<QPluginLoader *>list = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_importMimeTypes = mimeTypes.toList();
}
return m_importMimeTypes;
}
else if (direction == KisImportExportManager::Export) {
if (m_exportMimeTypes.isEmpty()) {
KoJsonTrader trader;
QList<QPluginLoader *>list = trader.query("Krita/FileFilter", "");
Q_FOREACH(QPluginLoader *loader, list) {
QJsonObject json = loader->metaData().value("MetaData").toObject();
Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) {
//qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader;
mimeTypes << mimetype;
}
}
qDeleteAll(list);
m_exportMimeTypes = mimeTypes.toList();
}
return m_exportMimeTypes;
}
return QStringList();
}
KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction)
{
int weight = -1;
KisImportExportFilter *filter = 0;
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;
}
void KisImportExportManager::setBatchMode(const bool batch)
{
d->batchMode = batch;
}
bool KisImportExportManager::batchMode(void) const
{
return d->batchMode;
}
void KisImportExportManager::setProgresUpdater(KoProgressUpdater *updater)
{
d->progressUpdater = updater;
}
QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent)
{
KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio");
if (!defaultDir.isEmpty()) {
dialog.setDefaultDir(defaultDir);
}
QStringList mimeTypes;
mimeTypes << "audio/mpeg";
mimeTypes << "audio/ogg";
mimeTypes << "audio/vorbis";
mimeTypes << "audio/vnd.wave";
mimeTypes << "audio/flac";
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@titile:window", "Open Audio"));
return dialog.filename();
}
KisImportExportFilter::ConversionStatus KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration)
{
QString typeName = mimeType;
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(location);
}
QSharedPointer<KisImportExportFilter> filter(filterForMimeType(typeName, direction));
if (!filter) {
return KisImportExportFilter::FilterCreationError;
}
filter->setFilename(location);
filter->setRealFilename(realLocation);
filter->setBatchMode(batchMode());
filter->setMimeType(typeName);
if (d->progressUpdater) {
filter->setUpdater(d->progressUpdater->startSubtask());
}
QByteArray from, to;
if (direction == Export) {
from = m_document->nativeFormatMimeType();
to = mimeType.toLatin1();
}
else {
from = mimeType.toLatin1();
to = m_document->nativeFormatMimeType();
}
if (!exportConfiguration) {
exportConfiguration = filter->lastSavedConfiguration(from, to);
if (exportConfiguration) {
// Fill with some meta information about the image
KisImageWSP image = m_document->image();
KisPaintDeviceSP pd = image->projection();
bool isThereAlpha = false;
KisSequentialConstIterator it(pd, image->bounds());
const KoColorSpace* cs = pd->colorSpace();
do {
if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) {
isThereAlpha = true;
break;
}
} while (it.nextPixel());
exportConfiguration->setProperty("ImageContainsTransparency", isThereAlpha);
exportConfiguration->setProperty("ColorModelID", cs->colorModelId().id());
exportConfiguration->setProperty("ColorDepthID", cs->colorDepthId().id());
bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10")));
exportConfiguration->setProperty("sRGB", sRGB);
}
}
KisPreExportChecker checker;
if (direction == Export) {
checker.check(m_document->image(), filter->exportChecks());
}
KisConfigWidget *wdg = filter->createConfigurationWidget(0, from, to);
bool alsoAsKra = false;
QStringList warnings = checker.warnings();
QStringList errors = checker.errors();
// Extra checks that cannot be done by the checker, because the checker only has access to the image.
if (!m_document->assistants().isEmpty() && typeName != m_document->nativeFormatMimeType()) {
warnings.append(i18nc("image conversion warning", "The image contains <b>assistants</b>. The assistants will not be saved."));
}
if (m_document->guidesConfig().hasGuides() && typeName != 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() && typeName != 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.", KisMimeDatabase::descriptionForMimeType(typeName))
+ "</b> 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 KisImportExportFilter::UserCancelled;
}
if (!batchMode() && (wdg || !warnings.isEmpty())) {
KoDialog dlg;
dlg.setButtons(KoDialog::Ok | KoDialog::Cancel);
dlg.setWindowTitle(KisMimeDatabase::descriptionForMimeType(mimeType));
QWidget *page = new QWidget(&dlg);
QVBoxLayout *layout = new QVBoxLayout(page);
if (!checker.warnings().isEmpty()) {
if (showWarnings) {
QHBoxLayout *hLayout = new QHBoxLayout();
QLabel *labelWarning = new QLabel();
labelWarning->setPixmap(KisIconUtils::loadIcon("dialog-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. ", KisMimeDatabase::descriptionForMimeType(mimeType)));
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.", KisMimeDatabase::descriptionForMimeType(typeName));
if (warnings.size() == 1) {
warning += "</b> Reason:</p>";
}
else {
warning += "</b> Reasons:</p>";
}
warning += "<p/><ul>";
Q_FOREACH(const QString &w, warnings) {
warning += "\n<li>" + w + "</li>";
}
warning += "</ul>";
browser->setHtml(warning);
}
}
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) {
if (!checker.warnings().isEmpty()) {
chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file."));
chkAlsoAsKra->setChecked(KisConfig().readEntry<bool>("AlsoSaveAsKra", false));
layout->addWidget(chkAlsoAsKra);
}
}
dlg.setMainWidget(page);
dlg.resize(dlg.minimumSize());
if (showWarnings || wdg) {
if (!dlg.exec()) {
return KisImportExportFilter::UserCancelled;
}
}
if (chkAlsoAsKra) {
KisConfig().writeEntry<bool>("AlsoSaveAsKra", chkAlsoAsKra->isChecked());
alsoAsKra = chkAlsoAsKra->isChecked();
}
if (wdg) {
exportConfiguration = wdg->configuration();
}
}
QFile io(location);
+ QSaveFile out(location);
if (direction == Import) {
if (!io.exists()) {
return KisImportExportFilter::FileNotFound;
}
- if (!io.open(QFile::ReadOnly)) {
+ if (!io.open(QFile::ReadOnly) ) {
return KisImportExportFilter::FileNotFound;
}
}
else if (direction == Export) {
- if (!io.open(QFile::WriteOnly)) {
- return KisImportExportFilter::CreationError;
+ if (filter->supportsIO()) {
+ out.setDirectWriteFallback(true);
+ if (!out.open(QFile::WriteOnly)) {
+ out.cancelWriting();
+ return KisImportExportFilter::CreationError;
+ }
}
}
else {
return KisImportExportFilter::BadConversionGraph;
}
if (!batchMode()) {
QApplication::setOverrideCursor(Qt::WaitCursor);
}
- KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &io, exportConfiguration);
- io.close();
+ KisImportExportFilter::ConversionStatus status;
+ if (direction == Import || !filter->supportsIO()) {
+ status = filter->convert(m_document, &io, exportConfiguration);
+ if (io.isOpen()) io.close();
+ }
+ else {
+ status = filter->convert(m_document, &out, exportConfiguration);
+ if (status != KisImportExportFilter::OK) {
+ out.cancelWriting();
+ }
+ else {
+ out.commit();
+ }
+ }
if (exportConfiguration) {
KisConfig().setExportConfiguration(typeName, exportConfiguration);
}
if (alsoAsKra) {
QString l = location + ".kra";
QByteArray ba = m_document->nativeFormatMimeType();
KisImportExportFilter *filter = filterForMimeType(QString::fromLatin1(ba), Export);
- QFile f(l);
+ QSaveFile f(l);
f.open(QIODevice::WriteOnly);
if (filter) {
filter->setFilename(l);
- filter->convert(m_document, &f);
+ if (filter->convert(m_document, &f) == KisImportExportFilter::OK) {
+ f.commit();
+ }
+ else {
+ f.cancelWriting();
+ }
}
- f.close();
delete filter;
}
if (!batchMode()) {
QApplication::restoreOverrideCursor();
}
return status;
}
#include <KisMimeDatabase.h>
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index 31fa61c2fc..1e7158357f 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2406 +1,2404 @@
/* 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 <QDesktopServices>
#include <QDesktopWidget>
#include <QDockWidget>
#include <QIcon>
#include <QLabel>
#include <QLayout>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QMutex>
#include <QMutexLocker>
#include <QPointer>
#include <QPrintDialog>
#include <QPrinter>
#include <QPrintPreviewDialog>
#include <QProgressBar>
#include <QToolButton>
#include <QSignalMapper>
#include <QTabBar>
#include <QMoveEvent>
#include <QUrl>
#include <QMessageBox>
#include <QTemporaryFile>
#include <QStatusBar>
#include <QMenu>
#include <QMenuBar>
#include <KisMimeDatabase.h>
#include <QMimeData>
#include <kactioncollection.h>
#include <QAction>
#include <kactionmenu.h>
#include <kis_debug.h>
#include <kedittoolbar.h>
#include <khelpmenu.h>
#include <klocalizedstring.h>
#include <kaboutdata.h>
#ifdef HAVE_KIO
#include <krecentdocument.h>
#endif
#include <krecentfilesaction.h>
#include <KoResourcePaths.h>
#include <ktoggleaction.h>
#include <ktoolbar.h>
#include <kmainwindow.h>
#include <kxmlguiwindow.h>
#include <kxmlguifactory.h>
#include <kxmlguiclient.h>
#include <kguiitem.h>
#include <kwindowconfig.h>
#include "KoDockFactoryBase.h"
#include "KoDockWidgetTitleBar.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include <kis_icon.h>
#include <KoPageLayoutDialog.h>
#include <KoPageLayoutWidget.h>
#include <KoToolManager.h>
#include <KoZoomController.h>
#include "KoToolDocker.h"
#include <KoToolBoxFactory.h>
#include <KoDockRegistry.h>
#include <KoPluginLoader.h>
#include <KoColorSpaceEngine.h>
#include <KisMimeDatabase.h>
#include <brushengine/kis_paintop_settings.h>
#include "dialogs/kis_about_application.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include "dialogs/kis_dlg_preferences.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisApplication.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
-#include "kis_config_notifier.h"
#include "kis_custom_image_widget.h"
#include <KisDocument.h>
#include "KisDocument.h"
#include "KisDocument.h"
#include "kis_group_layer.h"
#include "kis_icon_utils.h"
#include "kis_image_from_clipboard_widget.h"
#include "kis_image.h"
#include <KisImportExportFilter.h>
#include "KisImportExportManager.h"
#include "kis_mainwindow_observer.h"
#include "kis_node.h"
#include "KisOpenPane.h"
#include "kis_paintop_box.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_resource_server_provider.h"
#include "kis_signal_compressor_with_param.h"
#include "KisView.h"
#include "KisViewManager.h"
#include "thememanager.h"
#include "kis_animation_importer.h"
#include "dialogs/kis_dlg_import_image_sequence.h"
#include "kis_animation_exporter.h"
#include <mutex>
#ifdef Q_OS_WIN
#include <QtPlatformHeaders/QWindowsWindowFunctions>
#endif
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
dockWidget->setTabEnabled(false);
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent)
: q(parent)
, dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
, windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
, documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new QSignalMapper(parent))
, documentMapper(new QSignalMapper(parent))
{
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
KisViewManager *viewManager {0};
QPointer<KisView> activeView;
QPointer<QProgressBar> progress;
QPointer<QToolButton> progressCancel;
QMutex progressMutex;
QList<QAction *> toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {false};
bool isImporting {false};
bool isExporting {false};
bool noCleanup {false};
KisAction *showDocumentInfo {0};
KisAction *saveAction {0};
KisAction *saveActionAs {0};
// KisAction *printAction;
// KisAction *printActionPreview;
// KisAction *exportPdf {0};
KisAction *importAnimation {0};
KisAction *closeAll {0};
// KisAction *reloadFile;
KisAction *importFile {0};
KisAction *exportFile {0};
KisAction *undo {0};
KisAction *redo {0};
KisAction *newWindow {0};
KisAction *close {0};
KisAction *mdiCascade {0};
KisAction *mdiTile {0};
KisAction *mdiNextWindow {0};
KisAction *mdiPreviousWindow {0};
KisAction *toggleDockers {0};
KisAction *toggleDockerTitleBars {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
QUrl lastExportUrl;
QMap<QString, QDockWidget *> dockWidgetsMap;
QMap<QDockWidget *, bool> dockWidgetVisibilityMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
QSignalMapper *windowMapper;
QSignalMapper *documentMapper;
QByteArray lastExportedFormat;
QScopedPointer<KisSignalCompressorWithParam<int> > tabSwitchCompressor;
QMutex savingEntryMutex;
KisActionManager * actionManager() {
return viewManager->actionManager();
}
QTabBar* findTabBarHACK() {
QObjectList objects = mdiArea->children();
Q_FOREACH (QObject *object, objects) {
QTabBar *bar = qobject_cast<QTabBar*>(object);
if (bar) {
return bar;
}
}
return 0;
}
};
KisMainWindow::KisMainWindow()
: KXmlGuiWindow()
, d(new Private(this))
{
KisConfig cfg;
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
setAcceptDrops(true);
setStandardToolBarMenuEnabled(true);
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
setDockNestingEnabled(true);
qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events
#ifdef Q_OS_OSX
setUnifiedTitleAndToolBarOnMac(true);
#endif
connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons()));
connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu()));
connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
actionCollection()->addAssociatedWidget(this);
+ KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager);
+
KoToolBoxFactory toolBoxFactory;
QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
if (cfg.toolOptionsInDocker()) {
ToolDockerFactory toolDockerFactory;
d->toolOptionsDocker = qobject_cast<KoToolDocker*>(createDockWidget(&toolDockerFactory));
d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
}
QMap<QString, QAction*> dockwidgetActions;
dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction();
Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) {
KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker);
QDockWidget *dw = createDockWidget(factory);
dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction();
}
if (d->toolOptionsDocker) {
dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction();
}
connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList<QPointer<QWidget> >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList<QPointer<QWidget> >)));
Q_FOREACH (QString title, dockwidgetActions.keys()) {
d->dockWidgetMenu->addAction(dockwidgetActions[title]);
}
Q_FOREACH (QDockWidget *wdg, dockWidgets()) {
if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
wdg->setVisible(true);
}
}
Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) {
observer->setObservedCanvas(0);
KisMainwindowObserver* mainwindowObserver = dynamic_cast<KisMainwindowObserver*>(observer);
if (mainwindowObserver) {
mainwindowObserver->setMainWindow(d->viewManager);
}
}
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setTabPosition(QTabWidget::North);
d->mdiArea->setTabsClosable(true);
setCentralWidget(d->mdiArea);
connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated()));
connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*)));
connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*)));
createActions();
setAutoSaveSettings("krita", false);
- KoPluginLoader::instance()->load("Krita/ViewPlugin",
- "Type == 'Service' and ([X-Krita-Version] == 28)",
- KoPluginLoader::PluginsConfig(),
- viewManager(),
- 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", "krita.xmlgui"));
setXMLFile(":/kxmlgui5/krita.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList<QAction *> toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast<KToolBar *>(it);
if (toolBar) {
if (toolBar->objectName() == "BrushesAndStuff") {
toolBar->setEnabled(false);
}
KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
act->setChecked(!toolBar->isHidden());
toolbarList.append(act);
} else
warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
}
plugActionList("toolbarlist", toolbarList);
setToolbarList(toolbarList);
applyToolBarLayout();
d->viewManager->updateGUI();
d->viewManager->updateIcons();
#ifdef Q_OS_WIN
auto w = qApp->activeWindow();
if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true);
#endif
QTimer::singleShot(1000, this, SLOT(checkSanity()));
{
using namespace std::placeholders; // For _1 placeholder
std::function<void (int)> callback(
std::bind(&KisMainWindow::switchTab, this, _1));
d->tabSwitchCompressor.reset(
new KisSignalCompressorWithParam<int>(500, callback, KisSignalCompressor::FIRST_INACTIVE));
}
}
void KisMainWindow::setNoCleanup(bool noCleanup)
{
d->noCleanup = noCleanup;
}
KisMainWindow::~KisMainWindow()
{
// Q_FOREACH (QAction *ac, actionCollection()->actions()) {
// QAction *action = qobject_cast<QAction*>(ac);
// if (action) {
// dbgKrita << "<Action"
// << "name=" << action->objectName()
// << "icon=" << action->icon().name()
// << "text=" << action->text().replace("&", "&amp;")
// << "whatsThis=" << action->whatsThis()
// << "toolTip=" << action->toolTip().replace("<html>", "").replace("</html>", "")
// << "iconText=" << action->iconText().replace("&", "&amp;")
// << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString()
// << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString()
// << "isCheckable=" << QString((action->isChecked() ? "true" : "false"))
// << "statusTip=" << action->statusTip()
// << "/>" ;
// }
// else {
// dbgKrita << "Got a QAction:" << ac->objectName();
// }
// }
// The doc and view might still exist (this is the case when closing the window)
KisPart::instance()->removeMainWindow(this);
if (d->noCleanup)
return;
delete d->viewManager;
delete d;
}
void KisMainWindow::addView(KisView *view)
{
if (d->activeView == view) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
showView(view);
updateCaption();
emit restoringDone();
if (d->activeView) {
connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified(QString,bool)));
}
}
void KisMainWindow::showView(KisView *imageView)
{
if (imageView && activeView() != imageView) {
// XXX: find a better way to initialize this!
imageView->setViewManager(d->viewManager);
imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
imageView->slotLoadingFinished();
QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg;
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setWindowIcon(qApp->windowIcon());
/**
* Hack alert!
*
* Here we explicitly request KoToolManager to emit all the tool
* activation signals, to reinitialize the tool options docker.
*
* That is needed due to a design flaw we have in the
* initialization procedure. The tool in the KoToolManager is
* initialized in KisView::setViewManager() calls, which
* happens early enough. During this call the tool manager
* requests KoCanvasControllerWidget to emit the signal to
* update the widgets in the tool docker. *But* at that moment
* of time the view is not yet connected to the main window,
* because it happens in KisViewManager::setCurrentView a bit
* later. This fact makes the widgets updating signals be lost
* and never reach the tool docker.
*
* So here we just explicitly call the tool activation stub.
*/
KoToolManager::instance()->initializeCurrentToolForCanvas();
if (d->mdiArea->subWindowList().size() == 1) {
imageView->showMaximized();
}
else {
imageView->show();
}
// No, no, no: do not try to call this _before_ the show() has
// been called on the view; only when that has happened is the
// opengl context active, and very bad things happen if we tell
// the dockers to update themselves with a view if the opengl
// context is not active.
setActiveView(imageView);
updateWindowMenu();
updateCaption();
}
}
void KisMainWindow::slotPreferences()
{
if (KisDlgPreferences::editPreferences()) {
KisConfigNotifier::instance()->notifyConfigChanged();
// XXX: should this be changed for the views in other windows as well?
Q_FOREACH (QPointer<KisView> koview, KisPart::instance()->views()) {
KisViewManager *view = qobject_cast<KisViewManager*>(koview);
if (view) {
// Update the settings for all nodes -- they don't query
// KisConfig directly because they need the settings during
// compositing, and they don't connect to the config notifier
// because nodes are not QObjects (because only one base class
// can be a QObject).
KisNode* node = dynamic_cast<KisNode*>(view->image()->rootLayer().data());
node->updateSettings();
}
}
d->viewManager->showHideScrollbars();
}
}
void KisMainWindow::slotThemeChanged()
{
// save theme changes instantly
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
// reload action icons!
Q_FOREACH (QAction *action, actionCollection()->actions()) {
KisIconUtils::updateIcon(action);
}
emit themeChanged();
}
void KisMainWindow::updateReloadFileAction(KisDocument *doc)
{
Q_UNUSED(doc);
// d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KisMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KisMainWindow::addRecentURL(const QUrl &url)
{
dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString();
// Add entry to recent documents list
// (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp");
for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it)
if (path.contains(*it))
ok = false; // it's in the tmp resource
#ifdef HAVE_KIO
if (ok) {
KRecentDocument::add(QUrl::fromLocalFile(path));
}
#endif
}
#ifdef HAVE_KIO
else {
KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash));
}
#endif
if (ok) {
d->recentFiles->addUrl(url);
}
saveRecentFiles();
}
}
void KisMainWindow::saveRecentFiles()
{
// Save list of recent files
KSharedConfigPtr config = KSharedConfig::openConfig();
d->recentFiles->saveEntries(config->group("RecentFiles"));
config->sync();
// Tell all windows to reload their list, after saving
// Doesn't work multi-process, but it's a start
Q_FOREACH (KMainWindow* window, KMainWindow::memberList())
static_cast<KisMainWindow *>(window)->reloadRecentFileList();
}
void KisMainWindow::reloadRecentFileList()
{
d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles"));
}
void KisMainWindow::updateCaption()
{
if (!d->mdiArea->activeSubWindow()) {
updateCaption(QString(), false);
}
else if (d->activeView && d->activeView->document()){
QString caption( d->activeView->document()->caption() );
if (d->readOnly) {
caption += ' ' + i18n("(write protected)");
}
d->activeView->setWindowTitle(caption);
updateCaption(caption, d->activeView->document()->isModified());
if (!d->activeView->document()->url().fileName().isEmpty())
d->saveAction->setToolTip(i18n("Save as %1", d->activeView->document()->url().fileName()));
else
d->saveAction->setToolTip(i18n("Save"));
}
}
void KisMainWindow::updateCaption(const QString & caption, bool mod)
{
dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")";
#ifdef KRITA_ALPHA
setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod);
return;
#endif
#ifdef KRITA_BETA
setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod);
return;
#endif
#ifdef KRITA_RC
setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod);
return;
#endif
setCaption(caption, mod);
}
KisView *KisMainWindow::activeView() const
{
if (d->activeView) {
return d->activeView;
}
return 0;
}
bool KisMainWindow::openDocument(const QUrl &url)
{
if (!QFile(url.toLocalFile()).exists()) {
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);
}
bool KisMainWindow::openDocumentInternal(const QUrl &url, KisDocument *newdoc)
{
if (!url.isLocalFile()) {
qDebug() << "KisMainWindow::openDocumentInternal. Not a local file:" << url;
return false;
}
if (!newdoc) {
newdoc = KisPart::instance()->createDocument();
}
d->firstTime = true;
connect(newdoc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
bool openRet = (!d->isImporting) ? newdoc->openUrl(url) : newdoc->importDocument(url);
if (!openRet) {
delete newdoc;
return false;
}
KisPart::instance()->addDocument(newdoc);
updateReloadFileAction(newdoc);
if (!QFileInfo(url.toLocalFile()).isWritable()) {
setReadWrite(false);
}
return true;
}
void KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document)
{
KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this);
addView(view);
emit guiLoadingFinished();
}
QStringList KisMainWindow::showOpenFileDialog()
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import));
dialog.setCaption(d->isImporting ? i18n("Import Images") : i18n("Open Images"));
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(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
emit loadCompleted();
}
}
void KisMainWindow::slotLoadCanceled(const QString & errMsg)
{
dbgUI << "KisMainWindow::slotLoadCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
// ... can't delete the document, it's the one who emitted the signal...
KisDocument* doc = qobject_cast<KisDocument*>(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
}
void KisMainWindow::slotSaveCanceled(const QString &errMsg)
{
dbgUI << "KisMainWindow::slotSaveCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
slotSaveCompleted();
}
void KisMainWindow::slotSaveCompleted()
{
dbgUI << "KisMainWindow::slotSaveCompleted";
KisDocument* doc = qobject_cast<KisDocument*>(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
if (d->deferredClosingEvent) {
KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
}
}
bool KisMainWindow::hackIsSaving() const
{
StdLockableWrapper<QMutex> wrapper(&d->savingEntryMutex);
std::unique_lock<StdLockableWrapper<QMutex>> l(wrapper, std::try_to_lock);
return !l.owns_lock();
}
bool KisMainWindow::saveDocument(KisDocument *document, bool saveas)
{
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() != QDialog::Accepted) {
return false;
}
bool reset_url;
if (document->url().isEmpty()) {
reset_url = true;
saveas = true;
} else {
reset_url = false;
}
connect(document, SIGNAL(sigProgress(int)), this, SLOT(slotProgress(int)));
connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
QUrl oldURL = document->url();
QString oldFile = document->localFilePath();
QByteArray _native_format = document->nativeFormatMimeType();
QByteArray oldOutputFormat = document->outputMimeType();
QUrl suggestedURL = document->url();
QStringList mimeFilter;
mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export);
if (!mimeFilter.contains(oldOutputFormat) && !d->isExporting) {
dbgUI << "KisMainWindow::saveDocument no export filter for" << oldOutputFormat;
// --- 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 = suggestedURL.fileName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
int c = suggestedFilename.lastIndexOf('.');
const QString ext = KisMimeDatabase::suffixesForMimeType(_native_format).first();
if (!ext.isEmpty()) {
if (c < 0)
suggestedFilename = suggestedFilename + "." + ext;
else
suggestedFilename = suggestedFilename.left(c) + "." + ext;
} else { // current filename extension wrong anyway
if (c > 0) {
// this assumes that a . signifies an extension, not just a .
suggestedFilename = suggestedFilename.left(c);
}
}
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() || 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(i18n("untitled"));
if (d->isExporting && !d->lastExportUrl.isEmpty()) {
dialog.setDefaultDir(d->lastExportUrl.toLocalFile());
}
else {
dialog.setDefaultDir(suggestedURL.toLocalFile());
}
// Default to all supported file types if user is exporting, otherwise use Krita default
dialog.setMimeTypeFilters(mimeFilter, QString(_native_format));
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
fn.append(KisMimeDatabase::suffixesForMimeType(_native_format).first());
newURL = QUrl::fromLocalFile(fn);
}
}
if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
QString fn = newURL.toLocalFile();
QFileInfo info(fn);
document->documentInfo()->setAboutInfo("title", info.baseName());
}
QByteArray outputFormat = _native_format;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile());
outputFormat = outputFormatString.toLatin1();
if (!d->isExporting) {
justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType());
}
else {
justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat);
}
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions) {
if (!document->isNativeFormat(outputFormat))
wantToSave = true;
}
if (wantToSave) {
//
// Note:
// If the user is stupid enough to Export to the current URL,
// we do _not_ change this operation into a Save As. Reasons
// follow:
//
// 1. A check like "d->isExporting && oldURL == newURL"
// doesn't _always_ work on case-insensitive filesystems
// and inconsistent behaviour is bad.
// 2. It is probably not a good idea to change document->mimeType
// and friends because the next time the user File/Save's,
// (not Save As) they won't be expecting that they are
// using their File/Export settings
//
// As a bad side-effect of this, the modified flag will not
// be updated and it is possible that what is currently on
// their screen is not what is stored on disk (through loss
// of formatting). But if you are dumb enough to change
// mimetype but not the filename, then arguably, _you_ are
// the "bug" :)
//
// - Clarence
//
document->setOutputMimeType(outputFormat);
if (!d->isExporting) { // Save As
ret = document->saveAs(newURL);
if (ret) {
dbgUI << "Successful Save As!";
addRecentURL(newURL);
setReadWrite(true);
} else {
dbgUI << "Failed Save As!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
document->setOutputMimeType(oldOutputFormat);
}
} else { // Export
ret = document->exportDocument(newURL);
if (ret) {
// a few file dialog convenience things
d->lastExportUrl = newURL;
d->lastExportedFormat = outputFormat;
}
// always restore output format
document->setOutputMimeType(oldOutputFormat);
}
} // if (wantToSave) {
else
ret = false;
} // if (bOk) {
else
ret = false;
} else { // saving
// be sure document has the correct outputMimeType!
if (d->isExporting || document->isModified()) {
ret = document->save();
}
if (!ret) {
dbgUI << "Failed Save!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
}
}
if (!ret && reset_url)
document->resetURL(); //clean the suggested filename as the save dialog was rejected
updateReloadFileAction(document);
updateCaption();
return ret;
}
void KisMainWindow::undo()
{
if (activeView()) {
activeView()->undoAction()->trigger();
d->undo->setText(activeView()->undoAction()->text());
}
}
void KisMainWindow::redo()
{
if (activeView()) {
activeView()->redoAction()->trigger();
d->redo->setText(activeView()->redoAction()->text());
}
}
void KisMainWindow::closeEvent(QCloseEvent *e)
{
d->mdiArea->closeAllSubWindows();
QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
if ((action) && (action->isChecked())) {
action->setChecked(false);
}
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
cfg.writeEntry("ko_geometry", saveGeometry().toBase64());
cfg.writeEntry("ko_windowstate", saveState().toBase64());
{
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
}
QList<QMdiSubWindow*> childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
if (!d->dockerStateBeforeHiding.isEmpty()) {
restoreState(d->dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
if (d->noCleanup)
return;
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
KisView *view = dynamic_cast<KisView*>(subwin);
if (view) {
KisPart::instance()->removeView(view);
}
}
if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency
Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap)
dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget));
}
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = config->group("MainWindow");
KWindowConfig::saveWindowSize(windowHandle(), group);
config->sync();
d->windowSizeDirty = false;
}
if (!d->activeView || d->activeView->document()) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
// Save collapsable state of dock widgets
for (QMap<QString, QDockWidget*>::const_iterator i = d->dockWidgetsMap.constBegin();
i != d->dockWidgetsMap.constEnd(); ++i) {
if (i.value()->widget()) {
KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden());
dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x());
dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y());
dockGroup.writeEntry("width", (int) i.value()->widget()->width());
dockGroup.writeEntry("height", (int) i.value()->widget()->height());
}
}
}
KSharedConfig::openConfig()->sync();
resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down
}
void KisMainWindow::resizeEvent(QResizeEvent * e)
{
d->windowSizeDirty = true;
KXmlGuiWindow::resizeEvent(e);
}
void KisMainWindow::setActiveView(KisView* view)
{
d->activeView = view;
updateCaption();
actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text());
actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text());
d->viewManager->setCurrentView(view);
}
void KisMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisMainWindow::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
openDocument(url);
}
}
}
void KisMainWindow::dragMoveEvent(QDragMoveEvent * event)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) {
qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!";
}
if (tabBar && tabBar->isVisible()) {
QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos()));
if (tabBar->rect().contains(pos)) {
const int tabIndex = tabBar->tabAt(pos);
if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) {
d->tabSwitchCompressor->start(tabIndex);
}
} else if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
}
void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/)
{
if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
void KisMainWindow::switchTab(int index)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar) return;
tabBar->setCurrentIndex(index);
}
void KisMainWindow::slotFileNew()
{
const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import);
KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/"));
startupWidget->setWindowModality(Qt::WindowModal);
KisConfig cfg;
int w = cfg.defImageWidth();
int h = cfg.defImageHeight();
const double resolution = cfg.defImageResolution();
const QString colorModel = cfg.defColorModel();
const QString colorDepth = cfg.defaultColorDepth();
const QString colorProfile = cfg.defColorProfile();
CustomDocumentWidgetItem item;
item.widget = new KisCustomImageWidget(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.icon = "document-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
QSize sz = KisClipboard::instance()->clipSize();
if (sz.isValid() && sz.width() != 0 && sz.height() != 0) {
w = sz.width();
h = sz.height();
}
item.widget = new KisImageFromClipboard(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.title = i18n("Create from Clipboard");
item.icon = "tab-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
// calls deleteLater
connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*)));
// calls deleteLater
connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&)));
startupWidget->exec();
// Cancel calls deleteLater...
}
void KisMainWindow::slotFileOpen()
{
QStringList urls = showOpenFileDialog();
if (urls.isEmpty())
return;
Q_FOREACH (const QString& url, urls) {
if (!url.isEmpty()) {
bool res = openDocument(QUrl::fromLocalFile(url));
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
}
void KisMainWindow::slotFileOpenRecent(const QUrl &url)
{
(void) openDocument(QUrl::fromLocalFile(url.toLocalFile()));
}
void KisMainWindow::slotFileSave()
{
if (saveDocument(d->activeView->document())) {
emit documentSaved();
}
}
void KisMainWindow::slotFileSaveAs()
{
if (saveDocument(d->activeView->document(), true)) {
emit documentSaved();
}
}
KoCanvasResourceManager *KisMainWindow::resourceManager() const
{
return d->viewManager->resourceProvider()->resourceManager();
}
int KisMainWindow::viewCount() const
{
return d->mdiArea->subWindowList().size();
}
bool KisMainWindow::restoreWorkspace(const QByteArray &state)
{
QByteArray oldState = saveState();
const bool showTitlebars = KisConfig().showDockerTitleBars();
// needed because otherwise the layout isn't correctly restored in some situations
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
dock->hide();
dock->titleBarWidget()->setVisible(showTitlebars);
}
bool success = KXmlGuiWindow::restoreState(state);
if (!success) {
KXmlGuiWindow::restoreState(oldState);
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating());
}
}
return false;
}
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget();
dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed));
}
}
return success;
}
KisViewManager *KisMainWindow::viewManager() const
{
return d->viewManager;
}
void KisMainWindow::slotDocumentInfo()
{
if (!d->activeView->document())
return;
KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
d->activeView->document()->setModified(false);
} else {
d->activeView->document()->setModified(true);
}
d->activeView->document()->setTitleModified();
}
delete dlg;
}
bool KisMainWindow::slotFileCloseAll()
{
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
if (subwin) {
if(!subwin->close())
return false;
}
}
updateCaption();
return true;
}
void KisMainWindow::slotFileQuit()
{
if(!slotFileCloseAll())
return;
close();
Q_FOREACH (QPointer<KisMainWindow> mainWin, KisPart::instance()->mainWindows()) {
if (mainWin != this) {
if(!mainWin->slotFileCloseAll())
return;
mainWin->close();
}
}
}
void KisMainWindow::slotFilePrint()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted) {
printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point);
printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()),
activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())),
QPrinter::Inch);
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
}
else {
delete printJob;
}
delete printDialog;
}
void KisMainWindow::slotFilePrintPreview()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KisPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName)
{
if (!activeView())
return 0;
if (!activeView()->document())
return 0;
KoPageLayout pageLayout;
pageLayout.width = 0;
pageLayout.height = 0;
pageLayout.topMargin = 0;
pageLayout.bottomMargin = 0;
pageLayout.leftMargin = 0;
pageLayout.rightMargin = 0;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
defaultDir = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation);
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();
document->setFileProgressProxy();
document->setFileProgressUpdater(i18n("Import frames"));
KisAnimationImporter importer(document);
KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
document->clearFileProgressUpdater();
document->clearFileProgressProxy();
if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty())
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
}
activeView()->canvasBase()->refetchDataFromImage();
}
}
void KisMainWindow::slotConfigureToolbars()
{
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
applyToolBarLayout();
}
void KisMainWindow::slotNewToolbarConfig()
{
applyMainWindowSettings(KSharedConfig::openConfig()->group("krita"));
KXMLGUIFactory *factory = guiFactory();
Q_UNUSED(factory);
// Check if there's an active view
if (!d->activeView)
return;
plugActionList("toolbarlist", d->toolbarList);
applyToolBarLayout();
}
void KisMainWindow::slotToolbarToggled(bool toggle)
{
//dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
// The action (sender) and the toolbar have the same name
KToolBar * bar = toolBar(sender()->objectName());
if (bar) {
if (toggle) {
bar->show();
}
else {
bar->hide();
}
if (d->activeView && d->activeView->document()) {
KConfigGroup group = KSharedConfig::openConfig()->group("krita");
saveMainWindowSettings(group);
}
} else
warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
void KisMainWindow::viewFullscreen(bool fullScreen)
{
KisConfig cfg;
cfg.setFullscreenMode(fullScreen);
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen); // set
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
}
}
void KisMainWindow::slotProgress(int value)
{
qApp->processEvents();
StdLockableWrapper<QMutex> wrapper(&d->progressMutex);
std::unique_lock<StdLockableWrapper<QMutex>> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return;
dbgUI << "KisMainWindow::slotProgress" << value;
if (value <= -1 || value >= 100) {
if (d->progress) {
statusBar()->removeWidget(d->progress);
delete d->progress;
d->progress = 0;
disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled()));
statusBar()->removeWidget(d->progressCancel);
delete d->progressCancel;
d->progressCancel = 0;
}
d->firstTime = true;
return;
}
if (d->firstTime || !d->progress) {
// The statusbar might not even be created yet.
// So check for that first, and create it if necessary
QStatusBar *bar = findChild<QStatusBar *>();
if (!bar) {
statusBar()->show();
QApplication::sendPostedEvents(this, QEvent::ChildAdded);
}
if (d->progress) {
statusBar()->removeWidget(d->progress);
delete d->progress;
d->progress = 0;
disconnect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled()));
statusBar()->removeWidget(d->progressCancel);
delete d->progressCancel;
d->progress = 0;
}
d->progressCancel = new QToolButton(statusBar());
d->progressCancel->setMaximumHeight(statusBar()->fontMetrics().height());
d->progressCancel->setIcon(KisIconUtils::loadIcon("process-stop"));
statusBar()->addPermanentWidget(d->progressCancel);
d->progress = new QProgressBar(statusBar());
d->progress->setMaximumHeight(statusBar()->fontMetrics().height());
d->progress->setRange(0, 100);
statusBar()->addPermanentWidget(d->progress);
connect(d->progressCancel, SIGNAL(clicked()), this, SLOT(slotProgressCanceled()));
d->progress->show();
d->progressCancel->show();
d->firstTime = false;
}
if (!d->progress.isNull()) {
d->progress->setValue(value);
}
qApp->processEvents();
}
void KisMainWindow::slotProgressCanceled()
{
emit sigProgressCanceled();
}
void KisMainWindow::setMaxRecentItems(uint _number)
{
d->recentFiles->setMaxItems(_number);
}
void KisMainWindow::slotReloadFile()
{
KisDocument* document = d->activeView->document();
if (!document || document->url().isEmpty())
return;
if (document->isModified()) {
bool ok = QMessageBox::question(this,
i18nc("@title:window", "Krita"),
i18n("You will lose all changes made since your last save\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
if (!ok)
return;
}
QUrl url = document->url();
saveWindowSettings();
if (!document->reload()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document"));
}
return;
}
void KisMainWindow::slotImportFile()
{
dbgUI << "slotImportFile()";
d->isImporting = true;
slotFileOpen();
d->isImporting = false;
}
void KisMainWindow::slotExportFile()
{
dbgUI << "slotExportFile()";
d->isExporting = true;
slotFileSaveAs();
d->isExporting = false;
}
QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory)
{
QDockWidget* dockWidget = 0;
if (!d->dockWidgetsMap.contains(factory->id())) {
dockWidget = factory->createDockWidget();
// It is quite possible that a dock factory cannot create the dock; don't
// do anything in that case.
if (!dockWidget) {
warnKrita << "Could not create docker for" << factory->id();
return 0;
}
KoDockWidgetTitleBar *titleBar = dynamic_cast<KoDockWidgetTitleBar*>(dockWidget->titleBarWidget());
// Check if the dock widget is supposed to be collapsable
if (!dockWidget->titleBarWidget()) {
titleBar = new KoDockWidgetTitleBar(dockWidget);
dockWidget->setTitleBarWidget(titleBar);
titleBar->setCollapsable(factory->isCollapsable());
}
titleBar->setFont(KoDockRegistry::dockFont());
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (dockWidget->widget() && dockWidget->widget()->layout())
dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
bool visible = true;
switch (factory->defaultDockPosition()) {
case KoDockFactoryBase::DockTornOff:
dockWidget->setFloating(true); // position nicely?
break;
case KoDockFactoryBase::DockTop:
side = Qt::TopDockWidgetArea; break;
case KoDockFactoryBase::DockLeft:
side = Qt::LeftDockWidgetArea; break;
case KoDockFactoryBase::DockBottom:
side = Qt::BottomDockWidgetArea; break;
case KoDockFactoryBase::DockRight:
side = Qt::RightDockWidgetArea; break;
case KoDockFactoryBase::DockMinimized:
default:
side = Qt::RightDockWidgetArea;
visible = false;
}
KConfigGroup group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id());
side = static_cast<Qt::DockWidgetArea>(group.readEntry("DockArea", static_cast<int>(side)));
if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
addDockWidget(side, dockWidget);
if (!visible) {
dockWidget->hide();
}
bool collapsed = factory->defaultCollapsed();
bool locked = false;
group = KSharedConfig::openConfig()->group("krita").group("DockWidget " + factory->id());
collapsed = group.readEntry("Collapsed", collapsed);
locked = group.readEntry("Locked", locked);
//dbgKrita << "docker" << factory->id() << dockWidget << "collapsed" << collapsed << "locked" << locked << "titlebar" << titleBar;
if (titleBar && collapsed)
titleBar->setCollapsed(true);
if (titleBar && locked)
titleBar->setLocked(true);
d->dockWidgetsMap.insert(factory->id(), dockWidget);
}
else {
dockWidget = d->dockWidgetsMap[factory->id()];
}
#ifdef Q_OS_OSX
dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
#endif
dockWidget->setFont(KoDockRegistry::dockFont());
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
return dockWidget;
}
void KisMainWindow::forceDockTabFonts()
{
Q_FOREACH (QObject *child, children()) {
if (child->inherits("QTabBar")) {
((QTabBar *)child)->setFont(KoDockRegistry::dockFont());
}
}
}
QList<QDockWidget*> KisMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
QDockWidget* KisMainWindow::dockWidget(const QString &id)
{
if (!d->dockWidgetsMap.contains(id)) return 0;
return d->dockWidgetsMap[id];
}
QList<KoCanvasObserverBase*> KisMainWindow::canvasObservers() const
{
QList<KoCanvasObserverBase*> observers;
Q_FOREACH (QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast<KoCanvasObserverBase*>(docker);
if (observer) {
observers << observer;
}
else {
warnKrita << docker << "is not a canvas observer";
}
}
return observers;
}
void KisMainWindow::toggleDockersVisibility(bool visible)
{
if (!visible) {
d->dockerStateBeforeHiding = saveState();
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast<QDockWidget*>(widget);
if (dw->isVisible()) {
dw->hide();
}
}
}
}
else {
restoreState(d->dockerStateBeforeHiding);
}
}
void KisMainWindow::setToolbarList(QList<QAction *> toolbarList)
{
qDeleteAll(d->toolbarList);
d->toolbarList = toolbarList;
}
void KisMainWindow::slotDocumentTitleModified(const QString &caption, bool mod)
{
updateCaption();
updateCaption(caption, mod);
updateReloadFileAction(d->activeView ? d->activeView->document() : 0);
}
void KisMainWindow::subWindowActivated()
{
bool enabled = (activeKisView() != 0);
d->mdiCascade->setEnabled(enabled);
d->mdiNextWindow->setEnabled(enabled);
d->mdiPreviousWindow->setEnabled(enabled);
d->mdiTile->setEnabled(enabled);
d->close->setEnabled(enabled);
d->closeAll->setEnabled(enabled);
setActiveSubWindow(d->mdiArea->activeSubWindow());
Q_FOREACH (QToolBar *tb, toolBars()) {
if (tb->objectName() == "BrushesAndStuff") {
tb->setEnabled(enabled);
}
}
updateCaption();
d->actionManager()->updateGUI();
}
void KisMainWindow::updateWindowMenu()
{
QMenu *menu = d->windowMenu->menu();
menu->clear();
menu->addAction(d->newWindow);
menu->addAction(d->documentMenu);
QMenu *docMenu = d->documentMenu->menu();
docMenu->clear();
Q_FOREACH (QPointer<KisDocument> doc, KisPart::instance()->documents()) {
if (doc) {
QString title = doc->url().toDisplayString();
- if (title.isEmpty()) title = doc->image()->objectName();
+ 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->addSeparator();
menu->addAction(d->close);
menu->addAction(d->closeAll);
if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
menu->addSeparator();
menu->addAction(d->mdiTile);
menu->addAction(d->mdiCascade);
}
menu->addSeparator();
menu->addAction(d->mdiNextWindow);
menu->addAction(d->mdiPreviousWindow);
menu->addSeparator();
QList<QMdiSubWindow *> windows = d->mdiArea->subWindowList();
for (int i = 0; i < windows.size(); ++i) {
QPointer<KisView>child = qobject_cast<KisView*>(windows.at(i)->widget());
if (child && child->document()) {
QString text;
if (i < 9) {
text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString());
}
else {
text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString());
}
QAction *action = menu->addAction(text);
action->setIcon(qApp->windowIcon());
action->setCheckable(true);
action->setChecked(child == activeKisView());
connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map()));
d->windowMapper->setMapping(action, windows.at(i));
}
}
updateCaption();
}
void KisMainWindow::setActiveSubWindow(QWidget *window)
{
if (!window) return;
QMdiSubWindow *subwin = qobject_cast<QMdiSubWindow *>(window);
//dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
if (subwin && subwin != d->activeSubWindow) {
KisView *view = qobject_cast<KisView *>(subwin->widget());
//dbgKrita << "\t" << view << activeView();
if (view && view != activeView()) {
d->mdiArea->setActiveSubWindow(subwin);
setActiveView(view);
}
d->activeSubWindow = subwin;
}
updateWindowMenu();
d->actionManager()->updateGUI();
}
void KisMainWindow::configChanged()
{
KisConfig cfg;
QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry<int>("mdi_viewmode", (int)QMdiArea::TabbedView);
d->mdiArea->setViewMode(viewMode);
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
}
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
d->actionManager()->updateGUI();
QBrush brush(cfg.getMDIBackgroundColor());
d->mdiArea->setBackground(brush);
QString backgroundImage = cfg.getMDIBackgroundImage();
if (backgroundImage != "") {
QImage image(backgroundImage);
QBrush brush(image);
d->mdiArea->setBackground(brush);
}
d->mdiArea->update();
}
void KisMainWindow::newView(QObject *document)
{
KisDocument *doc = qobject_cast<KisDocument*>(document);
addViewAndNotifyLoadingCompleted(doc);
d->actionManager()->updateGUI();
}
void KisMainWindow::newWindow()
{
KisPart::instance()->createMainWindow()->show();
}
void KisMainWindow::closeCurrentWindow()
{
d->mdiArea->currentSubWindow()->close();
d->actionManager()->updateGUI();
}
void KisMainWindow::checkSanity()
{
// print error if the lcms engine is not available
if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) {
// need to wait 1 event since exiting here would not work.
- m_errorMessage = i18n("The Calligra LittleCMS color management plugin is not installed. Krita will quit now.");
+ m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
if (rserver->resources().isEmpty()) {
m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
}
void KisMainWindow::showErrorAndDie()
{
QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage);
if (m_dieOnError) {
exit(10);
}
}
void KisMainWindow::showAboutApplication()
{
KisAboutApplication dlg(this);
dlg.exec();
}
QPointer<KisView>KisMainWindow::activeKisView()
{
if (!d->mdiArea) return 0;
QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
//dbgKrita << "activeKisView" << activeSubWindow;
if (!activeSubWindow) return 0;
return qobject_cast<KisView*>(activeSubWindow->widget());
}
void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList<QPointer<QWidget> > &optionWidgetList)
{
KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
bool isOurOwnView = false;
Q_FOREACH (QPointer<KisView> view, KisPart::instance()->views()) {
if (view && view->canvasController() == controller) {
isOurOwnView = view->mainWindow() == this;
}
}
if (!isOurOwnView) return;
Q_FOREACH (QWidget *w, optionWidgetList) {
#ifdef Q_OS_OSX
w->setAttribute(Qt::WA_MacSmallSize, true);
#endif
w->setFont(KoDockRegistry::dockFont());
}
if (d->toolOptionsDocker) {
d->toolOptionsDocker->setOptionWidgets(optionWidgetList);
}
else {
d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList);
}
}
void KisMainWindow::applyDefaultSettings(QPrinter &printer) {
if (!d->activeView) return;
QString title = d->activeView->document()->documentInfo()->aboutInfo("title");
if (title.isEmpty()) {
title = d->activeView->document()->url().fileName();
// strip off the native extension (I don't want foobar.kwd.ps when printing into a file)
QString extension = KisMimeDatabase::suffixesForMimeType(d->activeView->document()->outputMimeType()).first();
if (title.endsWith(extension)) {
title.chop(extension.length());
}
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
void KisMainWindow::createActions()
{
KisActionManager *actionManager = d->actionManager();
KisConfig cfg;
actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew()));
actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen()));
actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit()));
actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars()));
actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
KSharedConfigPtr configPtr = KSharedConfig::openConfig();
d->recentFiles->loadEntries(configPtr->group("RecentFiles"));
d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave()));
d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs()));
d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint()));
// d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview()));
// d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo()));
d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo()));
d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->exportPdf = actionManager->createAction("file_export_pdf");
// connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->importAnimation = actionManager->createAction("file_import_animation");
connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
d->closeAll = actionManager->createAction("file_close_all");
connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
// d->reloadFile = actionManager->createAction("file_reload_file");
// d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED);
// connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->importFile = actionManager->createAction("file_import_file");
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = actionManager->createAction("file_export_file");
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = actionManager->createAction("file_documentinfo");
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
d->themeManager->registerThemeActions(actionCollection());
connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
cfg.showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars");
d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars());
connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool)));
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
actionCollection()->addAction("window", d->windowMenu);
d->mdiCascade = actionManager->createAction("windows_cascade");
connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
d->mdiTile = actionManager->createAction("windows_tile");
connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
d->mdiNextWindow = actionManager->createAction("windows_next");
connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
d->mdiPreviousWindow = actionManager->createAction("windows_previous");
connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
d->newWindow = actionManager->createAction("view_newwindow");
connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
d->close = actionManager->createAction("file_close");
connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow()));
actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences()));
for (int i = 0; i < 2; i++) {
d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer"));
d->expandingSpacers[i]->setDefaultWidget(new QWidget(this));
d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]);
}
}
void KisMainWindow::applyToolBarLayout()
{
const bool isPlastiqueStyle = style()->objectName() == "plastique";
Q_FOREACH (KToolBar *toolBar, toolBars()) {
toolBar->layout()->setSpacing(4);
if (isPlastiqueStyle) {
toolBar->setContentsMargins(0, 0, 0, 2);
}
//Hide text for buttons with an icon in the toolbar
Q_FOREACH (QAction *ac, toolBar->actions()){
if (ac->icon().pixmap(QSize(1,1)).isNull() == false){
ac->setPriority(QAction::LowPriority);
}else {
ac->setIcon(QIcon());
}
}
}
}
void KisMainWindow::initializeGeometry()
{
// if the user didn's specify the geometry on the command line (does anyone do that still?),
// we first figure out some good default size and restore the x,y position. See bug 285804Z.
KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow");
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to componensate for the window decs
w = (deskWidth / 3) * 2;
h = (desk.height() / 3) * 2;
}
else {
w = desk.width();
h = desk.height();
}
x += (desk.width() - w) / 2;
y += (desk.height() - h) / 2;
move(x,y);
setGeometry(geometry().x(), geometry().y(), w, h);
}
restoreWorkspace(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray())));
}
void KisMainWindow::showManual()
{
QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
}
void KisMainWindow::showDockerTitleBars(bool show)
{
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
if (dock->titleBarWidget()) {
const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget();
dock->titleBarWidget()->setVisible(show || (dock->isFloating() && isCollapsed));
}
}
KisConfig cfg;
cfg.setShowDockerTitleBars(show);
}
void KisMainWindow::moveEvent(QMoveEvent *e)
{
if (qApp->desktop()->screenNumber(this) != qApp->desktop()->screenNumber(e->oldPos())) {
KisConfigNotifier::instance()->notifyConfigChanged();
}
}
#include <moc_KisMainWindow.cpp>
diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h
index 25675348a6..e1ef965785 100644
--- a/libs/ui/KisMainWindow.h
+++ b/libs/ui/KisMainWindow.h
@@ -1,454 +1,459 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2004 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MAIN_WINDOW_H
#define KIS_MAIN_WINDOW_H
#include "kritaui_export.h"
#include <QPointer>
#include <QPrinter>
#include <xmlgui/kxmlguiwindow.h>
#include <QUrl>
#include <KoCanvasObserverBase.h>
#include <KoCanvasSupervisor.h>
#include "KisView.h"
class QCloseEvent;
class QMoveEvent;
struct KoPageLayout;
class KoCanvasResourceManager;
class KisDocument;
class KisPrintJob;
class KoDockFactoryBase;
class QDockWidget;
class KisView;
class KisViewManager;
class KoCanvasController;
/**
* @brief Main window for Krita
*
* This class is used to represent a main window within a Krita session. Each
* main window contains a menubar and some toolbars, and potentially several
* views of several canvases.
*
*/
class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor
{
Q_OBJECT
public:
/**
* Constructor.
*
* Initializes a Calligra main window (with its basic GUI etc.).
*/
explicit KisMainWindow();
/**
* Destructor.
*/
virtual ~KisMainWindow();
/**
* Update caption from document info - call when document info
* (title in the about page) changes.
*/
void updateCaption();
// If noCleanup is set, KisMainWindow will not delete the root document
// or part manager on destruction.
void setNoCleanup(bool noCleanup);
/**
* @brief showView shows the given view. Override this if you want to show
* the view in a different way than by making it the central widget, for instance
* as an QMdiSubWindow
*/
virtual void showView(KisView *view);
/**
* @returns the currently active view
*/
KisView *activeView() const;
/**
* Sets the maximum number of recent documents entries.
*/
void setMaxRecentItems(uint _number);
/**
* The document opened a URL -> store into recent documents list.
*/
void addRecentURL(const QUrl &url);
/**
* Load the desired document and show it.
* @param url the URL to open
*
* @return TRUE on success.
*/
bool openDocument(const QUrl &url);
void setReadWrite(bool readwrite);
/// Return the list of dock widgets belonging to this main window.
QList<QDockWidget*> dockWidgets() const;
QDockWidget* dockWidget(const QString &id);
QList<KoCanvasObserverBase*> canvasObservers() const;
KoCanvasResourceManager *resourceManager() const;
int viewCount() const;
/**
* A wrapper around restoreState
* @param state the saved state
* @return TRUE on success
*/
bool restoreWorkspace(const QByteArray &state);
KisViewManager *viewManager() const;
void addViewAndNotifyLoadingCompleted(KisDocument *document);
QStringList showOpenFileDialog();
/**
* Shows if the main window is saving anything right now. If the
* user presses Ctrl+W too fast, then the document can be close
* before the saving is completed. I'm not sure if it is fixable
* in any way without avoiding using porcessEvents()
* everywhere (DK)
*
* Don't use it unless you have no option.
*/
bool hackIsSaving() const;
Q_SIGNALS:
/**
* This signal is emitted if the document has been saved successfully.
*/
void documentSaved();
/// This signal is emitted when this windows has finished loading of a
/// document. The document may be opened in another window in the end.
/// In this case, the signal means there is no link between the window
/// and the document anymore.
void loadCompleted();
/// This signal is emitted right after the docker states have been succefully restored from config
void restoringDone();
/// This signal is emitted when the color theme changes
void themeChanged();
/// This signal is emitted when the shortcut key configuration has changed
void keyBindingsChanged();
void guiLoadingFinished();
/// This signal is emitted when the user clicked on the progressbar cancel
void sigProgressCanceled();
public Q_SLOTS:
/**
* Slot for opening a new document.
*
* If the current document is empty, the new document replaces it.
* If not, a new mainwindow will be opened for showing the document.
*/
void slotFileNew();
/**
* Slot for opening a saved file.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpen();
/**
* Slot for opening a file among the recently opened files.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpenRecent(const QUrl &);
/**
* @brief slotPreferences open the preferences dialog
*/
void slotPreferences();
/**
* Saves the current document with the current name.
*/
void slotFileSave();
// XXX: disabled
KisPrintJob* exportToPdf(QString pdfFileName = QString());
void slotProgress(int value);
/**
* Saves the document, asking for a filename if necessary.
*
* @param saveas if set to TRUE the user is always prompted for a filename
* @param silent if set to TRUE rootDocument()->setTitleModified will not be called.
*
* @return TRUE on success, false on error or cancel
* (don't display anything in this case, the error dialog box is also implemented here
* but restore the original URL in slotFileSaveAs)
*/
bool saveDocument(KisDocument *document, bool saveas = false);
/**
* Update the option widgets to the argument ones, removing the currently set widgets.
*/
void newOptionWidgets(KoCanvasController *controller, const QList<QPointer<QWidget> > & optionWidgetList);
private Q_SLOTS:
/**
* Save the list of recent files.
*/
void saveRecentFiles();
void slotLoadCompleted();
void slotLoadCanceled(const QString &);
void slotSaveCompleted();
void slotSaveCanceled(const QString &);
void forceDockTabFonts();
void slotProgressCanceled();
/**
* @internal
*/
void slotDocumentTitleModified(const QString &caption, bool mod);
/**
* Prints the actual document.
*/
void slotFilePrint();
/**
* Saves the current document with a new name.
*/
void slotFileSaveAs();
void slotFilePrintPreview();
void importAnimation();
/**
* Show a dialog with author and document information.
*/
void slotDocumentInfo();
/**
* Closes all open documents.
*/
bool slotFileCloseAll();
/**
* @brief showAboutApplication show the about box
*/
virtual void showAboutApplication();
/**
* Closes the mainwindow.
*/
void slotFileQuit();
/**
* Configure toolbars.
*/
void slotConfigureToolbars();
/**
* Post toolbar config.
* (Plug action lists back in, etc.)
*/
void slotNewToolbarConfig();
/**
* Shows or hides a toolbar
*/
void slotToolbarToggled(bool toggle);
/**
* Toggle full screen on/off.
*/
void viewFullscreen(bool fullScreen);
/**
* Toggle docker titlebars on/off.
*/
void showDockerTitleBars(bool show);
/**
* Reload file
*/
void slotReloadFile();
/**
* File --> Import
*
* This will call slotFileOpen().
*/
void slotImportFile();
/**
* File --> Export
*
* This will call slotFileSaveAs().
*/
void slotExportFile();
/**
* Hide the dockers
*/
void toggleDockersVisibility(bool visible);
/**
* Handle theme changes from theme manager
*/
void slotThemeChanged();
void undo();
void redo();
- void subWindowActivated();
void updateWindowMenu();
void setActiveSubWindow(QWidget *window);
void configChanged();
void newView(QObject *document);
void newWindow();
void closeCurrentWindow();
void checkSanity();
/// Quits Krita with error message from m_errorMessage.
void showErrorAndDie();
protected:
void closeEvent(QCloseEvent * e);
void resizeEvent(QResizeEvent * e);
- /// Set the active view, this will update the undo/redo actions
- void setActiveView(KisView *view);
-
// QWidget overrides
virtual void dragEnterEvent(QDragEnterEvent * event);
virtual void dropEvent(QDropEvent * event);
virtual void dragMoveEvent(QDragMoveEvent * event);
virtual void dragLeaveEvent(QDragLeaveEvent * event);
void setToolbarList(QList<QAction*> toolbarList);
-private:
+
+public Q_SLOTS:
+
/**
* Add a the given view to the list of views of this mainwindow.
* This is a private implementation. For public usage please use
* newView() and addViewAndNotifyLoadingCompleted().
*/
void addView(KisView *view);
+ /// Set the active view, this will update the undo/redo actions
+ void setActiveView(KisView *view);
+
+ void subWindowActivated();
+
+
+private:
friend class KisApplication;
/**
* Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created.
* Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets.
* @param factory the factory used to create the dock widget if needed
* @return the dock widget specified by @p factory (may be 0)
*/
QDockWidget* createDockWidget(KoDockFactoryBase* factory);
bool openDocumentInternal(const QUrl &url, KisDocument *newdoc = 0);
/**
* Reloads the recent documents list.
*/
void reloadRecentFileList();
/**
* Updates the window caption based on the document info and path.
*/
void updateCaption(const QString & caption, bool mod);
void updateReloadFileAction(KisDocument *doc);
void saveWindowSettings();
QPointer<KisView>activeKisView();
void applyDefaultSettings(QPrinter &printer);
void createActions();
void applyToolBarLayout();
protected:
void moveEvent(QMoveEvent *e);
private Q_SLOTS:
void initializeGeometry();
void showManual();
void switchTab(int index);
private:
/**
* Struct used in the list created by createCustomDocumentWidgets()
*/
struct CustomDocumentWidgetItem {
/// Pointer to the custom document widget
QWidget *widget;
/// title used in the sidebar. If left empty it will be displayed as "Custom Document"
QString title;
/// icon used in the sidebar. If left empty it will use the unknown icon
QString icon;
};
class Private;
Private * const d;
QString m_errorMessage;
bool m_dieOnError;
};
#endif
diff --git a/libs/ui/KisNodeDelegate.cpp b/libs/ui/KisNodeDelegate.cpp
index 4f71bcd43d..42cfd32347 100644
--- a/libs/ui/KisNodeDelegate.cpp
+++ b/libs/ui/KisNodeDelegate.cpp
@@ -1,872 +1,872 @@
/*
Copyright (c) 2006 Gábor Lehel <illissius@gmail.com>
Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
Copyright (c) 2011 José Luis Vergara <pentalis@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_config.h"
#include "KisNodeDelegate.h"
#include "kis_node_model.h"
#include "KisNodeToolTip.h"
#include "KisNodeView.h"
#include "KisPart.h"
#include "input/kis_input_manager.h"
#include <QtDebug>
#include <QApplication>
#include <QKeyEvent>
#include <QLineEdit>
#include <QModelIndex>
#include <QMouseEvent>
#include <QPainter>
#include <QPointer>
#include <QStyle>
#include <QStyleOptionViewItem>
#include <klocalizedstring.h>
#include "kis_node_view_color_scheme.h"
#include "kis_icon_utils.h"
#include "kis_layer_properties_icons.h"
#include "krita_utils.h"
typedef KisBaseNode::Property* OptionalProperty;
#include <kis_base_node.h>
class KisNodeDelegate::Private
{
public:
Private() : view(0), edit(0) { }
KisNodeView *view;
QPointer<QWidget> edit;
KisNodeToolTip tip;
QList<OptionalProperty> rightmostProperties(const KisBaseNode::PropertyList &props) const;
int numProperties(const QModelIndex &index) const;
OptionalProperty findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const;
OptionalProperty findVisibilityProperty(KisBaseNode::PropertyList &props) const;
void toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty prop, bool controlPressed, const QModelIndex &index);
};
void KisNodeDelegate::slotOnCloseEditor()
{
- KisPart::currentInputManager()->slotFocusOnEnter(true);
+ KisPart::instance()->currentInputManager()->slotFocusOnEnter(true);
}
KisNodeDelegate::KisNodeDelegate(KisNodeView *view, QObject *parent)
: QAbstractItemDelegate(parent)
, d(new Private)
{
d->view = view;
QApplication::instance()->installEventFilter(this);
connect(this, SIGNAL(closeEditor(QWidget*)), this, SLOT(slotOnCloseEditor()));
}
KisNodeDelegate::~KisNodeDelegate()
{
delete d;
}
QSize KisNodeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
return QSize(option.rect.width(), scm.rowHeight());
}
void KisNodeDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const
{
p->save();
{
QStyleOptionViewItem option = getOptions(o, index);
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget);
bool shouldGrayOut = index.data(KisNodeModel::ShouldGrayOutRole).toBool();
if (shouldGrayOut) {
option.state &= ~QStyle::State_Enabled;
}
p->setFont(option.font);
drawColorLabel(p, option, index);
drawFrame(p, option, index);
drawThumbnail(p, option, index);
drawText(p, option, index);
drawIcons(p, option, index);
drawVisibilityIconHijack(p, option, index);
drawDecoration(p, option, index);
drawExpandButton(p, option, index);
drawBranch(p, option, index);
drawProgressBar(p, option, index);
}
p->restore();
}
void KisNodeDelegate::drawBranch(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const {
Q_UNUSED(index);
KisNodeViewColorScheme scm;
const QPoint base = scm.relThumbnailRect().translated(option.rect.topLeft()).topLeft() - QPoint( scm.indentation(), 0);
// there is no indention if we are starting negative, so don't draw a branch
if (base.x() < 0) {
return;
}
QPen oldPen = p->pen();
const qreal oldOpacity = p->opacity(); // remember previous opacity
p->setOpacity(1.0);
QColor color = scm.gridColor(option, d->view);
QColor bgColor = option.state & QStyle::State_Selected ?
qApp->palette().color(QPalette::Base) :
qApp->palette().color(QPalette::Text);
color = KritaUtils::blendColors(color, bgColor, 0.9);
// TODO: if we are a mask type, use dotted lines for the branch style
// p->setPen(QPen(p->pen().color(), 2, Qt::DashLine, Qt::RoundCap, Qt::RoundJoin));
p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
QPoint p2 = base + QPoint(scm.iconSize() - scm.decorationMargin()*2, scm.iconSize()*0.45);
QPoint p3 = base + QPoint(scm.iconSize() - scm.decorationMargin()*2, scm.iconSize());
QPoint p4 = base + QPoint(scm.iconSize()*1.4, scm.iconSize());
p->drawLine(p2, p3);
p->drawLine(p3, p4);
// draw parent lines (keep drawing until x position is less than 0
QPoint p5 = p2 - QPoint(scm.indentation(), 0);
QPoint p6 = p3 - QPoint(scm.indentation(), 0);
QPoint parentBase1 = p5;
QPoint parentBase2 = p6;
// indent lines needs to be very subtle to avoid making the docker busy looking
color = KritaUtils::blendColors(color, bgColor, 0.9); // makes it a little lighter than L lines
p->setPen(QPen(color, 0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
while (parentBase1.x() > scm.visibilityColumnWidth()) {
p->drawLine(parentBase1, parentBase2);
parentBase1 = parentBase1 - QPoint(scm.indentation(), 0);
parentBase2 = parentBase2 - QPoint(scm.indentation(), 0);
}
p->setPen(oldPen);
p->setOpacity(oldOpacity);
}
void KisNodeDelegate::drawColorLabel(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const int label = index.data(KisNodeModel::ColorLabelIndexRole).toInt();
QColor color = scm.colorLabel(label);
if (color.alpha() <= 0) return;
QColor bgColor = qApp->palette().color(QPalette::Base);
color = KritaUtils::blendColors(color, bgColor, 0.3);
const QRect rect = option.state & QStyle::State_Selected ?
iconsRect(option, index) :
option.rect.adjusted(-scm.indentation(), 0, 0, 0);
p->fillRect(rect, color);
}
void KisNodeDelegate::drawFrame(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
QPen oldPen = p->pen();
p->setPen(scm.gridColor(option, d->view));
const QPoint base = option.rect.topLeft();
QPoint p2 = base + QPoint(-scm.indentation() - 1, 0);
QPoint p3 = base + QPoint(2 * scm.decorationMargin() + scm.decorationSize(), 0);
QPoint p4 = base + QPoint(-1, 0);
QPoint p5(iconsRect(option,
index).left() - 1, base.y());
QPoint p6(option.rect.right(), base.y());
QPoint v(0, option.rect.height());
// draw a line that goes the length of the entire frame. one for the
// top, and one for the bottom
QPoint pTopLeft(0, option.rect.topLeft().y());
QPoint pTopRight(option.rect.bottomRight().x(),option.rect.topLeft().y() );
p->drawLine(pTopLeft, pTopRight);
QPoint pBottomLeft(0, option.rect.topLeft().y() + scm.rowHeight());
QPoint pBottomRight(option.rect.bottomRight().x(),option.rect.topLeft().y() + scm.rowHeight() );
p->drawLine(pBottomLeft, pBottomRight);
const bool paintForParent =
index.parent().isValid() &&
!index.row();
if (paintForParent) {
QPoint p1(-2 * scm.indentation() - 1, 0);
p1 += base;
p->drawLine(p1, p2);
}
QPoint k0(0, base.y());
QPoint k1(1 * scm.border() + 2 * scm.visibilityMargin() + scm.visibilitySize(), base.y());
p->drawLine(k0, k1);
p->drawLine(k0 + v, k1 + v);
p->drawLine(k0, k0 + v);
p->drawLine(k1, k1 + v);
p->drawLine(p2, p6);
p->drawLine(p2 + v, p6 + v);
p->drawLine(p2, p2 + v);
p->drawLine(p3, p3 + v);
p->drawLine(p4, p4 + v);
p->drawLine(p5, p5 + v);
p->drawLine(p6, p6 + v);
//// For debugging purposes only
//p->setPen(Qt::blue);
//KritaUtils::renderExactRect(p, iconsRect(option, index));
//KritaUtils::renderExactRect(p, textRect(option, index));
//KritaUtils::renderExactRect(p, scm.relThumbnailRect().translated(option.rect.topLeft()));
p->setPen(oldPen);
}
void KisNodeDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const int thumbSize = scm.thumbnailSize();
const qreal oldOpacity = p->opacity(); // remember previous opacity
QImage img = index.data(int(KisNodeModel::BeginThumbnailRole) + thumbSize).value<QImage>();
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.35);
}
QRect fitRect = scm.relThumbnailRect().translated(option.rect.topLeft());
QPoint offset;
offset.setX((fitRect.width() - img.width()) / 2);
offset.setY((fitRect.height() - img.height()) / 2);
offset += fitRect.topLeft();
KisConfig cfg;
// paint in a checkerboard pattern behind the layer contents to represent transparent
const int step = scm.thumbnailSize() / 6;
QImage checkers(2 * step, 2 * step, QImage::Format_ARGB32);
QPainter gc(&checkers);
gc.fillRect(QRect(0, 0, step, step), cfg.checkersColor1());
gc.fillRect(QRect(step, 0, step, step), cfg.checkersColor2());
gc.fillRect(QRect(step, step, step, step), cfg.checkersColor1());
gc.fillRect(QRect(0, step, step, step), cfg.checkersColor2());
QBrush brush(checkers);
p->setBrushOrigin(offset);
p->fillRect(img.rect().translated(offset), brush);
p->drawImage(offset, img);
p->setOpacity(oldOpacity); // restore old opacity
QRect borderRect = kisGrowRect(img.rect(), 1).translated(offset);
KritaUtils::renderExactRect(p, borderRect, scm.gridColor(option, d->view));
}
QRect KisNodeDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
int propCount = d->numProperties(index);
const int iconsWidth =
propCount * (scm.iconSize() + 2 * scm.iconMargin()) +
(propCount - 1) * scm.border();
const int x = option.rect.x() + option.rect.width()
- (iconsWidth + scm.border());
const int y = option.rect.y() + scm.border();
return QRect(x, y,
iconsWidth,
scm.rowHeight() - scm.border());
}
QRect KisNodeDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
static QFont f;
static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
if (minbearing == 2003 || f != option.font) {
f = option.font; //getting your bearings can be expensive, so we cache them
minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing();
}
const int decorationOffset =
2 * scm.border() +
2 * scm.decorationMargin() +
scm.decorationSize();
const int width =
iconsRect(option, index).left() - option.rect.x() -
scm.border() + minbearing - decorationOffset;
return QRect(option.rect.x() - minbearing + decorationOffset,
option.rect.y() + scm.border(),
width,
scm.rowHeight() - scm.border());
}
void KisNodeDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const QRect rc = textRect(option, index)
.adjusted(scm.textMargin(), 0, -scm.textMargin(), 0);
QPen oldPen = p->pen();
const qreal oldOpacity = p->opacity(); // remember previous opacity
p->setPen(option.palette.color(QPalette::Active,QPalette::Text ));
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.55);
}
const QString text = index.data(Qt::DisplayRole).toString();
const QString elided = elidedText(p->fontMetrics(), rc.width(), Qt::ElideRight, text);
p->drawText(rc, Qt::AlignLeft | Qt::AlignVCenter, elided);
p->setPen(oldPen); // restore pen settings
p->setOpacity(oldOpacity);
}
QList<OptionalProperty> KisNodeDelegate::Private::rightmostProperties(const KisBaseNode::PropertyList &props) const
{
QList<OptionalProperty> list;
QList<OptionalProperty> prependList;
list << OptionalProperty(0);
list << OptionalProperty(0);
list << OptionalProperty(0);
KisBaseNode::PropertyList::const_iterator it = props.constBegin();
KisBaseNode::PropertyList::const_iterator end = props.constEnd();
for (; it != end; ++it) {
if (!it->isMutable) continue;
if (it->id == KisLayerPropertiesIcons::visible.id()) {
// noop...
} else if (it->id == KisLayerPropertiesIcons::locked.id()) {
list[0] = OptionalProperty(&(*it));
} else if (it->id == KisLayerPropertiesIcons::inheritAlpha.id()) {
list[1] = OptionalProperty(&(*it));
} else if (it->id == KisLayerPropertiesIcons::alphaLocked.id()) {
list[2] = OptionalProperty(&(*it));
} else {
prependList.prepend(OptionalProperty(&(*it)));
}
}
{
QMutableListIterator<OptionalProperty> i(prependList);
i.toBack();
while (i.hasPrevious()) {
OptionalProperty val = i.previous();
int emptyIndex = list.lastIndexOf(0);
if (emptyIndex < 0) break;
list[emptyIndex] = val;
i.remove();
}
}
return prependList + list;
}
int KisNodeDelegate::Private::numProperties(const QModelIndex &index) const
{
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
QList<OptionalProperty> realProps = rightmostProperties(props);
return realProps.size();
}
OptionalProperty KisNodeDelegate::Private::findProperty(KisBaseNode::PropertyList &props, const OptionalProperty &refProp) const
{
KisBaseNode::PropertyList::iterator it = props.begin();
KisBaseNode::PropertyList::iterator end = props.end();
for (; it != end; ++it) {
if (it->id == refProp->id) {
return &(*it);
}
}
return 0;
}
OptionalProperty KisNodeDelegate::Private::findVisibilityProperty(KisBaseNode::PropertyList &props) const
{
KisBaseNode::PropertyList::iterator it = props.begin();
KisBaseNode::PropertyList::iterator end = props.end();
for (; it != end; ++it) {
if (it->id == KisLayerPropertiesIcons::visible.id()) {
return &(*it);
}
}
return 0;
}
void KisNodeDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
const QRect r = iconsRect(option, index);
QTransform oldTransform = p->transform();
QPen oldPen = p->pen();
p->setTransform(QTransform::fromTranslate(r.x(), r.y()));
p->setPen(scm.gridColor(option, d->view));
int x = 0;
const int y = (scm.rowHeight() - scm.border() - scm.iconSize()) / 2;
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
QList<OptionalProperty> realProps = d->rightmostProperties(props);
Q_FOREACH (OptionalProperty prop, realProps) {
x += scm.iconMargin();
if (prop) {
QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
bool fullColor = prop->state.toBool() && option.state & QStyle::State_Enabled;
const qreal oldOpacity = p->opacity(); // remember previous opacity
if (fullColor) {
p->setOpacity(1.0);
}
else {
p->setOpacity(0.35);
}
p->drawPixmap(x, y, icon.pixmap(scm.iconSize(), QIcon::Normal));
p->setOpacity(oldOpacity); // restore old opacity
}
x += scm.iconSize() + scm.iconMargin();
p->drawLine(x, 0, x, scm.rowHeight() - scm.border());
x += scm.border();
}
p->setTransform(oldTransform);
p->setPen(oldPen);
}
QRect KisNodeDelegate::visibilityClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
return QRect(scm.border(), scm.border() + option.rect.top(),
2 * scm.visibilityMargin() + scm.visibilitySize(),
scm.rowHeight() - scm.border());
}
QRect KisNodeDelegate::decorationClickRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect realVisualRect = d->view->originalVisualRect(index);
return QRect(realVisualRect.left(), scm.border() + realVisualRect.top(),
2 * scm.decorationMargin() + scm.decorationSize(),
scm.rowHeight() - scm.border());
}
void KisNodeDelegate::drawVisibilityIconHijack(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
/**
* Small hack Alert:
*
* Here wepaint over the area that sits basically outside our layer's
* row. Anyway, just update it later...
*/
KisNodeViewColorScheme scm;
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = d->findVisibilityProperty(props);
if (!prop) return;
const int x = scm.border() + scm.visibilityMargin();
const int y = option.rect.top() + (scm.rowHeight() - scm.border() - scm.visibilitySize()) / 2;
QIcon icon = prop->state.toBool() ? prop->onIcon : prop->offIcon;
p->setOpacity(1.0);
p->drawPixmap(x, y, icon.pixmap(scm.visibilitySize(), QIcon::Normal));
//// For debugging purposes only
// p->save();
// p->setPen(Qt::blue);
// KritaUtils::renderExactRect(p, visibilityClickRect(option, index));
// p->restore();
}
void KisNodeDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
KisNodeViewColorScheme scm;
QIcon icon = index.data(Qt::DecorationRole).value<QIcon>();
if (!icon.isNull()) {
QPixmap pixmap =
icon.pixmap(scm.decorationSize(),
(option.state & QStyle::State_Enabled) ?
QIcon::Normal : QIcon::Disabled);
const QRect rc = scm.relDecorationRect().translated(option.rect.topLeft());
const qreal oldOpacity = p->opacity(); // remember previous opacity
if (!(option.state & QStyle::State_Enabled)) {
p->setOpacity(0.35);
}
p->drawPixmap(rc.topLeft(), pixmap);
p->setOpacity(oldOpacity); // restore old opacity
}
}
void KisNodeDelegate::drawExpandButton(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
KisNodeViewColorScheme scm;
QRect rc = scm.relExpandButtonRect().translated(option.rect.topLeft());
rc = kisGrowRect(rc, 0);
if (!(option.state & QStyle::State_Children)) {
return;
}
QString iconName = option.state & QStyle::State_Open ? "arrow-down" : "arrow-right";
QIcon icon = KisIconUtils::loadIcon(iconName);
QPixmap pixmap = icon.pixmap(rc.width(),
(option.state & QStyle::State_Enabled) ?
QIcon::Normal : QIcon::Disabled);
p->drawPixmap(rc.topLeft(), pixmap);
}
void KisNodeDelegate::Private::toggleProperty(KisBaseNode::PropertyList &props, OptionalProperty clickedProperty, bool controlPressed, const QModelIndex &index)
{
QAbstractItemModel *model = view->model();
// Using Ctrl+click to enter stasis
if (controlPressed && clickedProperty->canHaveStasis) {
// STEP 0: Prepare to Enter or Leave control key stasis
quint16 numberOfLeaves = model->rowCount(index.parent());
QModelIndex eachItem;
// STEP 1: Go.
if (clickedProperty->isInStasis == false) { // Enter
/* Make every leaf of this node go State = False, saving the old property value to stateInStasis */
for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent())
eachItem = model->index(i, 1, index.parent());
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = findProperty(eachPropertyList, clickedProperty);
if (!prop) continue;
prop->stateInStasis = prop->state.toBool();
prop->state = eachItem == index;
prop->isInStasis = true;
model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole);
}
for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent())
eachItem = model->index(i, 1, index.parent());
KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = findProperty(eachPropertyList, clickedProperty);
if (!prop) continue;
}
} else { // Leave
/* Make every leaf of this node go State = stateInStasis */
for (quint16 i = 0; i < numberOfLeaves; ++i) {
eachItem = model->index(i, 1, index.parent());
// The entire property list has to be altered because model->setData cannot set individual properties
KisBaseNode::PropertyList eachPropertyList = eachItem.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty prop = findProperty(eachPropertyList, clickedProperty);
if (!prop) continue;
prop->state = prop->stateInStasis;
prop->isInStasis = false;
model->setData(eachItem, QVariant::fromValue(eachPropertyList), KisNodeModel::PropertiesRole);
}
}
} else {
clickedProperty->state = !clickedProperty->state.toBool();
model->setData(index, QVariant::fromValue(props), KisNodeModel::PropertiesRole);
}
}
bool KisNodeDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
KisNodeViewColorScheme scm;
if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
&& (index.flags() & Qt::ItemIsEnabled))
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
/**
* Small hack Alert:
*
* Here we handle clicking even when it happened outside
* the rectangle of the current index. The point is, we
* use some virtual scroling offset to move the tree to the
* right of the visibility icon. So the icon itself is placed
* in an empty area that doesn't belong to any index. But we still
* handle it.
*/
const QRect iconsRect = this->iconsRect(option, index);
const bool iconsClicked = iconsRect.isValid() &&
iconsRect.contains(mouseEvent->pos());
const QRect visibilityRect = visibilityClickRect(option, index);
const bool visibilityClicked = visibilityRect.isValid() &&
visibilityRect.contains(mouseEvent->pos());
const QRect decorationRect = decorationClickRect(option, index);
const bool decorationClicked = decorationRect.isValid() &&
decorationRect.contains(mouseEvent->pos());
const bool leftButton = mouseEvent->buttons() & Qt::LeftButton;
if (leftButton && iconsClicked) {
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
QList<OptionalProperty> realProps = d->rightmostProperties(props);
const int numProps = realProps.size();
const int iconWidth = scm.iconSize() + 2 * scm.iconMargin() + scm.border();
const int xPos = mouseEvent->pos().x() - iconsRect.left();
const int clickedIcon = xPos / iconWidth;
const int distToBorder = qMin(xPos % iconWidth, iconWidth - xPos % iconWidth);
if (iconsClicked &&
clickedIcon >= 0 &&
clickedIcon < numProps &&
distToBorder > scm.iconMargin()) {
OptionalProperty clickedProperty = realProps[clickedIcon];
if (!clickedProperty) return false;
d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index);
return true;
}
} else if (leftButton && visibilityClicked) {
KisBaseNode::PropertyList props = index.data(KisNodeModel::PropertiesRole).value<KisBaseNode::PropertyList>();
OptionalProperty clickedProperty = d->findVisibilityProperty(props);
if (!clickedProperty) return false;
d->toggleProperty(props, clickedProperty, mouseEvent->modifiers() == Qt::ControlModifier, index);
return true;
} else if (leftButton && decorationClicked) {
bool isExpandable = model->hasChildren(index);
if (isExpandable) {
bool isExpanded = d->view->isExpanded(index);
d->view->setExpanded(index, !isExpanded);
return true;
}
}
if (mouseEvent->button() == Qt::LeftButton &&
mouseEvent->modifiers() == Qt::AltModifier) {
d->view->setCurrentIndex(index);
model->setData(index, true, KisNodeModel::AlternateActiveRole);
return true;
}
}
else if (event->type() == QEvent::ToolTip) {
if (!KisConfig().hidePopups()) {
QHelpEvent *helpEvent = static_cast<QHelpEvent*>(event);
d->tip.showTip(d->view, helpEvent->pos(), option, index);
}
return true;
} else if (event->type() == QEvent::Leave) {
d->tip.hide();
}
return false;
}
QWidget *KisNodeDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const
{
- KisPart::currentInputManager()->slotFocusOnEnter(false);
+ KisPart::instance()->currentInputManager()->slotFocusOnEnter(false);
d->edit = new QLineEdit(parent);
d->edit->installEventFilter(const_cast<KisNodeDelegate*>(this)); //hack?
return d->edit;
}
void KisNodeDelegate::setEditorData(QWidget *widget, const QModelIndex &index) const
{
QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
Q_ASSERT(edit);
edit->setText(index.data(Qt::DisplayRole).toString());
}
void KisNodeDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
{
QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
Q_ASSERT(edit);
model->setData(index, edit->text(), Qt::DisplayRole);
}
void KisNodeDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index);
widget->setGeometry(option.rect);
}
// PROTECTED
bool KisNodeDelegate::eventFilter(QObject *object, QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress: {
if (d->edit) {
QMouseEvent *me = static_cast<QMouseEvent*>(event);
if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos())) {
emit commitData(d->edit);
emit closeEditor(d->edit);
}
}
} break;
case QEvent::KeyPress: {
QLineEdit *edit = qobject_cast<QLineEdit*>(object);
if (edit && edit == d->edit) {
QKeyEvent *ke = static_cast<QKeyEvent*>(event);
switch (ke->key()) {
case Qt::Key_Escape:
emit closeEditor(edit);
return true;
case Qt::Key_Tab:
emit commitData(edit);
emit closeEditor(edit,EditNextItem);
return true;
case Qt::Key_Backtab:
emit commitData(edit);
emit closeEditor(edit, EditPreviousItem);
return true;
case Qt::Key_Return:
case Qt::Key_Enter:
emit commitData(edit);
emit closeEditor(edit);
return true;
default: break;
}
}
} break;
case QEvent::FocusOut : {
QLineEdit *edit = qobject_cast<QLineEdit*>(object);
if (edit && edit == d->edit) {
emit commitData(edit);
emit closeEditor(edit);
}
}
default: break;
}
return QAbstractItemDelegate::eventFilter(object, event);
}
// PRIVATE
QStyleOptionViewItem KisNodeDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index)
{
QStyleOptionViewItem option = o;
QVariant v = index.data(Qt::FontRole);
if (v.isValid()) {
option.font = v.value<QFont>();
option.fontMetrics = QFontMetrics(option.font);
}
v = index.data(Qt::TextAlignmentRole);
if (v.isValid())
option.displayAlignment = QFlag(v.toInt());
v = index.data(Qt::TextColorRole);
if (v.isValid())
option.palette.setColor(QPalette::Text, v.value<QColor>());
v = index.data(Qt::BackgroundColorRole);
if (v.isValid())
option.palette.setColor(QPalette::Window, v.value<QColor>());
return option;
}
QRect KisNodeDelegate::progressBarRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return iconsRect(option, index);
}
void KisNodeDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QVariant value = index.data(KisNodeModel::ProgressRole);
if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) {
const QRect r = progressBarRect(option, index);
p->save();
{
p->setClipRect(r);
QStyle* style = QApplication::style();
QStyleOptionProgressBar opt;
opt.minimum = 0;
opt.maximum = 100;
opt.progress = value.toInt();
opt.textVisible = true;
opt.textAlignment = Qt::AlignHCenter;
opt.text = i18n("%1 %", opt.progress);
opt.rect = r;
opt.orientation = Qt::Horizontal;
opt.state = option.state;
style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0);
}
p->restore();
}
}
diff --git a/libs/ui/KisPart.cpp b/libs/ui/KisPart.cpp
index 7e5b904a0f..ab28ebc413 100644
--- a/libs/ui/KisPart.cpp
+++ b/libs/ui/KisPart.cpp
@@ -1,481 +1,503 @@
/* This file is part of the KDE project
* Copyright (C) 1998-1999 Torben Weis <weis@kde.org>
* Copyright (C) 2000-2005 David Faure <faure@kde.org>
* Copyright (C) 2007-2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2010-2012 Boudewijn Rempt <boud@kogmbh.com>
* Copyright (C) 2011 Inge Wallin <ingwa@kogmbh.com>
* Copyright (C) 2015 Michael Abrahams <miabraha@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 "KisPart.h"
#include "KoProgressProxy.h"
#include <KoCanvasController.h>
#include <KoCanvasControllerWidget.h>
#include <KoColorSpaceEngine.h>
#include <KoCanvasBase.h>
#include <KoToolManager.h>
#include <KoShapeBasedDocumentBase.h>
#include <KoResourceServerProvider.h>
#include <kis_icon.h>
#include "KisApplication.h"
#include "KisDocument.h"
#include "KisView.h"
#include "KisViewManager.h"
#include "KisImportExportManager.h"
#include <kis_debug.h>
#include <KoResourcePaths.h>
#include <KoDialog.h>
#include <kdesktopfile.h>
#include <QMessageBox>
#include <klocalizedstring.h>
#include <kactioncollection.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <QKeySequence>
#include <QDialog>
#include <QApplication>
#include <QDomDocument>
#include <QDomElement>
#include <QGlobalStatic>
#include <KisMimeDatabase.h>
#include "KisView.h"
#include "KisDocument.h"
#include "kis_config.h"
#include "kis_shape_controller.h"
#include "kis_resource_server_provider.h"
#include "kis_animation_cache_populator.h"
#include "kis_idle_watcher.h"
#include "kis_image.h"
#include "KisImportExportManager.h"
#include "KisDocument.h"
#include "KoToolManager.h"
+#include "KisViewManager.h"
+#include "kis_script_manager.h"
#include "KisOpenPane.h"
#include "kis_color_manager.h"
#include "kis_debug.h"
#include "kis_action.h"
#include "kis_action_registry.h"
Q_GLOBAL_STATIC(KisPart, s_instance)
class Q_DECL_HIDDEN KisPart::Private
{
public:
Private(KisPart *_part)
: part(_part)
, idleWatcher(2500)
, animationCachePopulator(_part)
{
}
~Private()
{
}
KisPart *part;
QList<QPointer<KisView> > views;
QList<QPointer<KisMainWindow> > mainWindows;
QList<QPointer<KisDocument> > documents;
+ QList<KisAction*> scriptActions;
KActionCollection *actionCollection{0};
KisIdleWatcher idleWatcher;
KisAnimationCachePopulator animationCachePopulator;
- void loadActions();
};
+
KisPart* KisPart::instance()
{
return s_instance;
}
KisPart::KisPart()
: d(new Private(this))
{
// Preload all the resources in the background
Q_UNUSED(KoResourceServerProvider::instance());
Q_UNUSED(KisResourceServerProvider::instance());
Q_UNUSED(KisColorManager::instance());
connect(this, SIGNAL(documentOpened(QString)),
this, SLOT(updateIdleWatcherConnections()));
connect(this, SIGNAL(documentClosed(QString)),
this, SLOT(updateIdleWatcherConnections()));
connect(KisActionRegistry::instance(), SIGNAL(shortcutsUpdated()),
this, SLOT(updateShortcuts()));
connect(&d->idleWatcher, SIGNAL(startedIdleMode()),
&d->animationCachePopulator, SLOT(slotRequestRegeneration()));
d->animationCachePopulator.slotRequestRegeneration();
}
KisPart::~KisPart()
{
while (!d->documents.isEmpty()) {
delete d->documents.takeFirst();
}
while (!d->views.isEmpty()) {
delete d->views.takeFirst();
}
while (!d->mainWindows.isEmpty()) {
delete d->mainWindows.takeFirst();
}
delete d;
}
void KisPart::updateIdleWatcherConnections()
{
QVector<KisImageSP> images;
Q_FOREACH (QPointer<KisDocument> document, documents()) {
- images << document->image();
+ if (document->image()) {
+ images << document->image();
+ }
}
d->idleWatcher.setTrackedImages(images);
}
void KisPart::addDocument(KisDocument *document)
{
//dbgUI << "Adding document to part list" << document;
Q_ASSERT(document);
if (!d->documents.contains(document)) {
d->documents.append(document);
emit documentOpened('/'+objectName());
+ emit sigDocumentAdded(document);
+ connect(document, SIGNAL(sigSavingFinished()), SLOT(slotDocumentSaved()));
}
}
QList<QPointer<KisDocument> > KisPart::documents() const
{
return d->documents;
}
KisDocument *KisPart::createDocument() const
{
KisDocument *doc = new KisDocument();
return doc;
}
int KisPart::documentCount() const
{
return d->documents.size();
}
void KisPart::removeDocument(KisDocument *document)
{
d->documents.removeAll(document);
emit documentClosed('/'+objectName());
+ emit sigDocumentRemoved(document->url().toLocalFile());
document->deleteLater();
}
KisMainWindow *KisPart::createMainWindow()
{
KisMainWindow *mw = new KisMainWindow();
-
+ Q_FOREACH(QAction *action, d->scriptActions) {
+ mw->viewManager()->scriptManager()->addAction(action);
+ }
dbgUI <<"mainWindow" << (void*)mw << "added to view" << this;
d->mainWindows.append(mw);
-
+ emit sigWindowAdded(mw);
return mw;
}
KisView *KisPart::createView(KisDocument *document,
KoCanvasResourceManager *resourceManager,
KActionCollection *actionCollection,
QWidget *parent)
{
// If creating the canvas fails, record this and disable OpenGL next time
KisConfig cfg;
KConfigGroup grp( KSharedConfig::openConfig(), "crashprevention");
if (grp.readEntry("CreatingCanvas", false)) {
cfg.setUseOpenGL(false);
}
if (cfg.canvasState() == "OPENGL_FAILED") {
cfg.setUseOpenGL(false);
}
grp.writeEntry("CreatingCanvas", true);
grp.sync();
QApplication::setOverrideCursor(Qt::WaitCursor);
KisView *view = new KisView(document, resourceManager, actionCollection, parent);
QApplication::restoreOverrideCursor();
// Record successful canvas creation
grp.writeEntry("CreatingCanvas", false);
grp.sync();
addView(view);
return view;
}
void KisPart::addView(KisView *view)
{
if (!view)
return;
if (!d->views.contains(view)) {
d->views.append(view);
}
connect(view, SIGNAL(destroyed()), this, SLOT(viewDestroyed()));
emit sigViewAdded(view);
}
void KisPart::removeView(KisView *view)
{
if (!view) return;
/**
* HACK ALERT: we check here explicitly if the document (or main
* window), is saving the stuff. If we close the
* document *before* the saving is completed, a crash
* will happen.
*/
if (view->mainWindow()->hackIsSaving()) {
return;
}
emit sigViewRemoved(view);
QPointer<KisDocument> doc = view->document();
d->views.removeAll(view);
if (doc) {
bool found = false;
Q_FOREACH (QPointer<KisView> view, d->views) {
if (view && view->document() == doc) {
found = true;
break;
}
}
if (!found) {
removeDocument(doc);
}
}
}
QList<QPointer<KisView> > KisPart::views() const
{
return d->views;
}
int KisPart::viewCount(KisDocument *doc) const
{
if (!doc) {
return d->views.count();
}
else {
int count = 0;
Q_FOREACH (QPointer<KisView> view, d->views) {
if (view && view->isVisible() && view->document() == doc) {
count++;
}
}
return count;
}
}
+void KisPart::slotDocumentSaved()
+{
+ KisDocument *doc = qobject_cast<KisDocument*>(sender());
+ emit sigDocumentSaved(doc->url().toLocalFile());
+}
void KisPart::removeMainWindow(KisMainWindow *mainWindow)
{
dbgUI <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this;
if (mainWindow) {
d->mainWindows.removeAll(mainWindow);
}
}
const QList<QPointer<KisMainWindow> > &KisPart::mainWindows() const
{
return d->mainWindows;
}
int KisPart::mainwindowCount() const
{
return d->mainWindows.count();
}
KisMainWindow *KisPart::currentMainwindow() const
{
QWidget *widget = qApp->activeWindow();
KisMainWindow *mainWindow = qobject_cast<KisMainWindow*>(widget);
while (!mainWindow && widget) {
widget = widget->parentWidget();
mainWindow = qobject_cast<KisMainWindow*>(widget);
}
if (!mainWindow && mainWindows().size() > 0) {
mainWindow = mainWindows().first();
}
return mainWindow;
}
+void KisPart::addScriptAction(KisAction *action)
+{
+ d->scriptActions << action;
+}
+
KisIdleWatcher* KisPart::idleWatcher() const
{
return &d->idleWatcher;
}
KisAnimationCachePopulator* KisPart::cachePopulator() const
{
return &d->animationCachePopulator;
}
void KisPart::openExistingFile(const QUrl &url)
{
Q_ASSERT(url.isLocalFile());
qApp->setOverrideCursor(Qt::BusyCursor);
KisDocument *document = createDocument();
if (!document->openUrl(url)) {
delete document;
return;
}
if (!document->image()) {
delete document;
return;
}
document->setModified(false);
addDocument(document);
KisMainWindow *mw = currentMainwindow();
mw->addViewAndNotifyLoadingCompleted(document);
qApp->restoreOverrideCursor();
}
void KisPart::updateShortcuts()
{
// Update any non-UI actionCollections. That includes:
// - Shortcuts called inside of tools
// - Perhaps other things?
KoToolManager::instance()->updateToolShortcuts();
// Now update the UI actions.
Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) {
KActionCollection *ac = mainWindow->actionCollection();
ac->updateShortcuts();
// Loop through mainWindow->actionCollections() to modify tooltips
// so that they list shortcuts at the end in parentheses
Q_FOREACH ( QAction* action, ac->actions())
{
// Remove any existing suffixes from the tooltips.
// Note this regexp starts with a space, e.g. " (Ctrl-a)"
QString strippedTooltip = action->toolTip().remove(QRegExp("\\s\\(.*\\)"));
// Now update the tooltips with the new shortcut info.
if(action->shortcut() == QKeySequence(0))
action->setToolTip(strippedTooltip);
else
action->setToolTip( strippedTooltip + " (" + action->shortcut().toString() + ")");
}
}
}
void KisPart::openTemplate(const QUrl &url)
{
qApp->setOverrideCursor(Qt::BusyCursor);
KisDocument *document = createDocument();
bool ok = document->loadNativeFormat(url.toLocalFile());
document->setModified(false);
document->undoStack()->clear();
if (ok) {
QString mimeType = KisMimeDatabase::mimeTypeForFile(url.toLocalFile());
// in case this is a open document template remove the -template from the end
mimeType.remove( QRegExp( "-template$" ) );
document->setMimeTypeAfterLoading(mimeType);
document->resetURL();
}
else {
if (document->errorMessage().isEmpty()) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1", document->localFilePath()));
}
else {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not create document from template\n%1\nReason: %2", document->localFilePath(), document->errorMessage()));
}
delete document;
return;
}
addDocument(document);
KisMainWindow *mw = currentMainwindow();
mw->addViewAndNotifyLoadingCompleted(document);
KisOpenPane *pane = qobject_cast<KisOpenPane*>(sender());
if (pane) {
pane->hide();
pane->deleteLater();
}
qApp->restoreOverrideCursor();
}
void KisPart::viewDestroyed()
{
KisView *view = qobject_cast<KisView*>(sender());
if (view) {
removeView(view);
}
}
void KisPart::addRecentURLToAllMainWindows(QUrl url)
{
// Add to recent actions list in our mainWindows
Q_FOREACH (KisMainWindow *mainWindow, d->mainWindows) {
mainWindow->addRecentURL(url);
}
}
void KisPart::startCustomDocument(KisDocument* doc)
{
addDocument(doc);
KisMainWindow *mw = currentMainwindow();
KisOpenPane *pane = qobject_cast<KisOpenPane*>(sender());
if (pane) {
pane->hide();
pane->deleteLater();
}
mw->addViewAndNotifyLoadingCompleted(doc);
}
KisInputManager* KisPart::currentInputManager()
{
- return instance()->currentMainwindow()->viewManager()->inputManager();
+ KisMainWindow *mw = currentMainwindow();
+ KisViewManager *manager = mw ? mw->viewManager() : 0;
+ return manager ? manager->inputManager() : 0;
}
diff --git a/libs/ui/KisPart.h b/libs/ui/KisPart.h
index b0891688c7..2f40c5bc1d 100644
--- a/libs/ui/KisPart.h
+++ b/libs/ui/KisPart.h
@@ -1,239 +1,261 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2000-2005 David Faure <faure@kde.org>
Copyright (C) 2007 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2010 Boudewijn Rempt <boud@kogmbh.com>
Copyright (C) 2015 Michael Abrahams <miabraha@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_PART_H
#define KIS_PART_H
#include <QList>
#include <QPointer>
#include <QUrl>
#include "kritaui_export.h"
#include <KoConfig.h>
#include <KisMainWindow.h>
namespace KIO {
}
+class KisAction;
class KisDocument;
class KisView;
class KisDocument;
class KisIdleWatcher;
class KisAnimationCachePopulator;
/**
* KisPart is the Great Deku Tree of Krita.
*
* It is a singleton class which provides the main entry point to the application.
* Krita supports multiple documents, multiple main windows, and multiple
* components. KisPart manages these resources and provides them to the rest of
* Krita. It manages lists of Actions and shortcuts as well.
*
* The terminology comes from KParts, which is a system allowing one KDE app
* to be run from inside another, like pressing F4 inside dophin to run konsole.
*
* Needless to say, KisPart hasn't got much to do with KParts anymore.
*/
class KRITAUI_EXPORT KisPart : public QObject
{
Q_OBJECT
public:
static KisPart *instance();
/**
* Constructor.
*
* @param parent may be another KisDocument, or anything else.
* Usually passed by KPluginFactory::create.
*/
explicit KisPart();
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
~KisPart();
// ----------------- Document management -----------------
/**
* create an empty document. The document is not automatically registered with the part.
*/
KisDocument *createDocument() const;
/**
* Add the specified document to the list of documents this KisPart manages.
*/
void addDocument(KisDocument *document);
/**
* @return a list of all documents this part manages
*/
QList<QPointer<KisDocument> > documents() const;
/**
* @return number of documents this part manages.
*/
int documentCount() const;
void removeDocument(KisDocument *document);
// ----------------- MainWindow management -----------------
/**
* Create a new main window.
*/
KisMainWindow *createMainWindow();
/**
* Removes a main window from the list of managed windows.
*
* This is called by the MainWindow after it finishes its shutdown routine.
*/
void removeMainWindow(KisMainWindow *mainWindow);
/**
* @return the list of main windows.
*/
const QList<QPointer<KisMainWindow> >& mainWindows() const;
/**
* @return the number of shells for the main window
*/
int mainwindowCount() const;
void addRecentURLToAllMainWindows(QUrl url);
/**
* @return the currently active main window.
*/
KisMainWindow *currentMainwindow() const;
+ /**
+ * Add a given action to the list of dynamically defined actions. On creating
+ * a mainwindow, all these actions will be added to the script manager.
+ */
+ void addScriptAction(KisAction *);
+
+ /**
+ * Load actions for currently active main window into KisActionRegistry.
+ */
+ void loadActions();
+
/**
* @return the application-wide KisIdleWatcher.
*/
KisIdleWatcher *idleWatcher() const;
/**
* @return the application-wide AnimationCachePopulator.
*/
KisAnimationCachePopulator *cachePopulator() const;
public Q_SLOTS:
/**
* This slot loads an existing file.
* @param url the file to load
*/
void openExistingFile(const QUrl &url);
/**
* This slot loads a template and deletes the sender.
* @param url the template to load
*/
void openTemplate(const QUrl &url);
/**
* @brief startCustomDocument adds the given document to the document list and deletes the sender()
* @param doc
*/
void startCustomDocument(KisDocument *doc);
private Q_SLOTS:
void viewDestroyed();
void updateIdleWatcherConnections();
void updateShortcuts();
Q_SIGNALS:
/**
- * emitted when a new document is opened.
+ * emitted when a new document is opened. (for the idle watcher)
*/
void documentOpened(const QString &ref);
/**
- * emitted when an old document is closed.
+ * emitted when an old document is closed. (for the idle watcher)
*/
void documentClosed(const QString &ref);
+ // These signals are for libkis or sketch
void sigViewAdded(KisView *view);
void sigViewRemoved(KisView *view);
+ void sigDocumentAdded(KisDocument *document);
+ void sigDocumentSaved(const QString &url);
+ void sigDocumentRemoved(const QString &filename);
+ void sigWindowAdded(KisMainWindow *window);
public:
- static KisInputManager *currentInputManager();
+ KisInputManager *currentInputManager();
//------------------ View management ------------------
/**
* Create a new view for the document. The view is added to the list of
* views, and if the document wasn't known yet, it's registered as well.
*/
KisView *createView(KisDocument *document,
KoCanvasResourceManager *resourceManager,
KActionCollection *actionCollection,
QWidget *parent);
/**
* Adds a view to the document. If the part doesn't know yet about
* the document, it is registered.
*
* This calls KisView::updateReadWrite to tell the new view
* whether the document is readonly or not.
*/
void addView(KisView *view);
/**
* Removes a view of the document.
*/
void removeView(KisView *view);
/**
* @return a list of views this document is displayed in
*/
QList<QPointer<KisView> > views() const;
/**
* @return number of views this document is displayed in
*/
int viewCount(KisDocument *doc) const;
+
+private Q_SLOTS:
+
+ void slotDocumentSaved();
+
private:
Q_DISABLE_COPY(KisPart)
class Private;
Private *const d;
};
#endif
diff --git a/libs/ui/KisSelectedShapesProxy.cpp b/libs/ui/KisSelectedShapesProxy.cpp
new file mode 100644
index 0000000000..32bee89341
--- /dev/null
+++ b/libs/ui/KisSelectedShapesProxy.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "KisSelectedShapesProxy.h"
+
+#include "kis_signal_auto_connection.h"
+#include <KoShapeManager.h>
+#include <KoSelection.h>
+
+
+struct KisSelectedShapesProxy::Private
+{
+ KoShapeManager *globalShapeManager;
+ QPointer<KoShapeManager> shapeManager;
+ KisSignalAutoConnectionsStore shapeManagerConnections;
+};
+
+KisSelectedShapesProxy::KisSelectedShapesProxy(KoShapeManager *globalShapeManager)
+ : m_d(new Private())
+{
+ m_d->globalShapeManager = globalShapeManager;
+
+ connect(m_d->globalShapeManager->selection(),
+ SIGNAL(currentLayerChanged(const KoShapeLayer*)),
+ SIGNAL(currentLayerChanged(const KoShapeLayer*)));
+}
+
+KisSelectedShapesProxy::~KisSelectedShapesProxy()
+{
+
+}
+
+void KisSelectedShapesProxy::setShapeManager(KoShapeManager *shapeManager)
+{
+ if (shapeManager != m_d->shapeManager) {
+ m_d->shapeManager = shapeManager;
+
+ m_d->shapeManagerConnections.clear();
+
+ if (m_d->shapeManager) {
+ m_d->shapeManagerConnections.addConnection(
+ m_d->shapeManager, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
+ m_d->shapeManagerConnections.addConnection(
+ m_d->shapeManager, SIGNAL(selectionContentChanged()), this, SIGNAL(selectionContentChanged()));
+ }
+
+ emit selectionChanged();
+ }
+}
+
+KoSelection *KisSelectedShapesProxy::selection()
+{
+ return m_d->shapeManager ?
+ m_d->shapeManager->selection() :
+ m_d->globalShapeManager->selection();
+}
+
diff --git a/libs/ui/KisSelectedShapesProxy.h b/libs/ui/KisSelectedShapesProxy.h
new file mode 100644
index 0000000000..79b957073b
--- /dev/null
+++ b/libs/ui/KisSelectedShapesProxy.h
@@ -0,0 +1,51 @@
+/*
+ * 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 KISSELECTEDSHAPESPROXY_H
+#define KISSELECTEDSHAPESPROXY_H
+
+#include <QObject>
+#include <QScopedPointer>
+#include <KoSelectedShapesProxy.h>
+
+class KoShapeManager;
+
+class KisSelectedShapesProxy : public KoSelectedShapesProxy
+{
+ Q_OBJECT
+public:
+ KisSelectedShapesProxy(KoShapeManager *globalShapeManager);
+ ~KisSelectedShapesProxy();
+
+ void setShapeManager(KoShapeManager *manager);
+
+ KoSelection *selection();
+
+
+Q_SIGNALS:
+ void selectionChanged();
+ void selectionContentChanged();
+ void currentLayerChanged(const KoShapeLayer *layer);
+
+
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
+};
+
+#endif // KISSELECTEDSHAPESPROXY_H
diff --git a/libs/ui/KisView.cpp b/libs/ui/KisView.cpp
index e58d0fc408..5cd1220519 100644
--- a/libs/ui/KisView.cpp
+++ b/libs/ui/KisView.cpp
@@ -1,954 +1,960 @@
/*
* Copyright (C) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisView.h"
#include "KisView_p.h"
#include <KoDockFactoryBase.h>
#include <KoDockRegistry.h>
#include <KoDocumentInfo.h>
#include "KoDocumentInfo.h"
#include "KoPageLayout.h"
#include <KoToolManager.h>
#include <kis_icon.h>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kselectaction.h>
#include <kconfiggroup.h>
#include <kactioncollection.h>
#include <QMenu>
#include <QMessageBox>
#include <QUrl>
#include <QTemporaryFile>
#include <QApplication>
#include <QDesktopWidget>
#include <QDockWidget>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QImage>
#include <QList>
#include <QPrintDialog>
#include <QToolBar>
#include <QUrl>
#include <QStatusBar>
#include <QMoveEvent>
#include <QTemporaryFile>
#include <kis_image.h>
#include <kis_node.h>
#include <kis_group_layer.h>
#include <kis_layer.h>
#include <kis_mask.h>
#include <kis_selection.h>
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_config.h"
#include "KisDocument.h"
#include "kis_image_manager.h"
#include "KisMainWindow.h"
#include "kis_mimedata.h"
#include "kis_mirror_axis.h"
#include "kis_node_commands_adapter.h"
#include "kis_node_manager.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_shape_controller.h"
#include "kis_tool_freehand.h"
#include "KisUndoStackAction.h"
#include "KisViewManager.h"
#include "kis_zoom_manager.h"
#include "kis_composite_progress_proxy.h"
#include "kis_statusbar.h"
#include "kis_painting_assistants_decoration.h"
#include "kis_progress_widget.h"
#include "kis_signal_compressor.h"
#include "kis_filter_manager.h"
#include "kis_file_layer.h"
#include "krita_utils.h"
#include "input/kis_input_manager.h"
#include "KisRemoteFileFetcher.h"
//static
QString KisView::newObjectName()
{
static int s_viewIFNumber = 0;
QString name; name.setNum(s_viewIFNumber++); name.prepend("view_");
return name;
}
bool KisView::s_firstView = true;
class Q_DECL_HIDDEN KisView::Private
{
public:
Private(KisView *_q,
KisDocument *document,
KoCanvasResourceManager *resourceManager,
KActionCollection *actionCollection)
: actionCollection(actionCollection)
, viewConverter()
, canvasController(_q, actionCollection)
, canvas(&viewConverter, resourceManager, _q, document->shapeController())
, zoomManager(_q, &this->viewConverter, &this->canvasController)
, paintingAssistantsDecoration(new KisPaintingAssistantsDecoration(_q))
, floatingMessageCompressor(100, KisSignalCompressor::POSTPONE)
{
}
KisUndoStackAction *undo = 0;
KisUndoStackAction *redo = 0;
bool inOperation; //in the middle of an operation (no screen refreshing)?
QPointer<KisDocument> document; // our KisDocument
QWidget *tempActiveWidget = 0;
/**
* Signals the document has been deleted. Can't use document==0 since this
* only happens in ~QObject, and views get deleted by ~KisDocument.
* XXX: either provide a better justification to do things this way, or
* rework the mechanism.
*/
bool documentDeleted = false;
KActionCollection* actionCollection;
KisCoordinatesConverter viewConverter;
KisCanvasController canvasController;
KisCanvas2 canvas;
KisZoomManager zoomManager;
KisViewManager *viewManager = 0;
KisNodeSP currentNode;
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration;
bool isCurrent = false;
bool showFloatingMessage = false;
QPointer<KisFloatingMessage> savedFloatingMessage;
KisSignalCompressor floatingMessageCompressor;
bool softProofing = false;
bool gamutCheck = false;
// Hmm sorry for polluting the private class with such a big inner class.
// At the beginning it was a little struct :)
class StatusBarItem
{
public:
StatusBarItem(QWidget * widget, int stretch, bool permanent)
: m_widget(widget),
m_stretch(stretch),
m_permanent(permanent),
m_connected(false),
m_hidden(false) {}
bool operator==(const StatusBarItem& rhs) {
return m_widget == rhs.m_widget;
}
bool operator!=(const StatusBarItem& rhs) {
return m_widget != rhs.m_widget;
}
QWidget * widget() const {
return m_widget;
}
void ensureItemShown(QStatusBar * sb) {
Q_ASSERT(m_widget);
if (!m_connected) {
if (m_permanent)
sb->addPermanentWidget(m_widget, m_stretch);
else
sb->addWidget(m_widget, m_stretch);
if(!m_hidden)
m_widget->show();
m_connected = true;
}
}
void ensureItemHidden(QStatusBar * sb) {
if (m_connected) {
m_hidden = m_widget->isHidden();
sb->removeWidget(m_widget);
m_widget->hide();
m_connected = false;
}
}
private:
QWidget * m_widget = 0;
int m_stretch;
bool m_permanent;
bool m_connected = false;
bool m_hidden = false;
};
};
KisView::KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent)
: QWidget(parent)
, d(new Private(this, document, resourceManager, actionCollection))
{
Q_ASSERT(document);
connect(document, SIGNAL(titleModified(QString,bool)), this, SIGNAL(titleModified(QString,bool)));
setObjectName(newObjectName());
d->document = document;
setFocusPolicy(Qt::StrongFocus);
d->undo = new KisUndoStackAction(d->document->undoStack(), KisUndoStackAction::UNDO);
d->redo = new KisUndoStackAction(d->document->undoStack(), KisUndoStackAction::RED0);
QStatusBar * sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document, SIGNAL(statusBarMessage(const QString&)),
this, SLOT(slotActionStatusText(const QString&)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
d->canvas.setup();
KisConfig cfg;
d->canvasController.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->canvasController.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->canvasController.setDrawShadow(false);
d->canvasController.setCanvasMode(KoCanvasController::Infinite);
d->canvasController.setVastScrolling(cfg.vastScrolling());
d->canvasController.setCanvas(&d->canvas);
d->zoomManager.setup(d->actionCollection);
connect(&d->canvasController, SIGNAL(documentSizeChanged()), &d->zoomManager, SLOT(slotScrollAreaSizeChanged()));
setAcceptDrops(true);
connect(d->document, SIGNAL(sigLoadingFinished()), this, SLOT(slotLoadingFinished()));
connect(d->document, SIGNAL(sigSavingFinished()), this, SLOT(slotSavingFinished()));
d->canvas.addDecoration(d->paintingAssistantsDecoration);
d->paintingAssistantsDecoration->setVisible(true);
d->showFloatingMessage = cfg.showCanvasMessages();
}
KisView::~KisView()
{
if (d->viewManager) {
KoProgressProxy *proxy = d->viewManager->statusBar()->progress()->progressProxy();
KIS_ASSERT_RECOVER_NOOP(proxy);
image()->compositeProgressProxy()->removeProxy(proxy);
if (d->viewManager->filterManager()->isStrokeRunning()) {
d->viewManager->filterManager()->cancel();
}
}
KoToolManager::instance()->removeCanvasController(&d->canvasController);
KisPart::instance()->removeView(this);
delete d;
}
void KisView::notifyCurrentStateChanged(bool isCurrent)
{
d->isCurrent = isCurrent;
if (!d->isCurrent && d->savedFloatingMessage) {
d->savedFloatingMessage->removeMessage();
}
KisInputManager *inputManager = globalInputManager();
if (d->isCurrent) {
inputManager->attachPriorityEventFilter(&d->canvasController);
} else {
inputManager->detachPriorityEventFilter(&d->canvasController);
}
}
void KisView::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisView::showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
{
if (!d->viewManager) return;
if(d->isCurrent && d->showFloatingMessage && d->viewManager->qtMainWindow()) {
if (d->savedFloatingMessage) {
d->savedFloatingMessage->tryOverrideMessage(message, icon, timeout, priority, alignment);
} else {
d->savedFloatingMessage = new KisFloatingMessage(message, this->canvasBase()->canvasWidget(), false, timeout, priority, alignment);
d->savedFloatingMessage->setShowOverParent(true);
d->savedFloatingMessage->setIcon(icon);
connect(&d->floatingMessageCompressor, SIGNAL(timeout()), d->savedFloatingMessage, SLOT(showMessage()));
d->floatingMessageCompressor.start();
}
}
}
bool KisView::canvasIsMirrored() const
{
return d->canvas.xAxisMirrored() || d->canvas.yAxisMirrored();
}
void KisView::setViewManager(KisViewManager *view)
{
d->viewManager = view;
KoToolManager::instance()->addController(&d->canvasController);
KoToolManager::instance()->registerToolActions(d->actionCollection, &d->canvasController);
dynamic_cast<KisShapeController*>(d->document->shapeController())->setInitialShapeForCanvas(&d->canvas);
if (resourceProvider()) {
resourceProvider()->slotImageSizeChanged();
}
if (d->viewManager && d->viewManager->nodeManager()) {
d->viewManager->nodeManager()->nodesUpdated();
}
connect(image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), this, SLOT(slotImageSizeChanged(const QPointF&, const QPointF&)));
connect(image(), SIGNAL(sigResolutionChanged(double,double)), this, SLOT(slotImageResolutionChanged()));
// executed in a context of an image thread
connect(image(), SIGNAL(sigNodeAddedAsync(KisNodeSP)),
SLOT(slotImageNodeAdded(KisNodeSP)),
Qt::DirectConnection);
// executed in a context of the gui thread
connect(this, SIGNAL(sigContinueAddNode(KisNodeSP)),
SLOT(slotContinueAddNode(KisNodeSP)),
Qt::AutoConnection);
// executed in a context of an image thread
connect(image(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)),
SLOT(slotImageNodeRemoved(KisNodeSP)),
Qt::DirectConnection);
// executed in a context of the gui thread
connect(this, SIGNAL(sigContinueRemoveNode(KisNodeSP)),
SLOT(slotContinueRemoveNode(KisNodeSP)),
Qt::AutoConnection);
/*
* WARNING: Currently we access the global progress bar in two ways:
* connecting to composite progress proxy (strokes) and creating
* progress updaters. The latter way should be deprecated in favour
* of displaying the status of the global strokes queue
*/
image()->compositeProgressProxy()->addProxy(d->viewManager->statusBar()->progress()->progressProxy());
connect(d->viewManager->statusBar()->progress(), SIGNAL(sigCancellationRequested()), image(), SLOT(requestStrokeCancellation()));
d->viewManager->updateGUI();
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
KisViewManager* KisView::viewManager() const
{
return d->viewManager;
}
void KisView::slotImageNodeAdded(KisNodeSP node)
{
emit sigContinueAddNode(node);
}
void KisView::slotContinueAddNode(KisNodeSP newActiveNode)
{
/**
* When deleting the last layer, root node got selected. We should
* fix it when the first layer is added back.
*
* Here we basically reimplement what Qt's view/model do. But
* since they are not connected, we should do it manually.
*/
if (!d->isCurrent &&
(!d->currentNode || !d->currentNode->parent())) {
d->currentNode = newActiveNode;
}
}
void KisView::slotImageNodeRemoved(KisNodeSP node)
{
emit sigContinueRemoveNode(KritaUtils::nearestNodeAfterRemoval(node));
}
void KisView::slotContinueRemoveNode(KisNodeSP newActiveNode)
{
if (!d->isCurrent) {
d->currentNode = newActiveNode;
}
}
QAction *KisView::undoAction() const
{
return d->undo;
}
QAction *KisView::redoAction() const
{
return d->redo;
}
KoZoomController *KisView::zoomController() const
{
return d->zoomManager.zoomController();
}
KisZoomManager *KisView::zoomManager() const
{
return &d->zoomManager;
}
KisCanvasController *KisView::canvasController() const
{
return &d->canvasController;
}
KisCanvasResourceProvider *KisView::resourceProvider() const
{
if (d->viewManager) {
return d->viewManager->resourceProvider();
}
return 0;
}
KisInputManager* KisView::globalInputManager() const
{
return d->viewManager ? d->viewManager->inputManager() : 0;
}
KisCanvas2 *KisView::canvasBase() const
{
return &d->canvas;
}
KisImageWSP KisView::image() const
{
if (d->document) {
return d->document->image();
}
return 0;
}
KisCoordinatesConverter *KisView::viewConverter() const
{
return &d->viewConverter;
}
void KisView::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasImage()
|| event->mimeData()->hasUrls()
|| event->mimeData()->hasFormat("application/x-krita-node")) {
event->accept();
// activate view if it should accept the drop
this->setFocus();
} else {
event->ignore();
}
}
void KisView::dropEvent(QDropEvent *event)
{
KisImageWSP kisimage = image();
Q_ASSERT(kisimage);
QPoint cursorPos = canvasBase()->coordinatesConverter()->widgetToImage(event->pos()).toPoint();
QRect imageBounds = kisimage->bounds();
QPoint pasteCenter;
bool forceRecenter;
if (event->keyboardModifiers() & Qt::ShiftModifier &&
imageBounds.contains(cursorPos)) {
pasteCenter = cursorPos;
forceRecenter = true;
} else {
pasteCenter = imageBounds.center();
forceRecenter = false;
}
if (event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasImage())
{
KisShapeController *kritaShapeController =
dynamic_cast<KisShapeController*>(d->document->shapeController());
QList<KisNodeSP> nodes =
KisMimeData::loadNodes(event->mimeData(), imageBounds,
pasteCenter, forceRecenter,
kisimage, kritaShapeController);
Q_FOREACH (KisNodeSP node, nodes) {
if (node) {
KisNodeCommandsAdapter adapter(viewManager());
if (!viewManager()->nodeManager()->activeLayer()) {
adapter.addNode(node, kisimage->rootLayer() , 0);
} else {
adapter.addNode(node,
viewManager()->nodeManager()->activeLayer()->parent(),
viewManager()->nodeManager()->activeLayer());
}
}
}
}
else if (event->mimeData()->hasUrls()) {
QList<QUrl> urls = event->mimeData()->urls();
if (urls.length() > 0) {
QMenu popup;
popup.setObjectName("drop_popup");
QAction *insertAsNewLayer = new QAction(i18n("Insert as New Layer"), &popup);
QAction *insertManyLayers = new QAction(i18n("Insert Many Layers"), &popup);
QAction *insertAsNewFileLayer = new QAction(i18n("Insert as New File Layer"), &popup);
QAction *insertManyFileLayers = new QAction(i18n("Insert Many File Layers"), &popup);
QAction *openInNewDocument = new QAction(i18n("Open in New Document"), &popup);
QAction *openManyDocuments = new QAction(i18n("Open Many Documents"), &popup);
QAction *cancel = new QAction(i18n("Cancel"), &popup);
popup.addAction(insertAsNewLayer);
popup.addAction(insertAsNewFileLayer);
popup.addAction(openInNewDocument);
popup.addAction(insertManyLayers);
popup.addAction(insertManyFileLayers);
popup.addAction(openManyDocuments);
insertAsNewLayer->setEnabled(image() && urls.count() == 1);
insertAsNewFileLayer->setEnabled(image() && urls.count() == 1);
openInNewDocument->setEnabled(urls.count() == 1);
insertManyLayers->setEnabled(image() && urls.count() > 1);
insertManyFileLayers->setEnabled(image() && urls.count() > 1);
openManyDocuments->setEnabled(urls.count() > 1);
popup.addSeparator();
popup.addAction(cancel);
QAction *action = popup.exec(QCursor::pos());
if (action != 0 && action != cancel) {
QTemporaryFile *tmp = 0;
for (QUrl url : urls) {
if (!url.isLocalFile()) {
// download the file and substitute the url
KisRemoteFileFetcher fetcher;
tmp = new QTemporaryFile();
tmp->setAutoRemove(true);
if (!fetcher.fetchFile(url, tmp)) {
qDebug() << "Fetching" << url << "failed";
continue;
}
url = url.fromLocalFile(tmp->fileName());
}
if (url.isLocalFile()) {
if (action == insertAsNewLayer || action == insertManyLayers) {
d->viewManager->imageManager()->importImage(url);
activateWindow();
}
else if (action == insertAsNewFileLayer || action == insertManyFileLayers) {
KisNodeCommandsAdapter adapter(viewManager());
KisFileLayer *fileLayer = new KisFileLayer(image(), "", url.toLocalFile(),
KisFileLayer::None, image()->nextLayerName(), OPACITY_OPAQUE_U8);
adapter.addNode(fileLayer, viewManager()->activeNode()->parent(), viewManager()->activeNode());
}
else {
Q_ASSERT(action == openInNewDocument || action == openManyDocuments);
if (mainWindow()) {
mainWindow()->openDocument(url);
}
}
}
delete tmp;
tmp = 0;
}
}
}
}
}
KisDocument *KisView::document() const
{
return d->document;
}
void KisView::setDocument(KisDocument *document)
{
d->document->disconnect(this);
d->document = document;
QStatusBar *sb = statusBar();
if (sb) { // No statusbar in e.g. konqueror
connect(d->document, SIGNAL(statusBarMessage(const QString&)),
this, SLOT(slotActionStatusText(const QString&)));
connect(d->document, SIGNAL(clearStatusBarMessage()),
this, SLOT(slotClearStatusText()));
}
}
void KisView::setDocumentDeleted()
{
d->documentDeleted = true;
}
QPrintDialog *KisView::createPrintDialog(KisPrintJob *printJob, QWidget *parent)
{
Q_UNUSED(parent);
QPrintDialog *printDialog = new QPrintDialog(&printJob->printer(), this);
printDialog->setMinMax(printJob->printer().fromPage(), printJob->printer().toPage());
printDialog->setEnabledOptions(printJob->printDialogOptions());
return printDialog;
}
KisMainWindow * KisView::mainWindow() const
{
return dynamic_cast<KisMainWindow *>(window());
}
QStatusBar * KisView::statusBar() const
{
KisMainWindow *mw = mainWindow();
return mw ? mw->statusBar() : 0;
}
void KisView::slotActionStatusText(const QString &text)
{
QStatusBar *sb = statusBar();
if (sb)
sb->showMessage(text);
}
void KisView::slotClearStatusText()
{
QStatusBar *sb = statusBar();
if (sb)
sb->clearMessage();
}
QList<QAction*> KisView::createChangeUnitActions(bool addPixelUnit)
{
UnitActionGroup* unitActions = new UnitActionGroup(d->document, addPixelUnit, this);
return unitActions->actions();
}
void KisView::closeEvent(QCloseEvent *event)
{
// Check whether we're the last view
int viewCount = KisPart::instance()->viewCount(document());
if (viewCount > 1) {
// there are others still, so don't bother the user
event->accept();
return;
}
if (queryClose()) {
d->viewManager->removeStatusBarItem(zoomManager()->zoomActionWidget());
event->accept();
return;
}
event->ignore();
}
bool KisView::queryClose()
{
if (!document())
return true;
if (document()->isModified()) {
QString name;
if (document()->documentInfo()) {
name = document()->documentInfo()->aboutInfo("title");
}
if (name.isEmpty())
name = document()->url().fileName();
if (name.isEmpty())
name = i18n("Untitled");
int res = QMessageBox::warning(this,
i18nc("@title:window", "Krita"),
i18n("<p>The document <b>'%1'</b> has been modified.</p><p>Do you want to save it?</p>", name),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
switch (res) {
case QMessageBox::Yes : {
bool isNative = (document()->outputMimeType() == document()->nativeFormatMimeType());
if (!viewManager()->mainWindow()->saveDocument(document(), !isNative))
return false;
break;
}
- case QMessageBox::No :
+ case QMessageBox::No : {
+ KisImageSP image = document()->image();
+ image->requestStrokeCancellation();
+ viewManager()->blockUntillOperationsFinishedForced(image);
+
document()->removeAutoSaveFiles();
document()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything.
break;
+ }
default : // case QMessageBox::Cancel :
return false;
}
}
return true;
}
void KisView::resetImageSizeAndScroll(bool changeCentering,
const QPointF &oldImageStillPoint,
const QPointF &newImageStillPoint)
{
const KisCoordinatesConverter *converter = d->canvas.coordinatesConverter();
QPointF oldPreferredCenter = d->canvasController.preferredCenter();
/**
* Calculating the still point in old coordinates depending on the
* parameters given
*/
QPointF oldStillPoint;
if (changeCentering) {
oldStillPoint =
converter->imageToWidget(oldImageStillPoint) +
converter->documentOffset();
} else {
QSize oldDocumentSize = d->canvasController.documentSize();
oldStillPoint = QPointF(0.5 * oldDocumentSize.width(), 0.5 * oldDocumentSize.height());
}
/**
* Updating the document size
*/
QSizeF size(image()->width() / image()->xRes(), image()->height() / image()->yRes());
KoZoomController *zc = d->zoomManager.zoomController();
zc->setZoom(KoZoomMode::ZOOM_CONSTANT, zc->zoomAction()->effectiveZoom());
zc->setPageSize(size);
zc->setDocumentSize(size, true);
/**
* Calculating the still point in new coordinates depending on the
* parameters given
*/
QPointF newStillPoint;
if (changeCentering) {
newStillPoint =
converter->imageToWidget(newImageStillPoint) +
converter->documentOffset();
} else {
QSize newDocumentSize = d->canvasController.documentSize();
newStillPoint = QPointF(0.5 * newDocumentSize.width(), 0.5 * newDocumentSize.height());
}
d->canvasController.setPreferredCenter(oldPreferredCenter - oldStillPoint + newStillPoint);
}
void KisView::setCurrentNode(KisNodeSP node)
{
d->currentNode = node;
+ d->canvas.slotTrySwitchShapeManager();
}
KisNodeSP KisView::currentNode() const
{
return d->currentNode;
}
KisLayerSP KisView::currentLayer() const
{
KisNodeSP node;
KisMaskSP mask = currentMask();
if (mask) {
node = mask->parent();
}
else {
node = d->currentNode;
}
return qobject_cast<KisLayer*>(node.data());
}
KisMaskSP KisView::currentMask() const
{
return dynamic_cast<KisMask*>(d->currentNode.data());
}
KisSelectionSP KisView::selection()
{
KisLayerSP layer = currentLayer();
if (layer)
return layer->selection(); // falls through to the global
// selection, or 0 in the end
if (image()) {
return image()->globalSelection();
}
return 0;
}
void KisView::slotSoftProofing(bool softProofing)
{
d->softProofing = softProofing;
QString message;
if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
{
message = i18n("Soft Proofing doesn't work in floating point.");
viewManager()->showFloatingMessage(message,QIcon());
return;
}
if (softProofing){
message = i18n("Soft Proofing turned on.");
} else {
message = i18n("Soft Proofing turned off.");
}
viewManager()->showFloatingMessage(message,QIcon());
canvasBase()->slotSoftProofing(softProofing);
}
void KisView::slotGamutCheck(bool gamutCheck)
{
d->gamutCheck = gamutCheck;
QString message;
if (canvasBase()->image()->colorSpace()->colorDepthId().id().contains("F"))
{
message = i18n("Gamut Warnings don't work in floating point.");
viewManager()->showFloatingMessage(message,QIcon());
return;
}
if (gamutCheck){
message = i18n("Gamut Warnings turned on.");
if (!d->softProofing){
message += "\n "+i18n("But Soft Proofing is still off.");
}
} else {
message = i18n("Gamut Warnings turned off.");
}
viewManager()->showFloatingMessage(message,QIcon());
canvasBase()->slotGamutCheck(gamutCheck);
}
bool KisView::softProofing()
{
return d->softProofing;
}
bool KisView::gamutCheck()
{
return d->gamutCheck;
}
void KisView::slotLoadingFinished()
{
if (!document()) return;
/**
* Cold-start of image size/resolution signals
*/
slotImageResolutionChanged();
if (image()->locked()) {
// If this is the first view on the image, the image will have been locked
// so unlock it.
image()->blockSignals(false);
image()->unlock();
}
canvasBase()->initializeImage();
/**
* Dirty hack alert
*/
d->zoomManager.zoomController()->setAspectMode(true);
if (viewConverter()) {
viewConverter()->setZoomMode(KoZoomMode::ZOOM_PAGE);
}
connect(image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), this, SIGNAL(sigColorSpaceChanged(const KoColorSpace*)));
connect(image(), SIGNAL(sigProfileChanged(const KoColorProfile*)), this, SIGNAL(sigProfileChanged(const KoColorProfile*)));
connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SIGNAL(sigSizeChanged(QPointF,QPointF)));
KisNodeSP activeNode = document()->preActivatedNode();
document()->setPreActivatedNode(0); // to make sure that we don't keep a reference to a layer the user can later delete.
if (!activeNode) {
activeNode = image()->rootLayer()->lastChild();
}
while (activeNode && !activeNode->inherits("KisLayer")) {
activeNode = activeNode->prevSibling();
}
setCurrentNode(activeNode);
zoomManager()->updateImageBoundsSnapping();
}
void KisView::slotSavingFinished()
{
if (d->viewManager && d->viewManager->mainWindow()) {
d->viewManager->mainWindow()->updateCaption();
}
}
KisPrintJob * KisView::createPrintJob()
{
return new KisPrintJob(image());
}
void KisView::slotImageResolutionChanged()
{
resetImageSizeAndScroll(false);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGUI();
// update KoUnit value for the document
if (resourceProvider()) {
resourceProvider()->resourceManager()->
setResource(KoCanvasResourceManager::Unit, d->canvas.unit());
}
}
void KisView::slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint)
{
resetImageSizeAndScroll(true, oldStillPoint, newStillPoint);
zoomManager()->updateImageBoundsSnapping();
zoomManager()->updateGUI();
}
diff --git a/libs/ui/KisView.h b/libs/ui/KisView.h
index 7089eeb532..0584cea397 100644
--- a/libs/ui/KisView.h
+++ b/libs/ui/KisView.h
@@ -1,281 +1,281 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
Copyright (C) 2007 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.
*/
#ifndef KIS_VIEW_H
#define KIS_VIEW_H
#include <QWidget>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <kis_types.h>
#include "kritaui_export.h"
#include "widgets/kis_floating_message.h"
class KisDocument;
class KisMainWindow;
class KisPrintJob;
class KisCanvasController;
class KisZoomManager;
class KisCanvas2;
class KisViewManager;
class KisDocument;
class KisCanvasResourceProvider;
class KisCoordinatesConverter;
class KisInputManager;
class KoZoomController;
class KoZoomController;
struct KoPageLayout;
class KoCanvasResourceManager;
// KDE classes
class QAction;
class KActionCollection;
// Qt classes
class QDragEnterEvent;
class QDropEvent;
class QPrintDialog;
class QCloseEvent;
class QStatusBar;
/**
* This class is used to display a @ref KisDocument.
*
* Multiple views can be attached to one document at a time.
*/
class KRITAUI_EXPORT KisView : public QWidget
{
Q_OBJECT
public:
/**
* Creates a new view for the document.
*/
KisView(KisDocument *document, KoCanvasResourceManager *resourceManager, KActionCollection *actionCollection, QWidget *parent = 0);
~KisView();
QAction *undoAction() const;
QAction *redoAction() const;
// Temporary while teasing apart view and mainwindow
void setViewManager(KisViewManager *view);
KisViewManager *viewManager() const;
public:
/**
* Retrieves the document object of this view.
*/
KisDocument *document() const;
/**
* Reset the view to show the given document.
*/
void setDocument(KisDocument *document);
/**
* Tells this view that its document has got deleted (called internally)
*/
void setDocumentDeleted();
/**
* In order to print the document represented by this view a new print job should
* be constructed that is capable of doing the printing.
* The default implementation returns 0, which silently cancels printing.
*/
KisPrintJob * createPrintJob();
/**
* Create a QPrintDialog based on the @p printJob
*/
QPrintDialog *createPrintDialog(KisPrintJob *printJob, QWidget *parent);
/**
* @return the KisMainWindow in which this view is currently.
*/
- KisMainWindow * mainWindow() const;
+ KisMainWindow *mainWindow() const;
/**
* @return the statusbar of the KisMainWindow in which this view is currently.
*/
- QStatusBar * statusBar() const;
+ QStatusBar *statusBar() const;
/**
* This adds a widget to the statusbar for this view.
* If you use this method instead of using statusBar() directly,
* KisView will take care of removing the items when the view GUI is deactivated
* and readding them when it is reactivated.
* The parameters are the same as QStatusBar::addWidget().
*/
void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false);
/**
* Remove a widget from the statusbar for this view.
*/
void removeStatusBarItem(QWidget * widget);
/**
* Return the zoomController for this view.
*/
KoZoomController *zoomController() const;
/// create a list of actions that when activated will change the unit on the document.
QList<QAction*> createChangeUnitActions(bool addPixelUnit = false);
public:
/**
* The zoommanager handles everything action-related to zooming
*/
KisZoomManager *zoomManager() const;
/**
* The CanvasController decorates the canvas with scrollbars
* and knows where to start painting on the canvas widget, i.e.,
* the document offset.
*/
KisCanvasController *canvasController() const;
KisCanvasResourceProvider *resourceProvider() const;
/**
* Filters events and sends them to canvas actions. Shared
* among all the views/canvases
*
* NOTE: May be null while initialization!
*/
KisInputManager* globalInputManager() const;
/**
* @return the canvas object
*/
KisCanvas2 *canvasBase() const;
/// @return the image this view is displaying
KisImageWSP image() const;
KisCoordinatesConverter *viewConverter() const;
void resetImageSizeAndScroll(bool changeCentering,
const QPointF &oldImageStillPoint = QPointF(),
const QPointF &newImageStillPoint = QPointF());
void setCurrentNode(KisNodeSP node);
KisNodeSP currentNode() const;
KisLayerSP currentLayer() const;
KisMaskSP currentMask() const;
/**
* @brief softProofing
* @return whether or not we're softproofing in this view.
*/
bool softProofing();
/**
* @brief gamutCheck
* @return whether or not we're using gamut warnings in this view.
*/
bool gamutCheck();
/// Convenience method to get at the active selection (the
/// selection of the current layer, or, if that does not exist,
/// the global selection.
KisSelectionSP selection();
void notifyCurrentStateChanged(bool isCurrent);
void setShowFloatingMessage(bool show);
void showFloatingMessageImpl(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment);
bool canvasIsMirrored() const;
public Q_SLOTS:
/**
* Display a message in the status bar (calls QStatusBar::message())
* @todo rename to something more generic
*/
void slotActionStatusText(const QString &text);
/**
* End of the message in the status bar (calls QStatusBar::clear())
* @todo rename to something more generic
*/
void slotClearStatusText();
/**
* @brief slotSoftProofing set whether or not we're softproofing in this view.
* Will be setting the same in the canvas belonging to the view.
*/
void slotSoftProofing(bool softProofing);
/**
* @brief slotGamutCheck set whether or not we're gamutchecking in this view.
* Will be setting the same in the vans belonging to the view.
*/
void slotGamutCheck(bool gamutCheck);
bool queryClose();
private Q_SLOTS:
void slotImageNodeAdded(KisNodeSP node);
void slotContinueAddNode(KisNodeSP newActiveNode);
void slotImageNodeRemoved(KisNodeSP node);
void slotContinueRemoveNode(KisNodeSP newActiveNode);
Q_SIGNALS:
// From KisImage
void sigSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
void sigProfileChanged(const KoColorProfile * profile);
void sigColorSpaceChanged(const KoColorSpace* cs);
void titleModified(QString,bool);
void sigContinueAddNode(KisNodeSP newActiveNode);
void sigContinueRemoveNode(KisNodeSP newActiveNode);
protected:
// QWidget overrides
void dragEnterEvent(QDragEnterEvent * event);
void dropEvent(QDropEvent * event);
void closeEvent(QCloseEvent *event);
/**
* Generate a name for this view.
*/
QString newObjectName();
public Q_SLOTS:
void slotLoadingFinished();
void slotSavingFinished();
void slotImageResolutionChanged();
void slotImageSizeChanged(const QPointF &oldStillPoint, const QPointF &newStillPoint);
private:
class Private;
Private * const d;
static bool s_firstView;
};
#endif
diff --git a/libs/ui/KisViewManager.cpp b/libs/ui/KisViewManager.cpp
index 5767e4654d..549d303ca4 100644
--- a/libs/ui/KisViewManager.cpp
+++ b/libs/ui/KisViewManager.cpp
@@ -1,1298 +1,1307 @@
/*
* 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>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include "KisViewManager.h"
#include <QPrinter>
#include <QAction>
#include <QApplication>
#include <QBuffer>
#include <QByteArray>
#include <QDesktopServices>
#include <QDesktopWidget>
#include <QGridLayout>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QObject>
#include <QPoint>
#include <QPrintDialog>
#include <QRect>
#include <QScrollBar>
#include <QStatusBar>
#include <QToolBar>
#include <QUrl>
#include <QWidget>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <KoResourcePaths.h>
#include <kselectaction.h>
#include <KoCanvasController.h>
#include <KoCompositeOp.h>
#include <KoDockRegistry.h>
#include <KoDockWidgetTitleBar.h>
#include <KoProperties.h>
#include <KoResourceItemChooserSync.h>
#include <KoSelection.h>
#include <KoStore.h>
#include <KoToolManager.h>
#include <KoToolRegistry.h>
#include <KoViewConverter.h>
#include <KoZoomHandler.h>
#include <KoPluginLoader.h>
#include <KoDocumentInfo.h>
#include <KoGlobal.h>
#include <KoColorSpaceRegistry.h>
#include "input/kis_input_manager.h"
#include "canvas/kis_canvas2.h"
#include "canvas/kis_canvas_controller.h"
#include "canvas/kis_grid_manager.h"
#include "dialogs/kis_dlg_blacklist_cleanup.h"
#include "input/kis_input_profile_manager.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_canvas_controls_manager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_composite_progress_proxy.h"
#include "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_manager.h"
#include <kis_layer.h>
#include "kis_mainwindow_observer.h"
#include "kis_mask_manager.h"
#include "kis_mimedata.h"
#include "kis_mirror_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_node.h"
#include "kis_node_manager.h"
#include "kis_painting_assistants_manager.h"
#include <kis_paint_layer.h>
#include "kis_paintop_box.h"
#include <brushengine/kis_paintop_preset.h>
#include "KisPart.h"
#include "KisPrintJob.h"
#include "kis_progress_widget.h"
#include "kis_resource_server_provider.h"
#include "kis_selection.h"
#include "kis_selection_manager.h"
#include "kis_shape_controller.h"
#include "kis_shape_layer.h"
#include <kis_signal_compressor.h>
#include "kis_statusbar.h"
#include <KisTemplateCreateDia.h>
#include <kis_tool_freehand.h>
#include "kis_tooltip_manager.h"
#include <kis_undo_adapter.h>
#include "KisView.h"
#include "kis_zoom_manager.h"
#include "widgets/kis_floating_message.h"
#include "kis_signal_auto_connection.h"
+#include "kis_script_manager.h"
#include "kis_icon_utils.h"
#include "kis_guides_manager.h"
#include "kis_derived_resources.h"
#include "dialogs/kis_delayed_save_dialog.h"
class BlockingUserInputEventFilter : public QObject
{
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched);
if(dynamic_cast<QWheelEvent*>(event)
|| dynamic_cast<QKeyEvent*>(event)
|| dynamic_cast<QMouseEvent*>(event)) {
return true;
}
else {
return false;
}
}
};
class KisViewManager::KisViewManagerPrivate
{
public:
KisViewManagerPrivate(KisViewManager *_q, KActionCollection *_actionCollection, QWidget *_q_parent)
: filterManager(_q)
, createTemplate(0)
, saveIncremental(0)
, saveIncrementalBackup(0)
, openResourcesDirectory(0)
, rotateCanvasRight(0)
, rotateCanvasLeft(0)
, resetCanvasRotation(0)
, wrapAroundAction(0)
, levelOfDetailAction(0)
, showRulersAction(0)
, rulersTrackMouseAction(0)
, zoomTo100pct(0)
, zoomIn(0)
, zoomOut(0)
, selectionManager(_q)
, statusBar(_q)
, controlFrame(_q, _q_parent)
, nodeManager(_q)
, imageManager(_q)
, gridManager(_q)
, canvasControlsManager(_q)
, paintingAssistantsManager(_q)
, actionManager(_q, _actionCollection)
, mainWindow(0)
, showFloatingMessage(true)
, currentImageView(0)
, canvasResourceProvider(_q)
, canvasResourceManager()
, guiUpdateCompressor(30, KisSignalCompressor::POSTPONE, _q)
, actionCollection(_actionCollection)
, mirrorManager(_q)
, inputManager(_q)
+ , scriptManager(_q)
, actionAuthor(0)
{
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisCompositeOpResourceConverter));
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEffectiveCompositeOpResourceConverter));
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisOpacityResourceConverter));
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisFlowResourceConverter));
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisSizeResourceConverter));
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisLodAvailabilityResourceConverter));
canvasResourceManager.addDerivedResourceConverter(toQShared(new KisEraserModeResourceConverter));
canvasResourceManager.addResourceUpdateMediator(toQShared(new KisPresetUpdateMediator));
}
public:
KisFilterManager filterManager;
KisAction *createTemplate;
KisAction *createCopy;
KisAction *saveIncremental;
KisAction *saveIncrementalBackup;
KisAction *openResourcesDirectory;
KisAction *rotateCanvasRight;
KisAction *rotateCanvasLeft;
KisAction *resetCanvasRotation;
KisAction *wrapAroundAction;
KisAction *levelOfDetailAction;
KisAction *showRulersAction;
KisAction *rulersTrackMouseAction;
KisAction *zoomTo100pct;
KisAction *zoomIn;
KisAction *zoomOut;
KisAction *softProof;
KisAction *gamutCheck;
KisSelectionManager selectionManager;
KisGuidesManager guidesManager;
KisStatusBar statusBar;
KisControlFrame controlFrame;
KisNodeManager nodeManager;
KisImageManager imageManager;
KisGridManager gridManager;
KisCanvasControlsManager canvasControlsManager;
KisPaintingAssistantsManager paintingAssistantsManager;
BlockingUserInputEventFilter blockingEventFilter;
KisActionManager actionManager;
QMainWindow* mainWindow;
QPointer<KisFloatingMessage> savedFloatingMessage;
bool showFloatingMessage;
QPointer<KisView> currentImageView;
KisCanvasResourceProvider canvasResourceProvider;
KoCanvasResourceManager canvasResourceManager;
KisSignalCompressor guiUpdateCompressor;
KActionCollection *actionCollection;
KisMirrorManager mirrorManager;
KisInputManager inputManager;
KisSignalAutoConnectionsStore viewConnections;
+ KisScriptManager scriptManager;
KSelectAction *actionAuthor; // Select action for author profile.
QByteArray canvasState;
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->controlFrame.setup(parent);
//Check to draw scrollbars after "Canvas only mode" toggle is created.
this->showHideScrollbars();
QScopedPointer<KoDummyCanvasController> dummy(new KoDummyCanvasController(actionCollection()));
KoToolManager::instance()->registerToolActions(actionCollection(), dummy.data());
QTimer::singleShot(0, this, SLOT(initializeStatusBarVisibility()));
connect(KoToolManager::instance(), SIGNAL(inputDeviceChanged(KoInputDevice)),
d->controlFrame.paintopBox(), SLOT(slotInputDeviceChanged(KoInputDevice)));
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)),
d->controlFrame.paintopBox(), SLOT(slotToolChanged(KoCanvasController*,int)));
connect(&d->nodeManager, SIGNAL(sigNodeActivated(KisNodeSP)),
resourceProvider(), SLOT(slotNodeActivated(KisNodeSP)));
connect(resourceProvider()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
d->controlFrame.paintopBox(), SLOT(slotCanvasResourceChanged(int,QVariant)));
connect(KisPart::instance(), SIGNAL(sigViewAdded(KisView*)), SLOT(slotViewAdded(KisView*)));
connect(KisPart::instance(), SIGNAL(sigViewRemoved(KisView*)), SLOT(slotViewRemoved(KisView*)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotUpdateAuthorProfileActions()));
KisInputProfileManager::instance()->loadProfiles();
KisConfig cfg;
d->showFloatingMessage = cfg.showCanvasMessages();
}
KisViewManager::~KisViewManager()
{
KisConfig cfg;
if (resourceProvider() && resourceProvider()->currentPreset()) {
cfg.writeEntry("LastPreset", resourceProvider()->currentPreset()->name());
cfg.writeKoColor("LastForeGroundColor",resourceProvider()->fgColor());
cfg.writeKoColor("LastBackGroundColor",resourceProvider()->bgColor());
}
cfg.writeEntry("baseLength", KoResourceItemChooserSync::instance()->baseLength());
delete d;
}
KActionCollection *KisViewManager::actionCollection() const
{
return d->actionCollection;
}
void KisViewManager::slotViewAdded(KisView *view)
{
d->inputManager.addTrackedCanvas(view->canvasBase());
if (viewCount() == 0) {
d->statusBar.showAllStatusBarItems();
}
}
void KisViewManager::slotViewRemoved(KisView *view)
{
d->inputManager.removeTrackedCanvas(view->canvasBase());
if (viewCount() == 0) {
d->statusBar.hideAllStatusBarItems();
}
}
void KisViewManager::setCurrentView(KisView *view)
{
bool first = true;
if (d->currentImageView) {
d->currentImageView->notifyCurrentStateChanged(false);
d->currentImageView->canvasBase()->setCursor(QCursor(Qt::ArrowCursor));
first = false;
KisDocument* doc = d->currentImageView->document();
if (doc) {
doc->disconnect(this);
}
d->currentImageView->canvasController()->proxyObject->disconnect(&d->statusBar);
d->viewConnections.clear();
}
d->softProof->setChecked(view->softProofing());
d->gamutCheck->setChecked(view->gamutCheck());
QPointer<KisView>imageView = qobject_cast<KisView*>(view);
if (imageView) {
// Wait for the async image to have loaded
KisDocument* doc = view->document();
// connect(canvasController()->proxyObject, SIGNAL(documentMousePositionChanged(QPointF)), d->statusBar, SLOT(documentMousePositionChanged(QPointF)));
d->currentImageView = imageView;
// Restore the last used brush preset, color and background color.
if (first) {
KisConfig cfg;
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
QString lastPreset = cfg.readEntry("LastPreset", QString("Basic_tip_default"));
KisPaintOpPresetSP preset = rserver->resourceByName(lastPreset);
if (!preset) {
preset = rserver->resourceByName("Basic_tip_default");
}
if (!preset) {
preset = rserver->resources().first();
}
if (preset) {
paintOpBox()->restoreResource(preset.data());
}
const KoColorSpace *cs = KoColorSpaceRegistry::instance()->rgb8();
KoColor foreground(Qt::black, cs);
d->canvasResourceProvider.setFGColor(cfg.readKoColor("LastForeGroundColor",foreground));
KoColor background(Qt::white, cs);
d->canvasResourceProvider.setBGColor(cfg.readKoColor("LastBackGroundColor",background));
}
KisCanvasController *canvasController = dynamic_cast<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)) );
imageView->zoomManager()->setShowRulers(d->showRulersAction->isChecked());
imageView->zoomManager()->setRulersTrackMouse(d->rulersTrackMouseAction->isChecked());
showHideScrollbars();
}
d->filterManager.setView(imageView);
d->selectionManager.setView(imageView);
d->guidesManager.setView(imageView);
d->nodeManager.setView(imageView);
d->imageManager.setView(imageView);
d->canvasControlsManager.setView(imageView);
d->actionManager.setView(imageView);
d->gridManager.setView(imageView);
d->statusBar.setView(imageView);
d->paintingAssistantsManager.setView(imageView);
d->mirrorManager.setView(imageView);
if (d->currentImageView) {
d->currentImageView->notifyCurrentStateChanged(true);
d->currentImageView->canvasController()->activate();
d->currentImageView->canvasController()->setFocus();
}
d->actionManager.updateGUI();
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)),
resourceProvider(), SLOT(slotImageSizeChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigResolutionChanged(double,double)),
resourceProvider(), SLOT(slotOnScreenResolutionChanged()));
d->viewConnections.addUniqueConnection(
image(), SIGNAL(sigNodeChanged(KisNodeSP)),
this, SLOT(updateGUI()));
d->viewConnections.addUniqueConnection(
view->zoomManager()->zoomController(),
SIGNAL(zoomChanged(KoZoomMode::Mode,qreal)),
resourceProvider(), SLOT(slotOnScreenResolutionChanged()));
resourceProvider()->slotImageSizeChanged();
resourceProvider()->slotOnScreenResolutionChanged();
Q_EMIT viewChanged();
}
KoZoomController *KisViewManager::zoomController() const
{
if (d->currentImageView) {
return d->currentImageView->zoomController();
}
return 0;
}
KisImageWSP KisViewManager::image() const
{
if (document()) {
return document()->image();
}
return 0;
}
KisCanvasResourceProvider * KisViewManager::resourceProvider()
{
return &d->canvasResourceProvider;
}
KisCanvas2 * KisViewManager::canvasBase() const
{
if (d && d->currentImageView) {
return d->currentImageView->canvasBase();
}
return 0;
}
QWidget* KisViewManager::canvas() const
{
if (d && d->currentImageView && d->currentImageView->canvasBase()->canvasWidget()) {
return d->currentImageView->canvasBase()->canvasWidget();
}
return 0;
}
KisStatusBar * KisViewManager::statusBar() const
{
return &d->statusBar;
}
void KisViewManager::addStatusBarItem(QWidget *widget, int stretch, bool permanent)
{
d->statusBar.addStatusBarItem(widget, stretch, permanent);
}
void KisViewManager::removeStatusBarItem(QWidget *widget)
{
d->statusBar.removeStatusBarItem(widget);
}
KisPaintopBox* KisViewManager::paintOpBox() const
{
return d->controlFrame.paintopBox();
}
KoProgressUpdater* KisViewManager::createProgressUpdater(KoProgressUpdater::Mode mode)
{
return new KisProgressUpdater(d->statusBar.progress(), document()->progressProxy(), mode);
}
KisSelectionManager * KisViewManager::selectionManager()
{
return &d->selectionManager;
}
KisNodeSP KisViewManager::activeNode()
{
return d->nodeManager.activeNode();
}
KisLayerSP KisViewManager::activeLayer()
{
return d->nodeManager.activeLayer();
}
KisPaintDeviceSP KisViewManager::activeDevice()
{
return d->nodeManager.activePaintDevice();
}
KisZoomManager * KisViewManager::zoomManager()
{
if (d->currentImageView) {
return d->currentImageView->zoomManager();
}
return 0;
}
KisFilterManager * KisViewManager::filterManager()
{
return &d->filterManager;
}
KisImageManager * KisViewManager::imageManager()
{
return &d->imageManager;
}
KisInputManager* KisViewManager::inputManager() const
{
return &d->inputManager;
}
KisSelectionSP KisViewManager::selection()
{
if (d->currentImageView) {
return d->currentImageView->selection();
}
return 0;
}
bool KisViewManager::selectionEditable()
{
KisLayerSP layer = activeLayer();
if (layer) {
KoProperties properties;
QList<KisNodeSP> masks = layer->childNodes(QStringList("KisSelectionMask"), properties);
if (masks.size() == 1) {
return masks[0]->isEditable();
}
}
// global selection is always editable
return true;
}
KisUndoAdapter * KisViewManager::undoAdapter()
{
if (!document()) return 0;
KisImageWSP image = document()->image();
Q_ASSERT(image);
return image->undoAdapter();
}
void KisViewManager::createActions()
{
KisConfig cfg;
d->saveIncremental = actionManager()->createAction("save_incremental_version");
connect(d->saveIncremental, SIGNAL(triggered()), this, SLOT(slotSaveIncremental()));
d->saveIncrementalBackup = actionManager()->createAction("save_incremental_backup");
connect(d->saveIncrementalBackup, SIGNAL(triggered()), this, SLOT(slotSaveIncrementalBackup()));
connect(mainWindow(), SIGNAL(documentSaved()), this, SLOT(slotDocumentSaved()));
d->saveIncremental->setEnabled(false);
d->saveIncrementalBackup->setEnabled(false);
KisAction *tabletDebugger = actionManager()->createAction("tablet_debugger");
connect(tabletDebugger, SIGNAL(triggered()), this, SLOT(toggleTabletLogger()));
d->createTemplate = actionManager()->createAction("create_template");
connect(d->createTemplate, SIGNAL(triggered()), this, SLOT(slotCreateTemplate()));
d->createCopy = actionManager()->createAction("create_copy");
connect(d->createCopy, SIGNAL(triggered()), this, SLOT(slotCreateCopy()));
d->openResourcesDirectory = actionManager()->createAction("open_resources_directory");
connect(d->openResourcesDirectory, SIGNAL(triggered()), SLOT(openResourcesDirectory()));
d->rotateCanvasRight = actionManager()->createAction("rotate_canvas_right");
d->rotateCanvasLeft = actionManager()->createAction("rotate_canvas_left");
d->resetCanvasRotation = actionManager()->createAction("reset_canvas_rotation");
d->wrapAroundAction = actionManager()->createAction("wrap_around_mode");
d->levelOfDetailAction = actionManager()->createAction("level_of_detail_mode");
d->softProof = actionManager()->createAction("softProof");
d->gamutCheck = actionManager()->createAction("gamutCheck");
KisAction *tAction = actionManager()->createAction("showStatusBar");
tAction->setChecked(cfg.showStatusBar());
connect(tAction, SIGNAL(toggled(bool)), this, SLOT(showStatusBar(bool)));
tAction = actionManager()->createAction("view_show_canvas_only");
tAction->setChecked(false);
connect(tAction, SIGNAL(toggled(bool)), this, SLOT(switchCanvasOnly(bool)));
//Workaround, by default has the same shortcut as mirrorCanvas
KisAction *a = dynamic_cast<KisAction*>(actionCollection()->action("format_italic"));
if (a) {
a->setDefaultShortcut(QKeySequence());
}
a = actionManager()->createAction("edit_blacklist_cleanup");
connect(a, SIGNAL(triggered()), this, SLOT(slotBlacklistCleanup()));
d->showRulersAction = actionManager()->createAction("view_ruler");
d->showRulersAction->setChecked(cfg.showRulers());
connect(d->showRulersAction, SIGNAL(toggled(bool)), SLOT(slotSaveShowRulersState(bool)));
d->rulersTrackMouseAction = actionManager()->createAction("rulers_track_mouse");
d->rulersTrackMouseAction->setChecked(cfg.rulersTrackMouse());
connect(d->rulersTrackMouseAction, SIGNAL(toggled(bool)), SLOT(slotSaveRulersTrackMouseState(bool)));
d->zoomTo100pct = actionManager()->createAction("zoom_to_100pct");
d->zoomIn = actionManager()->createStandardAction(KStandardAction::ZoomIn, 0, "");
d->zoomOut = actionManager()->createStandardAction(KStandardAction::ZoomOut, 0, "");
d->actionAuthor = new KSelectAction(KisIconUtils::loadIcon("im-user"), i18n("Active Author Profile"), this);
connect(d->actionAuthor, SIGNAL(triggered(const QString &)), this, SLOT(changeAuthorProfile(const QString &)));
actionCollection()->addAction("settings_active_author", d->actionAuthor);
slotUpdateAuthorProfileActions();
}
void KisViewManager::setupManagers()
{
// Create the managers for filters, selections, layers etc.
// XXX: When the currentlayer changes, call updateGUI on all
// managers
d->filterManager.setup(actionCollection(), actionManager());
d->selectionManager.setup(actionManager());
d->guidesManager.setup(actionManager());
d->nodeManager.setup(actionCollection(), actionManager());
d->imageManager.setup(actionManager());
d->gridManager.setup(actionManager());
d->paintingAssistantsManager.setup(actionManager());
d->canvasControlsManager.setup(actionManager());
d->mirrorManager.setup(actionCollection());
+ d->scriptManager.setup(actionCollection(), actionManager());
}
void KisViewManager::updateGUI()
{
d->guiUpdateCompressor.start();
}
void KisViewManager::slotBlacklistCleanup()
{
KisDlgBlacklistCleanup dialog;
dialog.exec();
}
KisNodeManager * KisViewManager::nodeManager() const
{
return &d->nodeManager;
}
KisActionManager* KisViewManager::actionManager() const
{
return &d->actionManager;
}
KisGridManager * KisViewManager::gridManager() const
{
return &d->gridManager;
}
KisGuidesManager * KisViewManager::guidesManager() const
{
return &d->guidesManager;
}
KisPaintingAssistantsManager* KisViewManager::paintingAssistantsManager() const
{
return &d->paintingAssistantsManager;
}
KisDocument *KisViewManager::document() const
{
if (d->currentImageView && d->currentImageView->document()) {
return d->currentImageView->document();
}
return 0;
}
+KisScriptManager *KisViewManager::scriptManager() const
+{
+ return &d->scriptManager;
+}
+
int KisViewManager::viewCount() const
{
KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
if (mw) {
return mw->viewCount();
}
return 0;
}
bool KisViewManager::KisViewManagerPrivate::blockUntilOperationsFinishedImpl(KisImageSP image, bool force)
{
const int busyWaitDelay = 1000;
KisDelayedSaveDialog dialog(image, !force ? KisDelayedSaveDialog::GeneralDialog : KisDelayedSaveDialog::ForcedDialog, busyWaitDelay, mainWindow);
dialog.blockIfImageIsBusy();
return dialog.result() == QDialog::Accepted;
}
bool KisViewManager::blockUntilOperationsFinished(KisImageSP image)
{
return d->blockUntilOperationsFinishedImpl(image, false);
}
void KisViewManager::blockUntillOperationsFinishedForced(KisImageSP image)
{
d->blockUntilOperationsFinishedImpl(image, true);
}
void KisViewManager::slotCreateTemplate()
{
if (!document()) return;
KisTemplateCreateDia::createTemplate( QStringLiteral("templates/"), ".kra", document(), mainWindow());
}
void KisViewManager::slotCreateCopy()
{
if (!document()) return;
KisDocument *doc = KisPart::instance()->createDocument();
QString name = document()->documentInfo()->aboutInfo("name");
if (name.isEmpty()) {
name = document()->url().toLocalFile();
}
name = i18n("%1 (Copy)", name);
doc->documentInfo()->setAboutInfo("title", name);
KisImageWSP image = document()->image();
KisImageSP newImage = new KisImage(doc->createUndoStore(), image->width(), image->height(), image->colorSpace(), name);
newImage->setRootLayer(dynamic_cast<KisGroupLayer*>(image->rootLayer()->clone().data()));
doc->setCurrentImage(newImage);
KisPart::instance()->addDocument(doc);
KisMainWindow *mw = qobject_cast<KisMainWindow*>(d->mainWindow);
mw->addViewAndNotifyLoadingCompleted(doc);
}
QMainWindow* KisViewManager::qtMainWindow() const
{
if (d->mainWindow)
return d->mainWindow;
//Fallback for when we have not yet set the main window.
QMainWindow* w = qobject_cast<QMainWindow*>(qApp->activeWindow());
if(w)
return w;
return mainWindow();
}
void KisViewManager::setQtMainWindow(QMainWindow* newMainWindow)
{
d->mainWindow = newMainWindow;
}
void KisViewManager::slotDocumentSaved()
{
d->saveIncremental->setEnabled(true);
d->saveIncrementalBackup->setEnabled(true);
}
void KisViewManager::slotSaveIncremental()
{
if (!document()) return;
bool foundVersion;
bool fileAlreadyExists;
bool isBackup;
QString version = "000";
QString newVersion;
QString letter;
QString fileName = document()->localFilePath();
// Find current version filenames
// v v Regexp to find incremental versions in the filename, taking our backup scheme into account as well
// Considering our incremental version and backup scheme, format is filename_001~001.ext
QRegExp regex("_\\d{1,4}[.]|_\\d{1,4}[a-z][.]|_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
regex.indexIn(fileName); // Perform the search
QStringList matches = regex.capturedTexts();
foundVersion = matches.at(0).isEmpty() ? false : true;
// Ensure compatibility with Save Incremental Backup
// If this regex is not kept separate, the entire algorithm needs modification;
// It's simpler to just add this.
QRegExp regexAux("_\\d{1,4}[~]|_\\d{1,4}[a-z][~]");
regexAux.indexIn(fileName); // Perform the search
QStringList matchesAux = regexAux.capturedTexts();
isBackup = matchesAux.at(0).isEmpty() ? false : true;
// If the filename has a version, prepare it for incrementation
if (foundVersion) {
version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches
if (version.contains(QRegExp("[a-z]"))) {
version.chop(1); // Trim "."
letter = version.right(1); // Save letter
version.chop(1); // Trim letter
} else {
version.chop(1); // Trim "."
}
version.remove(0, 1); // Trim "_"
} else {
// ...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()->setFileBatchMode(false);
if (mainWindow()) {
mainWindow()->updateCaption();
}
}
void KisViewManager::slotSaveIncrementalBackup()
{
if (!document()) return;
bool workingOnBackup;
bool fileAlreadyExists;
QString version = "000";
QString newVersion;
QString letter;
QString fileName = document()->localFilePath();
// First, discover if working on a backup file, or a normal file
QRegExp regex("~\\d{1,4}[.]|~\\d{1,4}[a-z][.]");
regex.indexIn(fileName); // Perform the search
QStringList matches = regex.capturedTexts();
workingOnBackup = matches.at(0).isEmpty() ? false : true;
if (workingOnBackup) {
// Try to save incremental version (of backup), use letter for alt versions
version = matches.at(matches.count() - 1); // Look at the last index, we don't care about other matches
if (version.contains(QRegExp("[a-z]"))) {
version.chop(1); // Trim "."
letter = version.right(1); // Save letter
version.chop(1); // Trim letter
} else {
version.chop(1); // Trim "."
}
version.remove(0, 1); // Trim "~"
// Prepare the base for new version filename
int intVersion = version.toInt(0);
++intVersion;
QString baseNewVersion = QString::number(intVersion);
QString backupFileName = document()->localFilePath();
while (baseNewVersion.length() < version.length()) {
baseNewVersion.prepend("0");
}
// Check if the file exists under the new name and search until options are exhausted (test appending a to z)
do {
newVersion = baseNewVersion;
newVersion.prepend("~");
if (!letter.isNull()) newVersion.append(letter);
newVersion.append(".");
backupFileName.replace(regex, newVersion);
fileAlreadyExists = QFile(backupFileName).exists();
if (fileAlreadyExists) {
if (!letter.isNull()) {
char letterCh = letter.at(0).toLatin1();
++letterCh;
letter = QString(QChar(letterCh));
} else {
letter = 'a';
}
}
} while (fileAlreadyExists && letter != "{"); // x, y, z, {...
if (letter == "{") {
QMessageBox::critical(mainWindow(), i18nc("@title:window", "Couldn't save incremental backup"), i18n("Alternative names exhausted, try manually saving with a higher number"));
return;
}
QFile::copy(fileName, backupFileName);
document()->saveAs(QUrl::fromUserInput(fileName));
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()->setFileBatchMode(false);
if (mainWindow()) mainWindow()->updateCaption();
}
}
void KisViewManager::disableControls()
{
// prevents possible crashes, if somebody changes the paintop during dragging by using the mousewheel
// this is for Bug 250944
// the solution blocks all wheel, mouse and key event, while dragging with the freehand tool
// see KisToolFreehand::initPaint() and endPaint()
d->controlFrame.paintopBox()->installEventFilter(&d->blockingEventFilter);
Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
child->installEventFilter(&d->blockingEventFilter);
}
}
void KisViewManager::enableControls()
{
d->controlFrame.paintopBox()->removeEventFilter(&d->blockingEventFilter);
Q_FOREACH (QObject* child, d->controlFrame.paintopBox()->children()) {
child->removeEventFilter(&d->blockingEventFilter);
}
}
void KisViewManager::showStatusBar(bool toggled)
{
KisMainWindow *mw = mainWindow();
if(mw && mw->statusBar()) {
mw->statusBar()->setVisible(toggled);
KisConfig cfg;
cfg.setShowStatusBar(toggled);
}
}
void KisViewManager::switchCanvasOnly(bool toggled)
{
KisConfig cfg;
KisMainWindow* main = mainWindow();
if(!main) {
dbgUI << "Unable to switch to canvas-only mode, main window not found";
return;
}
if (toggled) {
d->canvasState = qtMainWindow()->saveState();
}
if (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());
}
}
}
if (cfg.hideTitlebarFullscreen() && !cfg.fullscreenMode()) {
if(toggled) {
main->setWindowState( main->windowState() | Qt::WindowFullScreen);
} else {
main->setWindowState( main->windowState() & ~Qt::WindowFullScreen);
}
}
if (cfg.hideMenuFullscreen()) {
if (!toggled) {
if (main->menuBar()->dynamicPropertyNames().contains("wasvisible")) {
if (main->menuBar()->property("wasvisible").toBool()) {
main->menuBar()->setVisible(true);
}
}
}
else {
main->menuBar()->setProperty("wasvisible", main->menuBar()->isVisible());
main->menuBar()->setVisible(false);
}
}
if (cfg.hideToolbarFullscreen()) {
QList<QToolBar*> toolBars = main->findChildren<QToolBar*>();
Q_FOREACH (QToolBar* toolbar, toolBars) {
if (!toggled) {
if (toolbar->dynamicPropertyNames().contains("wasvisible")) {
if (toolbar->property("wasvisible").toBool()) {
toolbar->setVisible(true);
}
}
}
else {
toolbar->setProperty("wasvisible", toolbar->isVisible());
toolbar->setVisible(false);
}
}
}
showHideScrollbars();
if (toggled) {
// show a fading heads-up display about the shortcut to go back
showFloatingMessage(i18n("Going into Canvas-Only mode.\nPress %1 to go back.",
actionCollection()->action("view_show_canvas_only")->shortcut().toString()), QIcon());
}
else {
main->restoreState(d->canvasState);
}
}
void KisViewManager::toggleTabletLogger()
{
d->inputManager.toggleTabletLogger();
}
void KisViewManager::openResourcesDirectory()
{
QString dir = KoResourcePaths::locateLocal("data", "");
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
}
void KisViewManager::updateIcons()
{
if (mainWindow()) {
QList<QDockWidget*> dockers = mainWindow()->dockWidgets();
Q_FOREACH (QDockWidget* dock, dockers) {
dbgKrita << "name " << dock->objectName();
KoDockWidgetTitleBar* titlebar = dynamic_cast<KoDockWidgetTitleBar*>(dock->titleBarWidget());
if (titlebar) {
titlebar->updateIcons();
}
QObjectList objects;
objects.append(dock);
while (!objects.isEmpty()) {
QObject* object = objects.takeFirst();
objects.append(object->children());
KisIconUtils::updateIconCommon(object);
}
}
}
}
void KisViewManager::initializeStatusBarVisibility()
{
KisConfig cfg;
d->mainWindow->statusBar()->setVisible(cfg.showStatusBar());
}
void KisViewManager::guiUpdateTimeout()
{
d->nodeManager.updateGUI();
d->selectionManager.updateGUI();
d->filterManager.updateGUI();
if (zoomManager()) {
zoomManager()->updateGUI();
}
d->gridManager.updateGUI();
d->actionManager.updateGUI();
}
void KisViewManager::showFloatingMessage(const QString &message, const QIcon& icon, int timeout, KisFloatingMessage::Priority priority, int alignment)
{
if (!d->currentImageView) return;
d->currentImageView->showFloatingMessageImpl(message, icon, timeout, priority, alignment);
emit floatingMessageRequested(message, icon.name());
}
KisMainWindow *KisViewManager::mainWindow() const
{
return qobject_cast<KisMainWindow*>(d->mainWindow);
}
void KisViewManager::showHideScrollbars()
{
if (!d->currentImageView) return;
if (!d->currentImageView->canvasController()) return;
KisConfig cfg;
bool toggled = actionCollection()->action("view_show_canvas_only")->isChecked();
if ( (toggled && cfg.hideScrollbarsFullscreen()) || (!toggled && cfg.hideScrollbars()) ) {
d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
} else {
d->currentImageView->canvasController()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
d->currentImageView->canvasController()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}
}
void KisViewManager::slotSaveShowRulersState(bool value)
{
KisConfig cfg;
cfg.setShowRulers(value);
}
void KisViewManager::slotSaveRulersTrackMouseState(bool value)
{
KisConfig cfg;
cfg.setRulersTrackMouse(value);
}
void KisViewManager::setShowFloatingMessage(bool show)
{
d->showFloatingMessage = show;
}
void KisViewManager::changeAuthorProfile(const QString &profileName)
{
KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author");
if (profileName.isEmpty()) {
appAuthorGroup.writeEntry("active-profile", "");
} else if (profileName == i18nc("choice for author profile", "Anonymous")) {
appAuthorGroup.writeEntry("active-profile", "anonymous");
} else {
appAuthorGroup.writeEntry("active-profile", profileName);
}
appAuthorGroup.sync();
Q_FOREACH (KisDocument *doc, KisPart::instance()->documents()) {
doc->documentInfo()->updateParameters();
}
}
void KisViewManager::slotUpdateAuthorProfileActions()
{
Q_ASSERT(d->actionAuthor);
if (!d->actionAuthor) {
return;
}
d->actionAuthor->clear();
d->actionAuthor->addAction(i18n("Default Author Profile"));
d->actionAuthor->addAction(i18nc("choice for author profile", "Anonymous"));
KConfigGroup authorGroup(KoGlobal::calligraConfig(), "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
Q_FOREACH (const QString &profile , profiles) {
d->actionAuthor->addAction(profile);
}
KConfigGroup appAuthorGroup(KoGlobal::calligraConfig(), "Author");
QString profileName = appAuthorGroup.readEntry("active-profile", "");
if (profileName == "anonymous") {
d->actionAuthor->setCurrentItem(1);
} else if (profiles.contains(profileName)) {
d->actionAuthor->setCurrentAction(profileName);
} else {
d->actionAuthor->setCurrentItem(0);
}
}
diff --git a/libs/ui/KisViewManager.h b/libs/ui/KisViewManager.h
index a9ec5c67e1..8e9d75e36b 100644
--- a/libs/ui/KisViewManager.h
+++ b/libs/ui/KisViewManager.h
@@ -1,269 +1,272 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_GUI_CLIENT_H
#define KIS_GUI_CLIENT_H
#include <QDockWidget>
#include <QQueue>
#include <QPointer>
#include <KisMainWindow.h>
#include <KoProgressUpdater.h>
#include <KoToolManager.h>
#include <kritaui_export.h>
#include <kis_types.h>
#include "kis_floating_message.h"
class QPoint;
class KisView;
class KisCanvas2;
class KisCanvasResourceProvider;
class KisDocument;
class KisFilterManager;
class KisGridManager;
class KisGuidesManager;
class KisImageManager;
class KisNodeManager;
class KisPaintingAssistantsManager;
class KisPaintopBox;
class KisSelectionManager;
class KisStatusBar;
class KisUndoAdapter;
class KisZoomManager;
class KisPaintopBox;
class KisActionManager;
+class KisScriptManager;
class KisInputManager;
/**
* Krita view class
*
* Following the broad model-view-controller idea this class shows you one view on the document.
* There can be multiple views of the same document each in with independent settings for viewMode and zoom etc.
*/
class KRITAUI_EXPORT KisViewManager : public QObject
{
Q_OBJECT
public:
/**
* Construct a new view on the krita document.
* @param document the document we show.
* @param parent a parent widget we show ourselves in.
*/
KisViewManager(QWidget *parent, KActionCollection *actionCollection);
virtual ~KisViewManager();
/**
* Retrieves the entire action collection.
*/
virtual KActionCollection* actionCollection() const;
public: // Krita specific interfaces
void setCurrentView(KisView *view);
/// Return the image this view is displaying
KisImageWSP image() const;
KoZoomController *zoomController() const;
/// The resource provider contains all per-view settings, such as
/// current color, current paint op etc.
KisCanvasResourceProvider * resourceProvider();
/// Return the canvasbase class
KisCanvas2 * canvasBase() const;
/// Return the actual widget that is displaying the current image
QWidget* canvas() const;
/// Return the wrapper class around the statusbar
KisStatusBar * statusBar() const;
/**
* This adds a widget to the statusbar for this view.
* If you use this method instead of using statusBar() directly,
* KisView will take care of removing the items when the view GUI is deactivated
* and readding them when it is reactivated.
* The parameters are the same as QStatusBar::addWidget().
*/
void addStatusBarItem(QWidget * widget, int stretch = 0, bool permanent = false);
/**
* Remove a widget from the statusbar for this view.
*/
void removeStatusBarItem(QWidget * widget);
KisPaintopBox* paintOpBox() const;
/// create a new progress updater
KoProgressUpdater *createProgressUpdater(KoProgressUpdater::Mode mode = KoProgressUpdater::Threaded);
/// The selection manager handles everything action related to
/// selections.
KisSelectionManager *selectionManager();
/// The node manager handles everything about nodes
KisNodeManager *nodeManager() const;
KisActionManager *actionManager() const;
/**
* Convenience method to get at the active node, which may be
* a layer or a mask or a selection
*/
KisNodeSP activeNode();
/// Convenience method to get at the active layer
KisLayerSP activeLayer();
/// Convenience method to get at the active paint device
KisPaintDeviceSP activeDevice();
/// The filtermanager handles everything action-related to filters
KisFilterManager *filterManager();
/// The image manager handles everything action-related to the
/// current image
KisImageManager *imageManager();
/// Filters events and sends them to canvas actions
KisInputManager *inputManager() const;
/// Convenience method to get at the active selection (the
/// selection of the current layer, or, if that does not exist,
/// the global selection.
KisSelectionSP selection();
/// Checks if the current global or local selection is editable
bool selectionEditable();
/// The undo adapter is used to add commands to the undo stack
KisUndoAdapter *undoAdapter();
KisDocument *document() const;
+
+ KisScriptManager *scriptManager() const;
int viewCount() const;
/**
* @brief blockUntillOperationsFinished blocks the GUI of the application until execution
* of actions on \p image is finished
* @param image the image which we should wait for
* @return true if the image has finished execution of the actions, false if
* the user cancelled operation
*/
bool blockUntilOperationsFinished(KisImageSP image);
/**
* @brief blockUntillOperationsFinished blocks the GUI of the application until execution
* of actions on \p image is finished. Does *not* provide a "Cancel" button. So the
* user is forced to wait.
* @param image the image which we should wait for
*/
void blockUntillOperationsFinishedForced(KisImageSP image);
public:
KisGridManager * gridManager() const;
KisGuidesManager * guidesManager() const;
KisPaintingAssistantsManager* paintingAssistantsManager() const;
/// disable and enable toolbar controls. used for disabling them during painting.
void enableControls();
void disableControls();
/// shows a floating message in the top right corner of the canvas
void showFloatingMessage(const QString &message, const QIcon& icon, int timeout = 4500,
KisFloatingMessage::Priority priority = KisFloatingMessage::Medium,
int alignment = Qt::AlignCenter | Qt::TextWordWrap);
/// @return the KoMaindow this view is in, or 0
KisMainWindow *mainWindow() const;
/// The QMainWindow associated with this view. This is most likely going to be shell(), but
/// when running as Gemini or Sketch, this will be set to the applications' own QMainWindow.
/// This can be checked by qobject_casting to KisMainWindow to check the difference.
QMainWindow* qtMainWindow() const;
/// The mainWindow function will return the shell() value, unless this function is called
/// with a non-null value. To make it return shell() again, simply pass null to this function.
void setQtMainWindow(QMainWindow* newMainWindow);
public Q_SLOTS:
void switchCanvasOnly(bool toggled);
void setShowFloatingMessage(bool show);
void showHideScrollbars();
/// Visit all managers to update gui elements, e.g. enable / disable actions.
/// This is heavy-duty call, so it uses a compressor.
void updateGUI();
/// Update the style of all the icons
void updateIcons();
void slotViewAdded(KisView *view);
void slotViewRemoved(KisView *view);
Q_SIGNALS:
void floatingMessageRequested(const QString &message, const QString &iconName);
/**
* @brief viewChanged
* sent out when the view has changed.
*/
void viewChanged();
private Q_SLOTS:
void slotBlacklistCleanup();
void slotCreateTemplate();
void slotCreateCopy();
void slotDocumentSaved();
void slotSaveIncremental();
void slotSaveIncrementalBackup();
void showStatusBar(bool toggled);
void toggleTabletLogger();
void openResourcesDirectory();
void initializeStatusBarVisibility();
void guiUpdateTimeout();
void changeAuthorProfile(const QString &profileName);
void slotUpdateAuthorProfileActions();
void slotSaveShowRulersState(bool value);
void slotSaveRulersTrackMouseState(bool value);
private:
void createActions();
void setupManagers();
/// The zoommanager handles everything action-related to zooming
KisZoomManager * zoomManager();
private:
class KisViewManagerPrivate;
KisViewManagerPrivate * const d;
};
#endif
diff --git a/libs/ui/actions/KisPasteActionFactory.cpp b/libs/ui/actions/KisPasteActionFactory.cpp
new file mode 100644
index 0000000000..5a23f23456
--- /dev/null
+++ b/libs/ui/actions/KisPasteActionFactory.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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 "KisPasteActionFactory.h"
+
+#include "kis_image.h"
+#include "KisViewManager.h"
+#include "kis_tool_proxy.h"
+#include "kis_canvas2.h"
+#include "kis_canvas_controller.h"
+#include "kis_paint_device.h"
+#include "kis_paint_layer.h"
+#include "kis_shape_layer.h"
+#include "kis_import_catcher.h"
+#include "kis_clipboard.h"
+#include "commands/kis_image_layer_add_command.h"
+#include "kis_processing_applicator.h"
+
+#include <KoSvgPaste.h>
+#include <KoShapeController.h>
+#include <KoShapeManager.h>
+#include <KoSelection.h>
+#include <KoSelectedShapesProxy.h>
+#include "kis_algebra_2d.h"
+#include <KoShapeMoveCommand.h>
+
+namespace {
+QPointF getFittingOffset(QList<KoShape*> shapes,
+ const QPointF &shapesOffset,
+ const QRectF &documentRect,
+ const qreal fitRatio)
+{
+ QPointF accumulatedFitOffset;
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ const QRectF bounds = shape->boundingRect();
+
+ const QPointF center = bounds.center() + shapesOffset;
+
+ const qreal wMargin = (0.5 - fitRatio) * bounds.width();
+ const qreal hMargin = (0.5 - fitRatio) * bounds.height();
+ const QRectF allowedRect = documentRect.adjusted(-wMargin, -hMargin, wMargin, hMargin);
+
+ const QPointF fittedCenter = KisAlgebra2D::clampPoint(center, allowedRect);
+
+ accumulatedFitOffset += fittedCenter - center;
+ }
+
+ return accumulatedFitOffset;
+}
+
+bool tryPasteShapes(bool pasteAtCursorPosition, KisViewManager *view)
+{
+ bool result = false;
+
+ KoSvgPaste paste;
+
+ if (paste.hasShapes()) {
+ KoCanvasBase *canvas = view->canvasBase();
+
+ QSizeF fragmentSize;
+ QList<KoShape*> shapes =
+ paste.fetchShapes(canvas->shapeController()->documentRectInPixels(),
+ canvas->shapeController()->pixelsPerInch(), &fragmentSize);
+
+ if (!shapes.isEmpty()) {
+ KoShapeManager *shapeManager = canvas->shapeManager();
+ shapeManager->selection()->deselectAll();
+
+ KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18n("Paste shapes"));
+ canvas->shapeController()->addShapesDirect(shapes, parentCommand);
+
+ QPointF finalShapesOffset;
+
+
+ if (pasteAtCursorPosition) {
+ QRectF boundingRect = KoShape::boundingRect(shapes);
+ const QPointF cursorPos = canvas->canvasController()->currentCursorPosition();
+ finalShapesOffset = cursorPos - boundingRect.center();
+
+ } else {
+ bool foundOverlapping = false;
+
+ QRectF boundingRect = KoShape::boundingRect(shapes);
+ const QPointF offsetStep = 0.1 * QPointF(boundingRect.width(), boundingRect.height());
+
+ QPointF offset;
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ QRectF br1 = shape->boundingRect();
+
+ bool hasOverlappingShape = false;
+
+ do {
+ hasOverlappingShape = false;
+
+ // we cannot use shapesAt() here, because the groups are not
+ // handled in the shape manager's tree
+ QList<KoShape*> conflicts = shapeManager->shapes();
+
+ Q_FOREACH (KoShape *intersectedShape, conflicts) {
+ if (intersectedShape == shape) continue;
+
+ QRectF br2 = intersectedShape->boundingRect();
+
+ const qreal tolerance = 2.0; /* pt */
+ if (KisAlgebra2D::fuzzyCompareRects(br1, br2, tolerance)) {
+ br1.translate(offsetStep.x(), offsetStep.y());
+ offset += offsetStep;
+
+ hasOverlappingShape = true;
+ foundOverlapping = true;
+ break;
+ }
+ }
+ } while (hasOverlappingShape);
+
+ if (foundOverlapping) break;
+ }
+
+ if (foundOverlapping) {
+ finalShapesOffset = offset;
+ }
+ }
+
+ const QRectF documentRect = canvas->shapeController()->documentRect();
+ finalShapesOffset += getFittingOffset(shapes, finalShapesOffset, documentRect, 0.1);
+
+ if (!finalShapesOffset.isNull()) {
+ new KoShapeMoveCommand(shapes, finalShapesOffset, parentCommand);
+ }
+
+ canvas->addCommand(parentCommand);
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ canvas->selectedShapesProxy()->selection()->select(shape);
+ }
+
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+}
+
+void KisPasteActionFactory::run(bool pasteAtCursorPosition, KisViewManager *view)
+{
+ KisImageSP image = view->image();
+ if (!image) return;
+
+ if (tryPasteShapes(pasteAtCursorPosition, view)) {
+ return;
+ }
+
+ const QRect fittingBounds = pasteAtCursorPosition ? QRect() : image->bounds();
+ KisPaintDeviceSP clip = KisClipboard::instance()->clip(fittingBounds, true);
+
+ if (clip) {
+ if (pasteAtCursorPosition) {
+ const QPointF docPos = view->canvasBase()->canvasController()->currentCursorPosition();
+ const QPointF imagePos = view->canvasBase()->coordinatesConverter()->documentToImage(docPos);
+
+ const QPointF offset = (imagePos - QRectF(clip->exactBounds()).center()).toPoint();
+
+ clip->setX(clip->x() + offset.x());
+ clip->setY(clip->y() + offset.y());
+ }
+
+ KisImportCatcher::adaptClipToImageColorSpace(clip, image);
+ KisPaintLayer *newLayer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip);
+ KisNodeSP aboveNode = view->activeLayer();
+ KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root();
+
+ KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode);
+ KisProcessingApplicator *ap = beginAction(view, cmd->text());
+ ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
+ endAction(ap, KisOperationConfiguration(id()).toXML());
+ } else {
+ // XXX: "Add saving of XML data for Paste of shapes"
+ view->canvasBase()->toolProxy()->paste();
+ }
+}
diff --git a/libs/ui/actions/KisPasteActionFactory.h b/libs/ui/actions/KisPasteActionFactory.h
new file mode 100644
index 0000000000..3b158ff510
--- /dev/null
+++ b/libs/ui/actions/KisPasteActionFactory.h
@@ -0,0 +1,35 @@
+/*
+ * 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 KISPASTEACTIONFACTORY_H
+#define KISPASTEACTIONFACTORY_H
+
+#include "operations/kis_operation.h"
+#include "operations/kis_operation_configuration.h"
+
+struct KRITAUI_EXPORT KisPasteActionFactory : public KisOperation {
+ KisPasteActionFactory() : KisOperation("paste-ui-action") {}
+
+ void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) {
+ run(config.getBool("paste-at-cursor-position", false), view);
+ }
+
+ void run(bool pasteAtCursorPosition, KisViewManager *view);
+};
+
+#endif // KISPASTEACTIONFACTORY_H
diff --git a/libs/ui/actions/kis_selection_action_factories.cpp b/libs/ui/actions/kis_selection_action_factories.cpp
index d0123a689a..e11d6124cb 100644
--- a/libs/ui/actions/kis_selection_action_factories.cpp
+++ b/libs/ui/actions/kis_selection_action_factories.cpp
@@ -1,643 +1,579 @@
/*
* 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_selection_action_factories.h"
#include <QMimeData>
#include <klocalizedstring.h>
#include <kundo2command.h>
#include <KisMainWindow.h>
#include <KisDocument.h>
#include <KisPart.h>
#include <KoPathShape.h>
#include <KoShapeController.h>
#include <KoShapeRegistry.h>
#include <KoCompositeOpRegistry.h>
-#include <KoOdfPaste.h>
-#include <KoOdfLoadingContext.h>
-#include <KoOdfReadStore.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
-#include <KoDrag.h>
-#include <KoShapeOdfSaveHelper.h>
#include <KoShapeController.h>
#include <KoDocumentResourceManager.h>
#include <KoShapeStroke.h>
#include "KisViewManager.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_pixel_selection.h"
#include "kis_paint_layer.h"
#include "kis_image.h"
#include "kis_image_barrier_locker.h"
#include "kis_fill_painter.h"
#include "kis_transaction.h"
#include "kis_iterator_ng.h"
#include "kis_processing_applicator.h"
#include "kis_group_layer.h"
#include "commands/kis_selection_commands.h"
#include "commands/kis_image_layer_add_command.h"
#include "kis_tool_proxy.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_selection_manager.h"
#include "kis_transaction_based_command.h"
#include "kis_selection_filters.h"
#include "kis_shape_selection.h"
#include "KisPart.h"
#include "kis_shape_layer.h"
#include <kis_shape_controller.h>
-#include "kis_import_catcher.h"
#include <processing/fill_processing_visitor.h>
#include <kis_selection_tool_helper.h>
#include "kis_canvas_resource_provider.h"
#include "kis_figure_painting_tool_helper.h"
namespace ActionHelper {
void copyFromDevice(KisViewManager *view, KisPaintDeviceSP device, bool makeSharpClip = false)
{
KisImageWSP image = view->image();
if (!image) return;
KisSelectionSP selection = view->selection();
QRect rc = (selection) ? selection->selectedExactRect() : image->bounds();
KisPaintDeviceSP clip = new KisPaintDevice(device->colorSpace());
Q_CHECK_PTR(clip);
const KoColorSpace *cs = clip->colorSpace();
// TODO if the source is linked... copy from all linked layers?!?
// Copy image data
KisPainter::copyAreaOptimized(QPoint(), device, clip, rc);
if (selection) {
// Apply selection mask.
KisPaintDeviceSP selectionProjection = selection->projection();
KisHLineIteratorSP layerIt = clip->createHLineIteratorNG(0, 0, rc.width());
KisHLineConstIteratorSP selectionIt = selectionProjection->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
const KoColorSpace *selCs = selection->projection()->colorSpace();
for (qint32 y = 0; y < rc.height(); y++) {
for (qint32 x = 0; x < rc.width(); x++) {
/**
* Sharp method is an exact reverse of COMPOSITE_OVER
* so if you cover the cut/copied piece over its source
* you get an exactly the same image without any seams
*/
if (makeSharpClip) {
qreal dstAlpha = cs->opacityF(layerIt->rawData());
qreal sel = selCs->opacityF(selectionIt->oldRawData());
qreal newAlpha = sel * dstAlpha / (1.0 - dstAlpha + sel * dstAlpha);
float mask = newAlpha / dstAlpha;
cs->applyAlphaNormedFloatMask(layerIt->rawData(), &mask, 1);
} else {
cs->applyAlphaU8Mask(layerIt->rawData(), selectionIt->oldRawData(), 1);
}
layerIt->nextPixel();
selectionIt->nextPixel();
}
layerIt->nextRow();
selectionIt->nextRow();
}
}
KisClipboard::instance()->setClip(clip, rc.topLeft());
}
}
void KisSelectAllActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Select All"));
if (!image->globalSelection()) {
ap->applyCommand(new KisSetEmptyGlobalSelectionCommand(image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
}
struct SelectAll : public KisTransactionBasedCommand {
SelectAll(KisImageSP image) : m_image(image) {}
KisImageSP m_image;
KUndo2Command* paint() override {
KisSelectionSP selection = m_image->globalSelection();
KisSelectionTransaction transaction(selection->pixelSelection());
selection->pixelSelection()->select(m_image->bounds());
return transaction.endAndTake();
}
};
ap->applyCommand(new SelectAll(image),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisDeselectActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
KUndo2Command *cmd = new KisDeselectGlobalSelectionCommand(image);
KisProcessingApplicator *ap = beginAction(view, cmd->text());
ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisReselectActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
KUndo2Command *cmd = new KisReselectGlobalSelectionCommand(image);
KisProcessingApplicator *ap = beginAction(view, cmd->text());
ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
void KisFillActionFactory::run(const QString &fillSource, KisViewManager *view)
{
KisNodeSP node = view->activeNode();
if (!node || !node->hasEditablePaintDevice()) return;
KisSelectionSP selection = view->selection();
QRect selectedRect = selection ?
selection->selectedRect() : view->image()->bounds();
Q_UNUSED(selectedRect);
KisPaintDeviceSP filled = node->paintDevice()->createCompositionSourceDevice();
Q_UNUSED(filled);
bool usePattern = false;
bool useBgColor = false;
if (fillSource.contains("pattern")) {
usePattern = true;
} else if (fillSource.contains("bg")) {
useBgColor = true;
}
KisProcessingApplicator applicator(view->image(), node,
KisProcessingApplicator::NONE,
KisImageSignalVector() << ModifiedSignal,
kundo2_i18n("Flood Fill Layer"));
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(view->image(), node, view->resourceProvider()->resourceManager());
if (!fillSource.contains("opacity")) {
resources->setOpacity(1.0);
}
KisProcessingVisitorSP visitor =
new FillProcessingVisitor(QPoint(0, 0), // start position
selection,
resources,
false, // fast mode
usePattern,
true, // fill only selection,
0, // feathering radius
0, // sizemod
80, // threshold,
false, // unmerged
useBgColor);
applicator.applyVisitor(visitor,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
applicator.end();
}
void KisClearActionFactory::run(KisViewManager *view)
{
// XXX: "Add saving of XML data for Clear action"
view->canvasBase()->toolProxy()->deleteSelection();
}
void KisImageResizeToSelectionActionFactory::run(KisViewManager *view)
{
// XXX: "Add saving of XML data for Image Resize To Selection action"
KisSelectionSP selection = view->selection();
if (!selection) return;
view->image()->cropImage(selection->selectedExactRect());
}
void KisCutCopyActionFactory::run(bool willCut, bool makeSharpClip, KisViewManager *view)
{
KisImageSP image = view->image();
if (!image) return;
bool haveShapesSelected = view->selectionManager()->haveShapesSelected();
if (haveShapesSelected) {
// XXX: "Add saving of XML data for Cut/Copy of shapes"
KisImageBarrierLocker locker(image);
if (willCut) {
view->canvasBase()->toolProxy()->cut();
} else {
view->canvasBase()->toolProxy()->copy();
}
} else {
KisNodeSP node = view->activeNode();
if (!node) return;
KisSelectionSP selection = view->selection();
if (selection.isNull()) return;
{
KisImageBarrierLocker locker(image);
KisPaintDeviceSP dev = node->paintDevice();
if (!dev) {
dev = node->projection();
}
if (!dev) {
view->showFloatingMessage(
i18nc("floating message when cannot copy from a node",
"Cannot copy pixels from this type of layer "),
QIcon(), 3000, KisFloatingMessage::Medium);
return;
}
if (dev->exactBounds().isEmpty()) {
view->showFloatingMessage(
i18nc("floating message when copying empty selection",
"Selection is empty: no pixels were copied "),
QIcon(), 3000, KisFloatingMessage::Medium);
return;
}
ActionHelper::copyFromDevice(view, dev, makeSharpClip);
}
if (willCut) {
KUndo2Command *command = 0;
- if (willCut && node->hasEditablePaintDevice()) {
+ if (node->hasEditablePaintDevice()) {
struct ClearSelection : public KisTransactionBasedCommand {
ClearSelection(KisNodeSP node, KisSelectionSP sel)
: m_node(node), m_sel(sel) {}
KisNodeSP m_node;
KisSelectionSP m_sel;
KUndo2Command* paint() override {
KisSelectionSP cutSelection = m_sel;
// Shrinking the cutting area was previously used
// for getting seamless cut-paste. Now we use makeSharpClip
// instead.
// QRect originalRect = cutSelection->selectedExactRect();
// static const int preciseSelectionThreshold = 16;
//
// if (originalRect.width() > preciseSelectionThreshold ||
// originalRect.height() > preciseSelectionThreshold) {
// cutSelection = new KisSelection(*m_sel);
// delete cutSelection->flatten();
//
// KisSelectionFilter* filter = new KisShrinkSelectionFilter(1, 1, false);
//
// QRect processingRect = filter->changeRect(originalRect);
// filter->process(cutSelection->pixelSelection(), processingRect);
// }
KisTransaction transaction(m_node->paintDevice());
m_node->paintDevice()->clearSelection(cutSelection);
m_node->setDirty(cutSelection->selectedRect());
return transaction.endAndTake();
}
};
command = new ClearSelection(node, selection);
}
KUndo2MagicString actionName = willCut ?
kundo2_i18n("Cut") :
kundo2_i18n("Copy");
KisProcessingApplicator *ap = beginAction(view, actionName);
if (command) {
ap->applyCommand(command,
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
KisOperationConfiguration config(id());
config.setProperty("will-cut", willCut);
endAction(ap, config.toXML());
}
}
}
void KisCopyMergedActionFactory::run(KisViewManager *view)
{
KisImageWSP image = view->image();
if (!image) return;
if (!view->blockUntilOperationsFinished(image)) return;
image->barrierLock();
KisPaintDeviceSP dev = image->root()->projection();
ActionHelper::copyFromDevice(view, dev);
image->unlock();
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Copy Merged"));
endAction(ap, KisOperationConfiguration(id()).toXML());
}
-void KisPasteActionFactory::run(KisViewManager *view)
-{
- KisImageWSP image = view->image();
- if (!image) return;
-
- KisPaintDeviceSP clip = KisClipboard::instance()->clip(image->bounds(), true);
-
- if (clip) {
- KisImportCatcher::adaptClipToImageColorSpace(clip, image);
- KisPaintLayer *newLayer = new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"), OPACITY_OPAQUE_U8, clip);
- KisNodeSP aboveNode = view->activeLayer();
- KisNodeSP parentNode = aboveNode ? aboveNode->parent() : image->root();
-
- KUndo2Command *cmd = new KisImageLayerAddCommand(image, newLayer, parentNode, aboveNode);
- KisProcessingApplicator *ap = beginAction(view, cmd->text());
- ap->applyCommand(cmd, KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL);
- endAction(ap, KisOperationConfiguration(id()).toXML());
- } else {
- // XXX: "Add saving of XML data for Paste of shapes"
- view->canvasBase()->toolProxy()->paste();
- }
-}
-
void KisPasteNewActionFactory::run(KisViewManager *viewManager)
{
Q_UNUSED(viewManager);
KisPaintDeviceSP clip = KisClipboard::instance()->clip(QRect(), true);
if (!clip) return;
QRect rect = clip->exactBounds();
if (rect.isEmpty()) return;
KisDocument *doc = KisPart::instance()->createDocument();
KisImageSP image = new KisImage(doc->createUndoStore(),
rect.width(),
rect.height(),
clip->colorSpace(),
i18n("Pasted"));
KisPaintLayerSP layer =
new KisPaintLayer(image.data(), image->nextLayerName() + i18n("(pasted)"),
OPACITY_OPAQUE_U8, clip->colorSpace());
KisPainter::copyAreaOptimized(QPoint(), clip, layer->paintDevice(), rect);
image->addNode(layer.data(), image->rootLayer());
doc->setCurrentImage(image);
KisPart::instance()->addDocument(doc);
KisMainWindow *win = viewManager->mainWindow();
win->addViewAndNotifyLoadingCompleted(doc);
}
-void KisInvertSelectionOperaton::runFromXML(KisViewManager* view, const KisOperationConfiguration& config)
+void KisInvertSelectionOperation::runFromXML(KisViewManager* view, const KisOperationConfiguration& config)
{
KisSelectionFilter* filter = new KisInvertSelectionFilter();
runFilter(filter, view, config);
}
void KisSelectionToVectorActionFactory::run(KisViewManager *view)
{
KisSelectionSP selection = view->selection();
if (selection->hasShapeSelection() ||
!selection->outlineCacheValid()) {
return;
}
QPainterPath selectionOutline = selection->outlineCache();
QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform();
KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline));
shape->setShapeId(KoPathShapeId);
/**
* Mark a shape that it belongs to a shape selection
*/
if(!shape->userData()) {
shape->setUserData(new KisShapeSelectionMarker);
}
KisProcessingApplicator *ap = beginAction(view, kundo2_i18n("Convert to Vector Selection"));
ap->applyCommand(view->canvasBase()->shapeController()->addShape(shape),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::EXCLUSIVE);
endAction(ap, KisOperationConfiguration(id()).toXML());
}
-
-class KisShapeSelectionPaste : public KoOdfPaste
-{
-public:
- KisShapeSelectionPaste(KisViewManager* view) : m_view(view)
- {
- }
-
- ~KisShapeSelectionPaste() override {
- }
-
- bool process(const KoXmlElement & body, KoOdfReadStore & odfStore) override {
- KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
- KoShapeLoadingContext context(loadingContext, m_view->canvasBase()->shapeController()->resourceManager());
- KoXmlElement child;
-
- QList<KoShape*> shapes;
- forEachElement(child, body) {
- KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context);
- if (shape) {
- shapes.append(shape);
- }
- }
- if (!shapes.isEmpty()) {
- KisSelectionToolHelper helper(m_view->canvasBase(), kundo2_i18n("Convert shapes to vector selection"));
- helper.addSelectionShapes(shapes);
- }
- return true;
- }
-private:
- KisViewManager* m_view;
-};
-
void KisShapesToVectorSelectionActionFactory::run(KisViewManager* view)
{
- QList<KoShape*> shapes = view->canvasBase()->shapeManager()->selection()->selectedShapes();
-
- KoShapeOdfSaveHelper saveHelper(shapes);
- KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
- QMimeData* mimeData = drag.mimeData();
+ const QList<KoShape*> originalShapes = view->canvasBase()->shapeManager()->selection()->selectedShapes();
- Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text)));
+ QList<KoShape*> clonedShapes;
+ Q_FOREACH (KoShape *shape, originalShapes) {
+ clonedShapes << shape->cloneShape();
+ }
- KisShapeSelectionPaste paste(view);
- paste.paste(KoOdf::Text, mimeData);
+ KisSelectionToolHelper helper(view->canvasBase(), kundo2_i18n("Convert shapes to vector selection"));
+ helper.addSelectionShapes(clonedShapes);
}
void KisSelectionToShapeActionFactory::run(KisViewManager *view)
{
KisSelectionSP selection = view->selection();
if (!selection->outlineCacheValid()) {
return;
}
QPainterPath selectionOutline = selection->outlineCache();
QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform();
KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(selectionOutline));
shape->setShapeId(KoPathShapeId);
KoColor fgColor = view->canvasBase()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value<KoColor>();
- KoShapeStroke* border = new KoShapeStroke(1.0, fgColor.toQColor());
+ KoShapeStrokeSP border(new KoShapeStroke(1.0, fgColor.toQColor()));
shape->setStroke(border);
view->document()->shapeController()->addShape(shape);
}
void KisStrokeSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params)
{
KisImageWSP image = view->image();
if (!image) {
return;
}
KisSelectionSP selection = view->selection();
if (!selection) {
return;
}
int size = params.lineSize;
KisPixelSelectionSP pixelSelection = selection->projection();
if (!pixelSelection->outlineCacheValid()) {
pixelSelection->recalculateOutlineCache();
}
QPainterPath outline = pixelSelection->outlineCache();
QColor color = params.color.toQColor();
KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0) {
KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager();
KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush;
KisPainter::FillStyle fillStyle = params.fillStyle();
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"),
image,
currentNode,
rManager ,
strokeStyle,
fillStyle);
helper.setFGColorOverride(params.color);
helper.setSelectionOverride(0);
QPen pen(Qt::red, size);
pen.setJoinStyle(Qt::RoundJoin);
if (fillStyle != KisPainter::FillStyleNone) {
helper.paintPainterPathQPenFill(outline, pen, params.fillColor);
}
else {
helper.paintPainterPathQPen(outline, pen, params.fillColor);
}
}
else {
QTransform transform = view->canvasBase()->coordinatesConverter()->imageToDocumentTransform();
KoShape *shape = KoPathShape::createShapeFromPainterPath(transform.map(outline));
shape->setShapeId(KoPathShapeId);
- KoShapeStroke* border = new KoShapeStroke(size, color);
+ KoShapeStrokeSP border(new KoShapeStroke(size, color));
shape->setStroke(border);
view->document()->shapeController()->addShape(shape);
}
image->setModified();
}
void KisStrokeBrushSelectionActionFactory::run(KisViewManager *view, StrokeSelectionOptions params)
{
KisImageWSP image = view->image();
if (!image) {
return;
}
KisSelectionSP selection = view->selection();
if (!selection) {
return;
}
KisPixelSelectionSP pixelSelection = selection->projection();
if (!pixelSelection->outlineCacheValid()) {
pixelSelection->recalculateOutlineCache();
}
KisNodeSP currentNode = view->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
if (!currentNode->inherits("KisShapeLayer") && currentNode->childCount() == 0)
{
KoCanvasResourceManager * rManager = view->resourceProvider()->resourceManager();
QPainterPath outline = pixelSelection->outlineCache();
KisPainter::StrokeStyle strokeStyle = KisPainter::StrokeStyleBrush;
KisPainter::FillStyle fillStyle = KisPainter::FillStyleNone;
KoColor color = params.color;
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"),
image,
currentNode,
rManager ,
strokeStyle,
fillStyle);
helper.setFGColorOverride(color);
helper.setSelectionOverride(0);
helper.paintPainterPath(outline);
image->setModified();
}
}
diff --git a/libs/ui/actions/kis_selection_action_factories.h b/libs/ui/actions/kis_selection_action_factories.h
index d5a285805d..8f2c830054 100644
--- a/libs/ui/actions/kis_selection_action_factories.h
+++ b/libs/ui/actions/kis_selection_action_factories.h
@@ -1,134 +1,129 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SELECTION_ACTION_FACTORIES_H
#define __KIS_SELECTION_ACTION_FACTORIES_H
#include "operations/kis_operation.h"
#include "operations/kis_operation_configuration.h"
#include "operations/kis_filter_selection_operation.h"
#include "dialogs/kis_dlg_stroke_selection_properties.h"
class KRITAUI_EXPORT KisNoParameterActionFactory : public KisOperation
{
public:
KisNoParameterActionFactory(const QString &id) : KisOperation(id) {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) {
Q_UNUSED(config);
run(view);
}
virtual void run(KisViewManager *view) = 0;
};
struct KRITAUI_EXPORT KisSelectAllActionFactory : public KisNoParameterActionFactory {
KisSelectAllActionFactory() : KisNoParameterActionFactory("select-all-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisDeselectActionFactory : public KisNoParameterActionFactory {
KisDeselectActionFactory() : KisNoParameterActionFactory("deselect-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisReselectActionFactory : public KisNoParameterActionFactory {
KisReselectActionFactory() : KisNoParameterActionFactory("reselect-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisFillActionFactory : public KisOperation
{
KisFillActionFactory() : KisOperation("fill-ui-action") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) {
run(config.getString("fill-source", "fg"), view);
}
/**
* \p fillColor may be one of three variants:
* - "fg" --- foreground color
* - "bg" --- background color
* - "pattern" --- current pattern
*/
void run(const QString &fillSource, KisViewManager *view);
};
struct KRITAUI_EXPORT KisClearActionFactory : public KisNoParameterActionFactory {
KisClearActionFactory() : KisNoParameterActionFactory("clear-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisImageResizeToSelectionActionFactory : public KisNoParameterActionFactory {
KisImageResizeToSelectionActionFactory() : KisNoParameterActionFactory("resize-to-selection-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisCutCopyActionFactory : public KisOperation {
KisCutCopyActionFactory() : KisOperation("cut-copy-ui-action") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config) {
run(config.getBool("will-cut", false), config.getBool("use-sharp-clip", false), view);
}
void run(bool willCut, bool makeSharpClip, KisViewManager *view);
};
struct KRITAUI_EXPORT KisCopyMergedActionFactory : public KisNoParameterActionFactory {
KisCopyMergedActionFactory() : KisNoParameterActionFactory("copy-merged-ui-action") {}
void run(KisViewManager *view);
};
-struct KRITAUI_EXPORT KisPasteActionFactory : public KisNoParameterActionFactory {
- KisPasteActionFactory() : KisNoParameterActionFactory("paste-ui-action") {}
- void run(KisViewManager *view);
-};
-
struct KRITAUI_EXPORT KisPasteNewActionFactory : public KisNoParameterActionFactory {
KisPasteNewActionFactory() : KisNoParameterActionFactory("paste-new-ui-action") {}
void run(KisViewManager *view);
};
-struct KisInvertSelectionOperaton : public KisFilterSelectionOperation {
- KisInvertSelectionOperaton() : KisFilterSelectionOperation("invertselection") {}
+struct KisInvertSelectionOperation : public KisFilterSelectionOperation {
+ KisInvertSelectionOperation() : KisFilterSelectionOperation("invertselection") {}
void runFromXML(KisViewManager *view, const KisOperationConfiguration &config);
};
struct KRITAUI_EXPORT KisSelectionToVectorActionFactory : public KisNoParameterActionFactory {
KisSelectionToVectorActionFactory() : KisNoParameterActionFactory("paste-new-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisShapesToVectorSelectionActionFactory : public KisNoParameterActionFactory {
KisShapesToVectorSelectionActionFactory() : KisNoParameterActionFactory("paste-new-ui-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisSelectionToShapeActionFactory : public KisNoParameterActionFactory {
KisSelectionToShapeActionFactory() : KisNoParameterActionFactory("selection-to-shape-action") {}
void run(KisViewManager *view);
};
struct KRITAUI_EXPORT KisStrokeSelectionActionFactory : public KisOperation {
KisStrokeSelectionActionFactory() : KisOperation("selection-to-shape-action") {}
void run(KisViewManager *view, StrokeSelectionOptions params);
};
struct KRITAUI_EXPORT KisStrokeBrushSelectionActionFactory : public KisOperation {
KisStrokeBrushSelectionActionFactory() : KisOperation("selection-to-shape-action") {}
void run(KisViewManager *view, StrokeSelectionOptions params);
};
#endif /* __KIS_SELECTION_ACTION_FACTORIES_H */
diff --git a/libs/ui/canvas/KisSnapPointStrategy.cpp b/libs/ui/canvas/KisSnapPointStrategy.cpp
new file mode 100644
index 0000000000..712ebbedc1
--- /dev/null
+++ b/libs/ui/canvas/KisSnapPointStrategy.cpp
@@ -0,0 +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.
+ */
+
+#include "KisSnapPointStrategy.h"
+
+#include <QPainterPath>
+#include "kis_global.h"
+
+struct KisSnapPointStrategy::Private
+{
+ QList<QPointF> points;
+};
+
+KisSnapPointStrategy::KisSnapPointStrategy(KoSnapGuide::Strategy type)
+ : KoSnapStrategy(type),
+ m_d(new Private)
+{
+}
+
+KisSnapPointStrategy::~KisSnapPointStrategy()
+{
+}
+
+bool KisSnapPointStrategy::snap(const QPointF &mousePosition, KoSnapProxy *proxy, qreal maxSnapDistance)
+{
+ Q_UNUSED(proxy);
+
+ QPointF snappedPoint = mousePosition;
+ qreal minDistance = std::numeric_limits<qreal>::max();
+
+ Q_FOREACH (const QPointF &pt, m_d->points) {
+ const qreal dist = kisDistance(mousePosition, pt);
+
+ if (dist < maxSnapDistance && dist < minDistance) {
+ minDistance = dist;
+ snappedPoint = pt;
+ }
+ }
+
+ setSnappedPosition(snappedPoint);
+ return minDistance < std::numeric_limits<qreal>::max();
+}
+
+QPainterPath KisSnapPointStrategy::decoration(const KoViewConverter &converter) const
+{
+ Q_UNUSED(converter);
+ return QPainterPath();
+}
+
+void KisSnapPointStrategy::addPoint(const QPointF &pt)
+{
+ m_d->points << pt;
+}
+
diff --git a/libs/ui/canvas/KisSnapPointStrategy.h b/libs/ui/canvas/KisSnapPointStrategy.h
new file mode 100644
index 0000000000..506f1da047
--- /dev/null
+++ b/libs/ui/canvas/KisSnapPointStrategy.h
@@ -0,0 +1,50 @@
+/*
+ * 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 KISSNAPPOINTSTRATEGY_H
+#define KISSNAPPOINTSTRATEGY_H
+
+#include <QScopedPointer>
+
+#include "KoSnapStrategy.h"
+#include "kritaui_export.h"
+
+/**
+ * The KisSnapPointStrategy class is a custom strategy that allows snapping to
+ * arbitrary points on canvas, not linked to any real objects. It can be used,
+ * for example, for snapping to the *previous position* of the handle, while it
+ * is dragging by the user.
+ */
+
+class KRITAUI_EXPORT KisSnapPointStrategy : public KoSnapStrategy
+{
+public:
+ KisSnapPointStrategy(KoSnapGuide::Strategy type = KoSnapGuide::CustomSnapping);
+ ~KisSnapPointStrategy();
+
+ bool snap(const QPointF &mousePosition, KoSnapProxy * proxy, qreal maxSnapDistance) override;
+ QPainterPath decoration(const KoViewConverter &converter) const override;
+
+ void addPoint(const QPointF &pt);
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISSNAPPOINTSTRATEGY_H
diff --git a/libs/ui/canvas/kis_animation_player.cpp b/libs/ui/canvas/kis_animation_player.cpp
index 5641c603de..a944cf6347 100644
--- a/libs/ui/canvas/kis_animation_player.cpp
+++ b/libs/ui/canvas/kis_animation_player.cpp
@@ -1,529 +1,541 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_animation_player.h"
#include <QElapsedTimer>
#include <QTimer>
#include <QtMath>
//#define PLAYER_DEBUG_FRAMERATE
#include "kis_global.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_image.h"
#include "kis_canvas2.h"
#include "kis_animation_frame_cache.h"
#include "kis_signal_auto_connection.h"
#include "kis_image_animation_interface.h"
#include "kis_time_range.h"
#include "kis_signal_compressor.h"
#include <KisDocument.h>
#include <QFileInfo>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/rolling_mean.hpp>
#include "KisSyncedAudioPlayback.h"
#include "kis_signal_compressor_with_param.h"
#include "KisViewManager.h"
#include "kis_icon_utils.h"
+#include "KisPart.h"
+#include "dialogs/KisAnimationCacheUpdateProgressDialog.h"
+
using namespace boost::accumulators;
typedef accumulator_set<qreal, stats<tag::rolling_mean> > FpsAccumulator;
struct KisAnimationPlayer::Private
{
public:
Private(KisAnimationPlayer *_q)
: q(_q),
realFpsAccumulator(tag::rolling_window::window_size = 24),
droppedFpsAccumulator(tag::rolling_window::window_size = 24),
droppedFramesPortion(tag::rolling_window::window_size = 24),
dropFramesMode(true),
nextFrameExpectedTime(0),
expectedInterval(0),
expectedFrame(0),
lastTimerInterval(0),
lastPaintedFrame(0),
playbackStatisticsCompressor(1000, KisSignalCompressor::FIRST_INACTIVE),
stopAudioOnScrubbingCompressor(100, KisSignalCompressor::POSTPONE),
audioOffsetTolerance(-1)
{}
KisAnimationPlayer *q;
bool useFastFrameUpload;
bool playing;
QTimer *timer;
int initialFrame;
int firstFrame;
int lastFrame;
qreal playbackSpeed;
KisCanvas2 *canvas;
KisSignalAutoConnectionsStore cancelStrokeConnections;
QElapsedTimer realFpsTimer;
FpsAccumulator realFpsAccumulator;
FpsAccumulator droppedFpsAccumulator;
FpsAccumulator droppedFramesPortion;
bool dropFramesMode;
QElapsedTimer playbackTime;
int nextFrameExpectedTime;
int expectedInterval;
int expectedFrame;
int lastTimerInterval;
int lastPaintedFrame;
KisSignalCompressor playbackStatisticsCompressor;
QScopedPointer<KisSyncedAudioPlayback> syncedAudio;
QScopedPointer<KisSignalCompressorWithParam<int> > audioSyncScrubbingCompressor;
KisSignalCompressor stopAudioOnScrubbingCompressor;
int audioOffsetTolerance;
void stopImpl(bool doUpdates);
int incFrame(int frame, int inc) {
frame += inc;
if (frame > lastFrame) {
frame = firstFrame + frame - lastFrame - 1;
}
return frame;
}
qint64 frameToMSec(int value, int fps) {
return qreal(value) / fps * 1000.0;
}
int msecToFrame(qint64 value, int fps) {
return qreal(value) * fps / 1000.0;
}
};
KisAnimationPlayer::KisAnimationPlayer(KisCanvas2 *canvas)
: QObject(canvas)
, m_d(new Private(this))
{
m_d->useFastFrameUpload = false;
m_d->playing = false;
m_d->canvas = canvas;
m_d->playbackSpeed = 1.0;
m_d->timer = new QTimer(this);
connect(m_d->timer, SIGNAL(timeout()), this, SLOT(slotUpdate()));
m_d->timer->setSingleShot(true);
connect(KisConfigNotifier::instance(),
SIGNAL(dropFramesModeChanged()),
SLOT(slotUpdateDropFramesMode()));
slotUpdateDropFramesMode();
connect(&m_d->playbackStatisticsCompressor, SIGNAL(timeout()),
this, SIGNAL(sigPlaybackStatisticsUpdated()));
using namespace std::placeholders;
std::function<void (int)> callback(
std::bind(&KisAnimationPlayer::slotSyncScrubbingAudio, this, _1));
const int defaultScrubbingUdpatesDelay = 40; /* 40 ms == 25 fps */
m_d->audioSyncScrubbingCompressor.reset(
new KisSignalCompressorWithParam<int>(defaultScrubbingUdpatesDelay, callback, KisSignalCompressor::FIRST_ACTIVE));
m_d->stopAudioOnScrubbingCompressor.setDelay(defaultScrubbingUdpatesDelay);
connect(&m_d->stopAudioOnScrubbingCompressor, SIGNAL(timeout()), SLOT(slotTryStopScrubbingAudio()));
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()), SLOT(slotUpdateAudioChunkLength()));
slotUpdateAudioChunkLength();
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioChannelChanged()), SLOT(slotAudioChannelChanged()));
connect(m_d->canvas->image()->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SLOT(slotAudioVolumeChanged()));
slotAudioChannelChanged();
}
KisAnimationPlayer::~KisAnimationPlayer()
{}
void KisAnimationPlayer::slotUpdateDropFramesMode()
{
KisConfig cfg;
m_d->dropFramesMode = cfg.animationDropFrames();
}
void KisAnimationPlayer::slotSyncScrubbingAudio(int msecTime)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
if (!m_d->syncedAudio->isPlaying()) {
m_d->syncedAudio->play(msecTime);
} else {
m_d->syncedAudio->syncWithVideo(msecTime);
}
if (!isPlaying()) {
m_d->stopAudioOnScrubbingCompressor.start();
}
}
void KisAnimationPlayer::slotTryStopScrubbingAudio()
{
KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->syncedAudio);
if (m_d->syncedAudio && !isPlaying()) {
m_d->syncedAudio->stop();
}
}
void KisAnimationPlayer::slotAudioChannelChanged()
{
KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface();
QString fileName = interface->audioChannelFileName();
QFileInfo info(fileName);
if (info.exists() && !interface->isAudioMuted()) {
m_d->syncedAudio.reset(new KisSyncedAudioPlayback(info.absoluteFilePath()));
m_d->syncedAudio->setVolume(interface->audioVolume());
m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance);
connect(m_d->syncedAudio.data(), SIGNAL(error(const QString &, const QString &)), SLOT(slotOnAudioError(const QString &, const QString &)));
} else {
m_d->syncedAudio.reset();
}
}
void KisAnimationPlayer::slotAudioVolumeChanged()
{
KisImageAnimationInterface *interface = m_d->canvas->image()->animationInterface();
if (m_d->syncedAudio) {
m_d->syncedAudio->setVolume(interface->audioVolume());
}
}
void KisAnimationPlayer::slotOnAudioError(const QString &fileName, const QString &message)
{
QString errorMessage(i18nc("floating on-canvas message", "Cannot open audio: \"%1\"\nError: %2", fileName, message));
m_d->canvas->viewManager()->showFloatingMessage(errorMessage, KisIconUtils::loadIcon("dialog-warning"));
}
void KisAnimationPlayer::connectCancelSignals()
{
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigUndoDuringStrokeRequested()),
this, SLOT(slotCancelPlayback()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigStrokeCancellationRequested()),
this, SLOT(slotCancelPlayback()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image().data(), SIGNAL(sigStrokeEndRequested()),
this, SLOT(slotCancelPlaybackSafe()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigFramerateChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigFullClipRangeChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
m_d->cancelStrokeConnections.addConnection(
m_d->canvas->image()->animationInterface(), SIGNAL(sigPlaybackRangeChanged()),
this, SLOT(slotUpdatePlaybackTimer()));
}
void KisAnimationPlayer::disconnectCancelSignals()
{
m_d->cancelStrokeConnections.clear();
}
void KisAnimationPlayer::slotUpdateAudioChunkLength()
{
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const int animationFramePeriod = qMax(1, 1000 / animation->framerate());
KisConfig cfg;
int scrubbingAudioUdpatesDelay = cfg.scrubbingAudioUpdatesDelay();
if (scrubbingAudioUdpatesDelay < 0) {
scrubbingAudioUdpatesDelay = qMax(1, animationFramePeriod);
}
m_d->audioSyncScrubbingCompressor->setDelay(scrubbingAudioUdpatesDelay);
m_d->stopAudioOnScrubbingCompressor.setDelay(scrubbingAudioUdpatesDelay);
m_d->audioOffsetTolerance = cfg.audioOffsetTolerance();
if (m_d->audioOffsetTolerance < 0) {
m_d->audioOffsetTolerance = animationFramePeriod;
}
if (m_d->syncedAudio) {
m_d->syncedAudio->setSoundOffsetTolerance(m_d->audioOffsetTolerance);
}
}
void KisAnimationPlayer::slotUpdatePlaybackTimer()
{
m_d->timer->stop();
const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
const KisTimeRange &range = animation->playbackRange();
if (!range.isValid()) return;
const int fps = animation->framerate();
m_d->initialFrame = animation->currentUITime();
m_d->firstFrame = range.start();
m_d->lastFrame = range.end();
m_d->expectedFrame = qBound(m_d->firstFrame, m_d->expectedFrame, m_d->lastFrame);
m_d->expectedInterval = qreal(1000) / fps / m_d->playbackSpeed;
m_d->lastTimerInterval = m_d->expectedInterval;
if (m_d->syncedAudio) {
m_d->syncedAudio->setSpeed(m_d->playbackSpeed);
}
m_d->timer->start(m_d->expectedInterval);
if (m_d->playbackTime.isValid()) {
m_d->playbackTime.restart();
} else {
m_d->playbackTime.start();
}
m_d->nextFrameExpectedTime = m_d->playbackTime.elapsed() + m_d->expectedInterval;
}
void KisAnimationPlayer::play()
{
+ {
+ const KisImageAnimationInterface *animation = m_d->canvas->image()->animationInterface();
+ const KisTimeRange &range = animation->playbackRange();
+ if (!range.isValid()) return;
+
+ KisAnimationCacheUpdateProgressDialog dlg(200, KisPart::instance()->currentMainwindow());
+ dlg.regenerateRange(m_d->canvas->frameCache(), range, m_d->canvas->viewManager());
+ }
+
m_d->playing = true;
slotUpdatePlaybackTimer();
m_d->expectedFrame = m_d->firstFrame;
m_d->lastPaintedFrame = m_d->firstFrame;
connectCancelSignals();
if (m_d->syncedAudio) {
KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface();
m_d->syncedAudio->play(m_d->frameToMSec(m_d->firstFrame, animationInterface->framerate()));
}
}
void KisAnimationPlayer::Private::stopImpl(bool doUpdates)
{
if (syncedAudio) {
syncedAudio->stop();
}
q->disconnectCancelSignals();
timer->stop();
playing = false;
if (doUpdates) {
KisImageAnimationInterface *animation = canvas->image()->animationInterface();
if (animation->currentUITime() == initialFrame) {
canvas->refetchDataFromImage();
} else {
animation->switchCurrentTimeAsync(initialFrame);
}
}
emit q->sigPlaybackStopped();
}
void KisAnimationPlayer::stop()
{
m_d->stopImpl(true);
}
void KisAnimationPlayer::forcedStopOnExit()
{
m_d->stopImpl(false);
}
bool KisAnimationPlayer::isPlaying()
{
return m_d->playing;
}
int KisAnimationPlayer::currentTime()
{
return m_d->lastPaintedFrame;
}
void KisAnimationPlayer::displayFrame(int time)
{
uploadFrame(time);
}
void KisAnimationPlayer::slotUpdate()
{
uploadFrame(-1);
}
void KisAnimationPlayer::uploadFrame(int frame)
{
KisImageAnimationInterface *animationInterface = m_d->canvas->image()->animationInterface();
if (frame < 0) {
const int currentTime = m_d->playbackTime.elapsed();
const int framesDiff = currentTime - m_d->nextFrameExpectedTime;
const qreal framesDiffNorm = qreal(framesDiff) / m_d->expectedInterval;
// qDebug() << ppVar(framesDiff)
// << ppVar(m_d->expectedFrame)
// << ppVar(framesDiffNorm)
// << ppVar(m_d->lastTimerInterval);
if (m_d->dropFramesMode) {
const int numDroppedFrames = qMax(0, qRound(framesDiffNorm));
frame = m_d->incFrame(m_d->expectedFrame, numDroppedFrames);
} else {
frame = m_d->expectedFrame;
}
m_d->nextFrameExpectedTime = currentTime + m_d->expectedInterval;
m_d->lastTimerInterval = qMax(0.0, m_d->lastTimerInterval - 0.5 * framesDiff);
m_d->expectedFrame = m_d->incFrame(frame, 1);
m_d->timer->start(m_d->lastTimerInterval);
m_d->playbackStatisticsCompressor.start();
}
if (m_d->syncedAudio) {
const int msecTime = m_d->frameToMSec(frame, animationInterface->framerate());
if (isPlaying()) {
slotSyncScrubbingAudio(msecTime);
} else {
m_d->audioSyncScrubbingCompressor->start(msecTime);
}
}
if (m_d->canvas->frameCache() && m_d->canvas->frameCache()->uploadFrame(frame)) {
m_d->canvas->updateCanvas();
m_d->useFastFrameUpload = true;
emit sigFrameChanged();
} else {
m_d->useFastFrameUpload = false;
m_d->canvas->image()->barrierLock(true);
m_d->canvas->image()->unlock();
// no OpenGL cache or the frame just not cached yet
animationInterface->switchCurrentTimeAsync(frame);
emit sigFrameChanged();
}
if (!m_d->realFpsTimer.isValid()) {
m_d->realFpsTimer.start();
} else {
const int elapsed = m_d->realFpsTimer.restart();
m_d->realFpsAccumulator(elapsed);
int numFrames = frame - m_d->lastPaintedFrame;
if (numFrames < 0) {
numFrames += m_d->lastFrame - m_d->firstFrame + 1;
}
m_d->droppedFramesPortion(qreal(int(numFrames != 1)));
if (numFrames > 0) {
m_d->droppedFpsAccumulator(qreal(elapsed) / numFrames);
}
#ifdef PLAYER_DEBUG_FRAMERATE
qDebug() << " RFPS:" << 1000.0 / rolling_mean(m_d->realFpsAccumulator)
<< "DFPS:" << 1000.0 / rolling_mean(m_d->droppedFpsAccumulator) << ppVar(numFrames);
#endif /* PLAYER_DEBUG_FRAMERATE */
}
m_d->lastPaintedFrame = frame;
}
qreal KisAnimationPlayer::effectiveFps() const
{
return 1000.0 / rolling_mean(m_d->droppedFpsAccumulator);
}
qreal KisAnimationPlayer::realFps() const
{
return 1000.0 / rolling_mean(m_d->realFpsAccumulator);
}
qreal KisAnimationPlayer::framesDroppedPortion() const
{
return rolling_mean(m_d->droppedFramesPortion);
}
void KisAnimationPlayer::slotCancelPlayback()
{
stop();
}
void KisAnimationPlayer::slotCancelPlaybackSafe()
{
/**
* If there is no openGL support, then we have no (!) cache at
* all. Therefore we should regenerate frame on every time switch,
* which, yeah, can be very slow. What is more important, when
* regenerating a frame animation interface will emit a
* sigStrokeEndRequested() signal and we should ignore it. That is
* not an ideal solution, because the user will be able to paint
* on random frames while playing, but it lets users with faulty
* GPUs see at least some preview of their animation.
*/
if (m_d->useFastFrameUpload) {
stop();
}
}
qreal KisAnimationPlayer::playbackSpeed()
{
return m_d->playbackSpeed;
}
void KisAnimationPlayer::slotUpdatePlaybackSpeed(double value)
{
m_d->playbackSpeed = value;
if (m_d->playing) {
slotUpdatePlaybackTimer();
}
}
diff --git a/libs/ui/canvas/kis_canvas2.cpp b/libs/ui/canvas/kis_canvas2.cpp
index ca63168097..71ef214213 100644
--- a/libs/ui/canvas/kis_canvas2.cpp
+++ b/libs/ui/canvas/kis_canvas2.cpp
@@ -1,980 +1,1014 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) Lukáš Tvrdý <lukast.dev@gmail.com>, (C) 2010
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA..
*/
#include "kis_canvas2.h"
#include <QApplication>
#include <QWidget>
#include <QVBoxLayout>
#include <QTime>
#include <QLabel>
#include <QMouseEvent>
#include <QDesktopWidget>
#include <kis_debug.h>
#include <KoUnit.h>
#include <KoShapeManager.h>
+#include <KisSelectedShapesProxy.h>
#include <KoColorProfile.h>
#include <KoCanvasControllerWidget.h>
#include <KisDocument.h>
#include <KoSelection.h>
#include <KoShapeController.h>
#include <kis_lod_transform.h>
#include "kis_tool_proxy.h"
#include "kis_coordinates_converter.h"
#include "kis_prescaled_projection.h"
#include "kis_image.h"
#include "kis_image_barrier_locker.h"
+#include "kis_undo_adapter.h"
#include "KisDocument.h"
#include "flake/kis_shape_layer.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_abstract_canvas_widget.h"
#include "kis_qpainter_canvas.h"
#include "kis_group_layer.h"
#include "flake/kis_shape_controller.h"
#include "kis_node_manager.h"
#include "kis_selection.h"
#include "kis_selection_component.h"
#include "flake/kis_shape_selection.h"
#include "kis_image_config.h"
#include "kis_infinity_manager.h"
#include "kis_signal_compressor.h"
#include "kis_display_color_converter.h"
#include "kis_exposure_gamma_correction_interface.h"
#include "KisView.h"
#include "kis_canvas_controller.h"
#include "kis_grid_config.h"
#include "kis_animation_player.h"
#include "kis_animation_frame_cache.h"
#include "opengl/kis_opengl_canvas2.h"
#include "opengl/kis_opengl.h"
#include "kis_fps_decoration.h"
#include "KoColorConversionTransformation.h"
#include "KisProofingConfiguration.h"
#include <kis_favorite_resource_manager.h>
#include <kis_popup_palette.h>
#include "input/kis_input_manager.h"
#include "kis_painting_assistants_decoration.h"
#include "kis_canvas_updates_compressor.h"
#include "KoZoomController.h"
class Q_DECL_HIDDEN KisCanvas2::KisCanvas2Private
{
public:
KisCanvas2Private(KoCanvasBase *parent, KisCoordinatesConverter* coordConverter, QPointer<KisView> view, KoCanvasResourceManager* resourceManager)
: coordinatesConverter(coordConverter)
, view(view)
, shapeManager(parent)
+ , selectedShapesProxy(&shapeManager)
, toolProxy(parent)
, displayColorConverter(resourceManager, view)
{
}
KisCoordinatesConverter *coordinatesConverter;
QPointer<KisView>view;
KisAbstractCanvasWidget *canvasWidget = 0;
KoShapeManager shapeManager;
+ KisSelectedShapesProxy selectedShapesProxy;
bool currentCanvasIsOpenGL;
int openGLFilterMode;
KisToolProxy toolProxy;
KisPrescaledProjectionSP prescaledProjection;
bool vastScrolling;
KisSignalCompressor updateSignalCompressor;
QRect savedUpdateRect;
QBitArray channelFlags;
KisProofingConfigurationSP proofingConfig;
bool softProofing = false;
bool gamutCheck = false;
bool proofingConfigUpdated = false;
KisPopupPalette *popupPalette = 0;
KisDisplayColorConverter displayColorConverter;
KisCanvasUpdatesCompressor projectionUpdatesCompressor;
KisAnimationPlayer *animationPlayer;
KisAnimationFrameCacheSP frameCache;
bool lodAllowedInCanvas;
bool bootstrapLodBlocked;
+ QPointer<KoShapeManager> currentlyActiveShapeManager;
bool effectiveLodAllowedInCanvas() {
return lodAllowedInCanvas && !bootstrapLodBlocked;
}
};
+namespace {
+KoShapeManager* fetchShapeManagerFromNode(KisNodeSP node)
+{
+ KoShapeManager *shapeManager = 0;
+
+ KisLayer *layer = dynamic_cast<KisLayer*>(node.data());
+
+ if (layer) {
+ KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(layer);
+ if (shapeLayer) {
+ shapeManager = shapeLayer->shapeManager();
+
+ } else {
+ KisSelectionSP selection = layer->selection();
+ if (selection && selection->hasShapeSelection()) {
+ KisShapeSelection *shapeSelection = dynamic_cast<KisShapeSelection*>(selection->shapeSelection());
+ KIS_ASSERT_RECOVER_RETURN_VALUE(shapeSelection, 0);
+
+ shapeManager = shapeSelection->shapeManager();
+ }
+ }
+ }
+
+ return shapeManager;
+}
+}
+
KisCanvas2::KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc)
: KoCanvasBase(sc, resourceManager)
, m_d(new KisCanvas2Private(this, coordConverter, view, resourceManager))
{
/**
* While loading LoD should be blocked. Only when GUI has finished
* loading and zoom level settled down, LoD is given a green
* light.
*/
m_d->bootstrapLodBlocked = true;
connect(view->mainWindow(), SIGNAL(guiLoadingFinished()), SLOT(bootstrapFinished()));
m_d->updateSignalCompressor.setDelay(10);
m_d->updateSignalCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
-
-
}
void KisCanvas2::setup()
{
// a bit of duplication from slotConfigChanged()
KisConfig cfg;
m_d->vastScrolling = cfg.vastScrolling();
m_d->lodAllowedInCanvas = cfg.levelOfDetailEnabled();
createCanvas(cfg.useOpenGL());
setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
m_d->animationPlayer = new KisAnimationPlayer(this);
connect(m_d->view->canvasController()->proxyObject, SIGNAL(moveDocumentOffset(QPoint)), SLOT(documentOffsetMoved(QPoint)));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
/**
* We switch the shape manager every time vector layer or
* shape selection is activated. Flake does not expect this
* and connects all the signals of the global shape manager
* to the clients in the constructor. To workaround this we
* forward the signals of local shape managers stored in the
* vector layers to the signals of global shape manager. So the
* sequence of signal deliveries is the following:
*
* shapeLayer.m_d.canvas.m_shapeManager.selection() ->
* shapeLayer ->
* shapeController ->
* globalShapeManager.selection()
*/
KisShapeController *kritaShapeController = static_cast<KisShapeController*>(shapeController()->documentBase());
connect(kritaShapeController, SIGNAL(selectionChanged()),
this, SLOT(slotSelectionChanged()));
connect(kritaShapeController, SIGNAL(selectionContentChanged()),
globalShapeManager(), SIGNAL(selectionContentChanged()));
connect(kritaShapeController, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
globalShapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), SLOT(slotDoCanvasUpdate()));
}
KisCanvas2::~KisCanvas2()
{
if (m_d->animationPlayer->isPlaying()) {
m_d->animationPlayer->forcedStopOnExit();
}
delete m_d;
}
void KisCanvas2::setCanvasWidget(QWidget * widget)
{
KisAbstractCanvasWidget *tmp = dynamic_cast<KisAbstractCanvasWidget*>(widget);
Q_ASSERT_X(tmp, "setCanvasWidget", "Cannot cast the widget to a KisAbstractCanvasWidget");
if (m_d->popupPalette) {
m_d->popupPalette->setParent(widget);
}
if(m_d->canvasWidget != 0)
{
tmp->setDecorations(m_d->canvasWidget->decorations());
// Redundant check for the constructor case, see below
if(viewManager() != 0)
viewManager()->inputManager()->removeTrackedCanvas(this);
}
m_d->canvasWidget = tmp;
// Either tmp was null or we are being called by KisCanvas2 constructor that is called by KisView
// constructor, so the view manager still doesn't exists.
if(m_d->canvasWidget != 0 && viewManager() != 0)
viewManager()->inputManager()->addTrackedCanvas(this);
if (!m_d->canvasWidget->decoration(INFINITY_DECORATION_ID)) {
KisInfinityManager *manager = new KisInfinityManager(m_d->view, this);
manager->setVisible(true);
m_d->canvasWidget->addDecoration(manager);
}
widget->setAutoFillBackground(false);
widget->setAttribute(Qt::WA_OpaquePaintEvent);
widget->setMouseTracking(true);
widget->setAcceptDrops(true);
KoCanvasControllerWidget *controller = dynamic_cast<KoCanvasControllerWidget*>(canvasController());
if (controller) {
Q_ASSERT(controller->canvas() == this);
controller->changeCanvasWidget(widget);
}
}
bool KisCanvas2::canvasIsOpenGL() const
{
return m_d->currentCanvasIsOpenGL;
}
KisOpenGL::FilterMode KisCanvas2::openGLFilterMode() const
{
return KisOpenGL::FilterMode(m_d->openGLFilterMode);
}
void KisCanvas2::gridSize(QPointF *offset, QSizeF *spacing) const
{
QTransform transform = coordinatesConverter()->imageToDocumentTransform();
const QPoint intSpacing = m_d->view->document()->gridConfig().spacing();
const QPoint intOffset = m_d->view->document()->gridConfig().offset();
QPointF size = transform.map(QPointF(intSpacing));
spacing->rwidth() = size.x();
spacing->rheight() = size.y();
*offset = transform.map(QPointF(intOffset));
}
bool KisCanvas2::snapToGrid() const
{
return m_d->view->document()->gridConfig().snapToGrid();
}
qreal KisCanvas2::rotationAngle() const
{
return m_d->coordinatesConverter->rotationAngle();
}
bool KisCanvas2::xAxisMirrored() const
{
return m_d->coordinatesConverter->xAxisMirrored();
}
bool KisCanvas2::yAxisMirrored() const
{
return m_d->coordinatesConverter->yAxisMirrored();
}
void KisCanvas2::channelSelectionChanged()
{
KisImageSP image = this->image();
m_d->channelFlags = image->rootLayer()->channelFlags();
m_d->view->viewManager()->blockUntillOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->channelSelectionChanged(m_d->channelFlags);
startUpdateInPatches(image->bounds());
image->unlock();
}
void KisCanvas2::addCommand(KUndo2Command *command)
{
// This method exists to support flake-related operations
m_d->view->document()->addCommand(command);
}
KoShapeManager* KisCanvas2::shapeManager() const
{
- if (!viewManager()) return globalShapeManager();
- if (!viewManager()->nodeManager()) return globalShapeManager();
-
- KisLayerSP activeLayer = viewManager()->nodeManager()->activeLayer();
- if (activeLayer && activeLayer->isEditable()) {
- KisShapeLayer * shapeLayer = dynamic_cast<KisShapeLayer*>(activeLayer.data());
- if (shapeLayer) {
- return shapeLayer->shapeManager();
- }
- KisSelectionSP selection = activeLayer->selection();
- if (selection && !selection.isNull()) {
- if (selection->hasShapeSelection()) {
- KoShapeManager* m = dynamic_cast<KisShapeSelection*>(selection->shapeSelection())->shapeManager();
- return m;
- }
+ KisNodeSP node = m_d->view->currentNode();
+ KoShapeManager *localShapeManager = fetchShapeManagerFromNode(node);
- }
+ // sanity check for consistency of the local shape manager
+ KIS_SAFE_ASSERT_RECOVER (localShapeManager == m_d->currentlyActiveShapeManager) {
+ localShapeManager = globalShapeManager();
}
- return globalShapeManager();
+
+ return localShapeManager ? localShapeManager : globalShapeManager();
+}
+
+KoSelectedShapesProxy* KisCanvas2::selectedShapesProxy() const
+{
+ return &m_d->selectedShapesProxy;
}
-KoShapeManager * KisCanvas2::globalShapeManager() const
+KoShapeManager* KisCanvas2::globalShapeManager() const
{
return &m_d->shapeManager;
}
void KisCanvas2::updateInputMethodInfo()
{
// TODO call (the protected) QWidget::updateMicroFocus() on the proper canvas widget...
}
const KisCoordinatesConverter* KisCanvas2::coordinatesConverter() const
{
return m_d->coordinatesConverter;
}
KoViewConverter* KisCanvas2::viewConverter() const
{
return m_d->coordinatesConverter;
}
KisInputManager* KisCanvas2::globalInputManager() const
{
return m_d->view->globalInputManager();
}
QWidget* KisCanvas2::canvasWidget()
{
return m_d->canvasWidget->widget();
}
const QWidget* KisCanvas2::canvasWidget() const
{
return m_d->canvasWidget->widget();
}
KoUnit KisCanvas2::unit() const
{
KoUnit unit(KoUnit::Pixel);
KisImageWSP image = m_d->view->image();
if (image) {
if (!qFuzzyCompare(image->xRes(), image->yRes())) {
warnKrita << "WARNING: resolution of the image is anisotropic"
<< ppVar(image->xRes())
<< ppVar(image->yRes());
}
const qreal resolution = image->xRes();
unit.setFactor(resolution);
}
return unit;
}
KoToolProxy * KisCanvas2::toolProxy() const
{
return &m_d->toolProxy;
}
void KisCanvas2::createQPainterCanvas()
{
m_d->currentCanvasIsOpenGL = false;
KisQPainterCanvas * canvasWidget = new KisQPainterCanvas(this, m_d->coordinatesConverter, m_d->view);
m_d->prescaledProjection = new KisPrescaledProjection();
m_d->prescaledProjection->setCoordinatesConverter(m_d->coordinatesConverter);
m_d->prescaledProjection->setMonitorProfile(m_d->displayColorConverter.monitorProfile(),
m_d->displayColorConverter.renderingIntent(),
m_d->displayColorConverter.conversionFlags());
m_d->prescaledProjection->setDisplayFilter(m_d->displayColorConverter.displayFilter());
canvasWidget->setPrescaledProjection(m_d->prescaledProjection);
setCanvasWidget(canvasWidget);
}
void KisCanvas2::createOpenGLCanvas()
{
KisConfig cfg;
m_d->openGLFilterMode = cfg.openGLFilteringMode();
m_d->currentCanvasIsOpenGL = true;
KisOpenGLCanvas2 *canvasWidget = new KisOpenGLCanvas2(this, m_d->coordinatesConverter, 0, m_d->view->image(), &m_d->displayColorConverter);
m_d->frameCache = KisAnimationFrameCache::getFrameCache(canvasWidget->openGLImageTextures());
setCanvasWidget(canvasWidget);
if (canvasWidget->needsFpsDebugging() && !decoration(KisFpsDecoration::idTag)) {
addDecoration(new KisFpsDecoration(imageView()));
}
}
void KisCanvas2::createCanvas(bool useOpenGL)
{
// deinitialize previous canvas structures
m_d->prescaledProjection = 0;
m_d->frameCache = 0;
KisConfig cfg;
QDesktopWidget dw;
const KoColorProfile *profile = cfg.displayProfile(dw.screenNumber(imageView()));
m_d->displayColorConverter.setMonitorProfile(profile);
if (useOpenGL) {
if (KisOpenGL::hasOpenGL()) {
createOpenGLCanvas();
if (cfg.canvasState() == "OPENGL_FAILED") {
// Creating the opengl canvas failed, fall back
warnKrita << "OpenGL Canvas initialization returned OPENGL_FAILED. Falling back to QPainter.";
createQPainterCanvas();
}
} else {
warnKrita << "Tried to create OpenGL widget when system doesn't have OpenGL\n";
createQPainterCanvas();
}
}
else {
createQPainterCanvas();
}
if (m_d->popupPalette) {
m_d->popupPalette->setParent(m_d->canvasWidget->widget());
}
}
void KisCanvas2::initializeImage()
{
- KisImageWSP image = m_d->view->image();
+ KisImageSP image = m_d->view->image();
m_d->coordinatesConverter->setImage(image);
+ m_d->toolProxy.initializeImage(image);
connect(image, SIGNAL(sigImageUpdated(QRect)), SLOT(startUpdateCanvasProjection(QRect)), Qt::DirectConnection);
connect(this, SIGNAL(sigCanvasCacheUpdated()), SLOT(updateCanvasProjection()));
connect(image, SIGNAL(sigProofingConfigChanged()), SLOT(slotChangeProofingConfig()));
connect(image, SIGNAL(sigSizeChanged(const QPointF&, const QPointF&)), SLOT(startResizingImage()), Qt::DirectConnection);
connect(this, SIGNAL(sigContinueResizeImage(qint32,qint32)), SLOT(finishResizingImage(qint32,qint32)));
+ connect(image->undoAdapter(), SIGNAL(selectionChanged()), SLOT(slotTrySwitchShapeManager()));
connectCurrentCanvas();
}
void KisCanvas2::connectCurrentCanvas()
{
KisImageWSP image = m_d->view->image();
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->setImage(image);
}
startResizingImage();
emit imageChanged(image);
setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
}
void KisCanvas2::resetCanvas(bool useOpenGL)
{
// we cannot reset the canvas before it's created, but this method might be called,
// for instance when setting the monitor profile.
if (!m_d->canvasWidget) {
return;
}
KisConfig cfg;
bool needReset = (m_d->currentCanvasIsOpenGL != useOpenGL) ||
(m_d->currentCanvasIsOpenGL &&
m_d->openGLFilterMode != cfg.openGLFilteringMode());
if (needReset) {
createCanvas(useOpenGL);
connectCurrentCanvas();
notifyZoomChanged();
}
updateCanvasWidgetImpl();
}
void KisCanvas2::startUpdateInPatches(const QRect &imageRect)
{
if (m_d->currentCanvasIsOpenGL) {
startUpdateCanvasProjection(imageRect);
} else {
KisImageConfig imageConfig;
int patchWidth = imageConfig.updatePatchWidth();
int patchHeight = imageConfig.updatePatchHeight();
for (int y = 0; y < imageRect.height(); y += patchHeight) {
for (int x = 0; x < imageRect.width(); x += patchWidth) {
QRect patchRect(x, y, patchWidth, patchHeight);
startUpdateCanvasProjection(patchRect);
}
}
}
}
void KisCanvas2::setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter)
{
m_d->displayColorConverter.setDisplayFilter(displayFilter);
KisImageSP image = this->image();
m_d->view->viewManager()->blockUntillOperationsFinishedForced(image);
image->barrierLock();
m_d->canvasWidget->setDisplayFilter(displayFilter);
image->unlock();
}
QSharedPointer<KisDisplayFilter> KisCanvas2::displayFilter() const
{
return m_d->displayColorConverter.displayFilter();
}
KisDisplayColorConverter* KisCanvas2::displayColorConverter() const
{
return &m_d->displayColorConverter;
}
KisExposureGammaCorrectionInterface* KisCanvas2::exposureGammaCorrectionInterface() const
{
QSharedPointer<KisDisplayFilter> displayFilter = m_d->displayColorConverter.displayFilter();
return displayFilter ?
displayFilter->correctionInterface() :
KisDumbExposureGammaCorrectionInterface::instance();
}
void KisCanvas2::setProofingOptions(bool softProof, bool gamutCheck)
{
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
qDebug()<<"Canvas: No proofing config found, generating one.";
KisImageConfig cfg;
m_d->proofingConfig = cfg.defaultProofingconfiguration();
}
KoColorConversionTransformation::ConversionFlags conversionFlags = m_d->proofingConfig->conversionFlags;
if (softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::SoftProofing;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::SoftProofing;
}
if (gamutCheck && softProof && this->image()->colorSpace()->colorDepthId().id().contains("U")) {
conversionFlags |= KoColorConversionTransformation::GamutCheck;
} else {
conversionFlags = conversionFlags & ~KoColorConversionTransformation::GamutCheck;
}
m_d->proofingConfig->conversionFlags = conversionFlags;
m_d->proofingConfigUpdated = true;
startUpdateInPatches(this->image()->bounds());
}
void KisCanvas2::slotSoftProofing(bool softProofing)
{
m_d->softProofing = softProofing;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotGamutCheck(bool gamutCheck)
{
m_d->gamutCheck = gamutCheck;
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::slotChangeProofingConfig()
{
setProofingOptions(m_d->softProofing, m_d->gamutCheck);
}
void KisCanvas2::setProofingConfigUpdated(bool updated)
{
m_d->proofingConfigUpdated = updated;
}
bool KisCanvas2::proofingConfigUpdated()
{
return m_d->proofingConfigUpdated;
}
KisProofingConfigurationSP KisCanvas2::proofingConfiguration() const
{
if (!m_d->proofingConfig) {
m_d->proofingConfig = this->image()->proofingConfiguration();
if (!m_d->proofingConfig) {
qDebug()<<"Canvas: No proofing config found, generating one.";
KisImageConfig cfg;
m_d->proofingConfig = cfg.defaultProofingconfiguration();
}
}
return m_d->proofingConfig;
}
void KisCanvas2::startResizingImage()
{
KisImageWSP image = this->image();
qint32 w = image->width();
qint32 h = image->height();
emit sigContinueResizeImage(w, h);
QRect imageBounds(0, 0, w, h);
startUpdateInPatches(imageBounds);
}
void KisCanvas2::finishResizingImage(qint32 w, qint32 h)
{
m_d->canvasWidget->finishResizingImage(w, h);
}
void KisCanvas2::startUpdateCanvasProjection(const QRect & rc)
{
KisUpdateInfoSP info = m_d->canvasWidget->startUpdateCanvasProjection(rc, m_d->channelFlags);
if (m_d->projectionUpdatesCompressor.putUpdateInfo(info)) {
emit sigCanvasCacheUpdated();
}
}
void KisCanvas2::updateCanvasProjection()
{
while (KisUpdateInfoSP info = m_d->projectionUpdatesCompressor.takeUpdateInfo()) {
QRect vRect = m_d->canvasWidget->updateCanvasProjection(info);
if (!vRect.isEmpty()) {
updateCanvasWidgetImpl(m_d->coordinatesConverter->viewportToWidget(vRect).toAlignedRect());
}
}
// TODO: Implement info->dirtyViewportRect() for KisOpenGLCanvas2 to avoid updating whole canvas
if (m_d->currentCanvasIsOpenGL) {
updateCanvasWidgetImpl();
}
}
void KisCanvas2::slotDoCanvasUpdate()
{
if (m_d->canvasWidget->isBusy()) {
// just restarting the timer
updateCanvasWidgetImpl(m_d->savedUpdateRect);
return;
}
if (m_d->savedUpdateRect.isEmpty()) {
m_d->canvasWidget->widget()->update();
emit updateCanvasRequested(m_d->canvasWidget->widget()->rect());
} else {
emit updateCanvasRequested(m_d->savedUpdateRect);
m_d->canvasWidget->widget()->update(m_d->savedUpdateRect);
}
m_d->savedUpdateRect = QRect();
}
void KisCanvas2::updateCanvasWidgetImpl(const QRect &rc)
{
if (!m_d->updateSignalCompressor.isActive() ||
!m_d->savedUpdateRect.isEmpty()) {
m_d->savedUpdateRect |= rc;
}
m_d->updateSignalCompressor.start();
}
void KisCanvas2::updateCanvas()
{
updateCanvasWidgetImpl();
}
void KisCanvas2::updateCanvas(const QRectF& documentRect)
{
if (m_d->currentCanvasIsOpenGL && m_d->canvasWidget->decorations().size() > 0) {
updateCanvasWidgetImpl();
}
else {
// updateCanvas is called from tools, never from the projection
// updates, so no need to prescale!
QRect widgetRect = m_d->coordinatesConverter->documentToWidget(documentRect).toAlignedRect();
widgetRect.adjust(-2, -2, 2, 2);
if (!widgetRect.isEmpty()) {
updateCanvasWidgetImpl(widgetRect);
}
}
}
void KisCanvas2::disconnectCanvasObserver(QObject *object)
{
KoCanvasBase::disconnectCanvasObserver(object);
m_d->view->disconnect(object);
}
void KisCanvas2::notifyZoomChanged()
{
if (!m_d->currentCanvasIsOpenGL) {
Q_ASSERT(m_d->prescaledProjection);
m_d->prescaledProjection->notifyZoomChanged();
}
notifyLevelOfDetailChange();
updateCanvas(); // update the canvas, because that isn't done when zooming using KoZoomAction
}
+void KisCanvas2::slotTrySwitchShapeManager()
+{
+ QPointer<KoShapeManager> oldManager = m_d->currentlyActiveShapeManager;
+
+ KisNodeSP node = m_d->view->currentNode();
+
+ QPointer<KoShapeManager> newManager;
+ newManager = fetchShapeManagerFromNode(node);
+
+ if (newManager != oldManager) {
+ m_d->currentlyActiveShapeManager = newManager;
+ m_d->selectedShapesProxy.setShapeManager(newManager);
+ }
+}
+
void KisCanvas2::notifyLevelOfDetailChange()
{
if (!m_d->effectiveLodAllowedInCanvas()) return;
KisImageSP image = this->image();
const qreal effectiveZoom = m_d->coordinatesConverter->effectiveZoom();
KisConfig cfg;
const int maxLod = cfg.numMipmapLevels();
int lod = KisLodTransform::scaleToLod(effectiveZoom, maxLod);
image->setDesiredLevelOfDetail(lod);
}
-void KisCanvas2::preScale()
-{
- if (!m_d->currentCanvasIsOpenGL) {
- Q_ASSERT(m_d->prescaledProjection);
- m_d->prescaledProjection->preScale();
- }
-}
-
const KoColorProfile * KisCanvas2::monitorProfile()
{
return m_d->displayColorConverter.monitorProfile();
}
KisViewManager* KisCanvas2::viewManager() const
{
if (m_d->view) {
return m_d->view->viewManager();
}
return 0;
}
QPointer<KisView>KisCanvas2::imageView() const
{
return m_d->view;
}
KisImageWSP KisCanvas2::image() const
{
return m_d->view->image();
}
KisImageWSP KisCanvas2::currentImage() const
{
return m_d->view->image();
}
void KisCanvas2::documentOffsetMoved(const QPoint &documentOffset)
{
QPointF offsetBefore = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
m_d->coordinatesConverter->setDocumentOffset(documentOffset);
QPointF offsetAfter = m_d->coordinatesConverter->imageRectInViewportPixels().topLeft();
QPointF moveOffset = offsetAfter - offsetBefore;
if (!m_d->currentCanvasIsOpenGL)
m_d->prescaledProjection->viewportMoved(moveOffset);
emit documentOffsetUpdateFinished();
updateCanvas();
}
void KisCanvas2::slotConfigChanged()
{
KisConfig cfg;
m_d->vastScrolling = cfg.vastScrolling();
resetCanvas(cfg.useOpenGL());
slotSetDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this->canvasWidget())));
}
void KisCanvas2::refetchDataFromImage()
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
startUpdateInPatches(image->bounds());
}
void KisCanvas2::slotSetDisplayProfile(const KoColorProfile *monitorProfile)
{
if (m_d->displayColorConverter.monitorProfile() == monitorProfile) return;
m_d->displayColorConverter.setMonitorProfile(monitorProfile);
{
KisImageSP image = this->image();
KisImageBarrierLocker l(image);
m_d->canvasWidget->setDisplayProfile(&m_d->displayColorConverter);
}
refetchDataFromImage();
}
void KisCanvas2::addDecoration(KisCanvasDecorationSP deco)
{
m_d->canvasWidget->addDecoration(deco);
}
KisCanvasDecorationSP KisCanvas2::decoration(const QString& id) const
{
return m_d->canvasWidget->decoration(id);
}
QPoint KisCanvas2::documentOrigin() const
{
/**
* In Krita we don't use document origin anymore.
* All the centering when needed (vastScrolling < 0.5) is done
* automatically by the KisCoordinatesConverter.
*/
return QPoint();
}
QPoint KisCanvas2::documentOffset() const
{
return m_d->coordinatesConverter->documentOffset();
}
void KisCanvas2::setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager)
{
m_d->popupPalette = new KisPopupPalette(viewManager(), m_d->coordinatesConverter, favoriteResourceManager, displayColorConverter()->displayRendererInterface(),
m_d->view->resourceProvider(), m_d->canvasWidget->widget());
connect(m_d->popupPalette, SIGNAL(zoomLevelChanged(int)), this, SLOT(slotZoomChanged(int)));
connect(m_d->popupPalette, SIGNAL(sigUpdateCanvas()), this, SLOT(updateCanvas()));
m_d->popupPalette->showPopupPalette(false);
}
void KisCanvas2::slotZoomChanged(int zoom ) {
m_d->view->viewManager()->zoomController()->setZoom(KoZoomMode::ZOOM_CONSTANT, (qreal)(zoom/100.0)); // 1.0 is 100% zoom
notifyZoomChanged();
}
void KisCanvas2::setCursor(const QCursor &cursor)
{
canvasWidget()->setCursor(cursor);
}
KisAnimationFrameCacheSP KisCanvas2::frameCache() const
{
return m_d->frameCache;
}
KisAnimationPlayer *KisCanvas2::animationPlayer() const
{
return m_d->animationPlayer;
}
void KisCanvas2::slotSelectionChanged()
{
KisShapeLayer* shapeLayer = dynamic_cast<KisShapeLayer*>(viewManager()->activeLayer().data());
if (!shapeLayer) {
return;
}
m_d->shapeManager.selection()->deselectAll();
Q_FOREACH (KoShape* shape, shapeLayer->shapeManager()->selection()->selectedShapes()) {
m_d->shapeManager.selection()->select(shape);
}
}
bool KisCanvas2::isPopupPaletteVisible() const
{
if (!m_d->popupPalette) {
return false;
}
return m_d->popupPalette->isVisible();
}
void KisCanvas2::setWrapAroundViewingMode(bool value)
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
infinityDecoration->setVisible(!value);
}
m_d->canvasWidget->setWrapAroundViewingMode(value);
}
bool KisCanvas2::wrapAroundViewingMode() const
{
KisCanvasDecorationSP infinityDecoration =
m_d->canvasWidget->decoration(INFINITY_DECORATION_ID);
if (infinityDecoration) {
return !(infinityDecoration->visible());
}
return false;
}
void KisCanvas2::bootstrapFinished()
{
if (!m_d->bootstrapLodBlocked) return;
m_d->bootstrapLodBlocked = false;
setLodAllowedInCanvas(m_d->lodAllowedInCanvas);
}
void KisCanvas2::setLodAllowedInCanvas(bool value)
{
if (!KisOpenGL::supportsLoD()) {
qWarning() << "WARNING: Level of Detail functionality is available only with openGL + GLSL 1.3 support";
}
m_d->lodAllowedInCanvas =
value &&
m_d->currentCanvasIsOpenGL &&
KisOpenGL::supportsLoD() &&
(m_d->openGLFilterMode == KisOpenGL::TrilinearFilterMode ||
m_d->openGLFilterMode == KisOpenGL::HighQualityFiltering);
KisImageSP image = this->image();
if (m_d->effectiveLodAllowedInCanvas() != !image->levelOfDetailBlocked()) {
image->setLevelOfDetailBlocked(!m_d->effectiveLodAllowedInCanvas());
notifyLevelOfDetailChange();
}
KisConfig cfg;
cfg.setLevelOfDetailEnabled(m_d->lodAllowedInCanvas);
}
bool KisCanvas2::lodAllowedInCanvas() const
{
return m_d->lodAllowedInCanvas;
}
void KisCanvas2::slotShowPopupPalette(const QPoint &p)
{
if (!m_d->popupPalette) {
return;
}
m_d->popupPalette->showPopupPalette(p);
}
KisPaintingAssistantsDecorationSP KisCanvas2::paintingAssistantsDecoration() const
{
KisCanvasDecorationSP deco = decoration("paintingAssistantsDecoration");
return qobject_cast<KisPaintingAssistantsDecoration*>(deco.data());
}
diff --git a/libs/ui/canvas/kis_canvas2.h b/libs/ui/canvas/kis_canvas2.h
index 95512d9aa6..527860451a 100644
--- a/libs/ui/canvas/kis_canvas2.h
+++ b/libs/ui/canvas/kis_canvas2.h
@@ -1,297 +1,303 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CANVAS_H
#define KIS_CANVAS_H
#include <QObject>
#include <QWidget>
#include <QSize>
#include <QString>
#include <KoConfig.h>
#include <KoColorConversionTransformation.h>
#include <KoCanvasBase.h>
#include <kritaui_export.h>
#include <kis_types.h>
#include <KoPointerEvent.h>
#include "opengl/kis_opengl.h"
#include "kis_ui_types.h"
#include "kis_coordinates_converter.h"
#include "kis_canvas_decoration.h"
#include "kis_painting_assistants_decoration.h"
class KoToolProxy;
class KoColorProfile;
class KisViewManager;
class KisFavoriteResourceManager;
class KisDisplayFilter;
class KisDisplayColorConverter;
struct KisExposureGammaCorrectionInterface;
class KisView;
class KisInputManager;
class KisAnimationPlayer;
class KisShapeController;
class KisCoordinatesConverter;
class KoViewConverter;
/**
* KisCanvas2 is not an actual widget class, but rather an adapter for
* the widget it contains, which may be either a QPainter based
* canvas, or an OpenGL based canvas: that are the real widgets.
*/
class KRITAUI_EXPORT KisCanvas2 : public KoCanvasBase
{
Q_OBJECT
public:
/**
* Create a new canvas. The canvas manages a widget that will do
* the actual painting: the canvas itself is not a widget.
*
* @param viewConverter the viewconverter for converting between
* window and document coordinates.
*/
KisCanvas2(KisCoordinatesConverter *coordConverter, KoCanvasResourceManager *resourceManager, KisView *view, KoShapeBasedDocumentBase *sc);
virtual ~KisCanvas2();
void notifyZoomChanged();
virtual void disconnectCanvasObserver(QObject *object);
public: // KoCanvasBase implementation
bool canvasIsOpenGL() const;
KisOpenGL::FilterMode openGLFilterMode() const;
void gridSize(QPointF *offset, QSizeF *spacing) const;
bool snapToGrid() const;
- // XXX: Why?
+ // This method only exists to support flake-related operations
void addCommand(KUndo2Command *command);
virtual QPoint documentOrigin() const;
QPoint documentOffset() const;
/**
* Return the right shape manager for the current layer. That is
* to say, if the current layer is a vector layer, return the shape
* layer's canvas' shapemanager, else the shapemanager associated
* with the global krita canvas.
*/
- KoShapeManager * shapeManager() const;
+ KoShapeManager * shapeManager() const override;
+
+ /**
+ * Since shapeManager() may change, we need a persistent object where we can
+ * connect to and thack the selection. See more comments in KoCanvasBase.
+ */
+ KoSelectedShapesProxy *selectedShapesProxy() const override;
/**
* Return the shape manager associated with this canvas
*/
- KoShapeManager * globalShapeManager() const;
+ KoShapeManager *globalShapeManager() const;
void updateCanvas(const QRectF& rc);
virtual void updateInputMethodInfo();
const KisCoordinatesConverter* coordinatesConverter() const;
virtual KoViewConverter *viewConverter() const;
virtual QWidget* canvasWidget();
virtual const QWidget* canvasWidget() const;
virtual KoUnit unit() const;
virtual KoToolProxy* toolProxy() const;
const KoColorProfile* monitorProfile();
- /**
- * Prescale the canvas represention of the image (if necessary, it
- * is for QPainter, not for OpenGL).
- */
- void preScale();
-
// FIXME:
// Temporary! Either get the current layer and image from the
// resource provider, or use this, which gets them from the
// current shape selection.
KisImageWSP currentImage() const;
/**
* Filters events and sends them to canvas actions. Shared
* among all the views/canvases
*
* NOTE: May be null while initialization!
*/
KisInputManager* globalInputManager() const;
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const;
public: // KisCanvas2 methods
KisImageWSP image() const;
KisViewManager* viewManager() const;
QPointer<KisView> imageView() const;
/// @return true if the canvas image should be displayed in vertically mirrored mode
void addDecoration(KisCanvasDecorationSP deco);
KisCanvasDecorationSP decoration(const QString& id) const;
void setDisplayFilter(QSharedPointer<KisDisplayFilter> displayFilter);
QSharedPointer<KisDisplayFilter> displayFilter() const;
KisDisplayColorConverter *displayColorConverter() const;
KisExposureGammaCorrectionInterface* exposureGammaCorrectionInterface() const;
+
/**
* @brief setProofingOptions
* set the options for softproofing, without affecting the proofing options as stored inside the image.
*/
void setProofingOptions(bool softProof, bool gamutCheck);
KisProofingConfigurationSP proofingConfiguration() const;
+
/**
* @brief setProofingConfigUpdated This function is to set whether the proofing config is updated,
* this is needed for determining whether or not to generate a new proofing transform.
* @param updated whether it's updated. Just set it to false in normal usage.
*/
void setProofingConfigUpdated(bool updated);
+
/**
* @brief proofingConfigUpdated ask the canvas whether or not it updated the proofing config.
* @return whether or not the proofing config is updated, if so, a new proofing transform needs to be made
* in KisOpenGL canvas.
- */bool proofingConfigUpdated();
+ */
+ bool proofingConfigUpdated();
void setCursor(const QCursor &cursor);
KisAnimationFrameCacheSP frameCache() const;
KisAnimationPlayer *animationPlayer() const;
void refetchDataFromImage();
Q_SIGNALS:
void imageChanged(KisImageWSP image);
void sigCanvasCacheUpdated();
void sigContinueResizeImage(qint32 w, qint32 h);
void documentOffsetUpdateFinished();
// emitted whenever the canvas widget thinks sketch should update
void updateCanvasRequested(const QRect &rc);
public Q_SLOTS:
/// Update the entire canvas area
void updateCanvas();
void startResizingImage();
void finishResizingImage(qint32 w, qint32 h);
/// canvas rotation in degrees
qreal rotationAngle() const;
/// Bools indicating canvasmirroring.
bool xAxisMirrored() const;
bool yAxisMirrored() const;
void slotSoftProofing(bool softProofing);
void slotGamutCheck(bool gamutCheck);
void slotChangeProofingConfig();
void slotZoomChanged(int zoom);
void channelSelectionChanged();
/**
* Called whenever the display monitor profile resource changes
*/
void slotSetDisplayProfile(const KoColorProfile *profile);
void startUpdateInPatches(const QRect &imageRect);
+ void slotTrySwitchShapeManager();
+
private Q_SLOTS:
/// The image projection has changed, now start an update
/// of the canvas representation.
void startUpdateCanvasProjection(const QRect & rc);
void updateCanvasProjection();
/**
* Called whenever the view widget needs to show a different part of
* the document
*
* @param documentOffset the offset in widget pixels
*/
void documentOffsetMoved(const QPoint &documentOffset);
/**
* Called whenever the configuration settings change.
*/
void slotConfigChanged();
void slotSelectionChanged();
void slotDoCanvasUpdate();
void bootstrapFinished();
public:
bool isPopupPaletteVisible() const;
void slotShowPopupPalette(const QPoint& = QPoint(0,0));
// interface for KisCanvasController only
void setWrapAroundViewingMode(bool value);
bool wrapAroundViewingMode() const;
void setLodAllowedInCanvas(bool value);
bool lodAllowedInCanvas() const;
void initializeImage();
void setFavoriteResourceManager(KisFavoriteResourceManager* favoriteResourceManager);
private:
Q_DISABLE_COPY(KisCanvas2)
void connectCurrentCanvas();
void createCanvas(bool useOpenGL);
void createQPainterCanvas();
void createOpenGLCanvas();
void updateCanvasWidgetImpl(const QRect &rc = QRect());
void setCanvasWidget(QWidget *widget);
void resetCanvas(bool useOpenGL);
void notifyLevelOfDetailChange();
// Completes construction of canvas.
// To be called by KisView in its constructor, once it has been setup enough
// (to be defined what that means) for things KisCanvas2 expects from KisView
// TODO: see to avoid that
void setup();
private:
friend class KisView; // calls setup()
class KisCanvas2Private;
KisCanvas2Private * const m_d;
};
#endif
diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp
index 2ff3fc1340..31f380631d 100644
--- a/libs/ui/canvas/kis_canvas_controller.cpp
+++ b/libs/ui/canvas/kis_canvas_controller.cpp
@@ -1,290 +1,304 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_canvas_controller.h"
#include <QMouseEvent>
#include <QTabletEvent>
#include <klocalizedstring.h>
#include "kis_canvas_decoration.h"
#include "kis_paintop_transformation_connector.h"
#include "kis_coordinates_converter.h"
#include "kis_canvas2.h"
#include "kis_image.h"
#include "KisViewManager.h"
#include "KisView.h"
#include "krita_utils.h"
#include "kis_signal_compressor_with_param.h"
static const int gRulersUpdateDelay = 80 /* ms */;
struct KisCanvasController::Private {
Private(KisCanvasController *qq)
: q(qq),
paintOpTransformationConnector(0)
{
using namespace std::placeholders;
std::function<void (QPoint)> callback(
std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1));
mousePositionCompressor.reset(
new KisSignalCompressorWithParam<QPoint>(
gRulersUpdateDelay,
callback,
KisSignalCompressor::FIRST_ACTIVE));
}
QPointer<KisView> view;
KisCoordinatesConverter *coordinatesConverter;
KisCanvasController *q;
KisPaintopTransformationConnector *paintOpTransformationConnector;
QScopedPointer<KisSignalCompressorWithParam<QPoint> > mousePositionCompressor;
void emitPointerPositionChangedSignals(QPoint pointerPos);
void updateDocumentSizeAfterTransform();
void showRotationValueOnCanvas();
void showMirrorStateOnCanvas();
};
void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos)
{
if (!coordinatesConverter) return;
QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos);
q->proxyObject->emitDocumentMousePositionChanged(documentPos);
q->proxyObject->emitCanvasMousePositionChanged(pointerPos);
}
void KisCanvasController::Private::updateDocumentSizeAfterTransform()
{
// round the size of the area to the nearest integer instead of getting aligned rect
QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size();
q->updateDocumentSize(widgetSize, true);
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(q->canvas());
Q_ASSERT(kritaCanvas);
kritaCanvas->notifyZoomChanged();
}
KisCanvasController::KisCanvasController(QPointer<KisView>parent, KActionCollection * actionCollection)
: KoCanvasControllerWidget(actionCollection, parent),
m_d(new Private(this))
{
m_d->view = parent;
}
KisCanvasController::~KisCanvasController()
{
delete m_d;
}
void KisCanvasController::setCanvas(KoCanvasBase *canvas)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
Q_ASSERT(kritaCanvas);
m_d->coordinatesConverter =
const_cast<KisCoordinatesConverter*>(kritaCanvas->coordinatesConverter());
KoCanvasControllerWidget::setCanvas(canvas);
m_d->paintOpTransformationConnector =
new KisPaintopTransformationConnector(kritaCanvas, this);
}
void KisCanvasController::changeCanvasWidget(QWidget *widget)
{
KoCanvasControllerWidget::changeCanvasWidget(widget);
}
void KisCanvasController::activate()
{
KoCanvasControllerWidget::activate();
}
+QPointF KisCanvasController::currentCursorPosition() const
+{
+ KoCanvasBase *canvas = m_d->view->canvasBase();
+ QWidget *canvasWidget = canvas->canvasWidget();
+ const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos());
+
+ return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget);
+}
+
void KisCanvasController::keyPressEvent(QKeyEvent *event)
{
/**
* Dirty Hack Alert:
* Do not call the KoCanvasControllerWidget::keyPressEvent()
* to avoid activation of Pan and Default tool activation shortcuts
*/
Q_UNUSED(event);
}
void KisCanvasController::wheelEvent(QWheelEvent *event)
{
/**
* Dirty Hack Alert:
* Do not call the KoCanvasControllerWidget::wheelEvent()
* to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea
*/
Q_UNUSED(event);
}
bool KisCanvasController::eventFilter(QObject *watched, QEvent *event)
{
KoCanvasBase *canvas = this->canvas();
if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false;
if (event->type() == QEvent::MouseMove) {
QMouseEvent *mevent = static_cast<QMouseEvent*>(event);
m_d->mousePositionCompressor->start(mevent->pos());
} else if (event->type() == QEvent::TabletMove) {
QTabletEvent *tevent = static_cast<QTabletEvent*>(event);
m_d->mousePositionCompressor->start(tevent->pos());
}
return false;
}
void KisCanvasController::updateDocumentSize(const QSize &sz, bool recalculateCenter)
{
KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter);
emit documentSizeChanged();
}
void KisCanvasController::Private::showMirrorStateOnCanvas()
{
bool isXMirrored = coordinatesConverter->xAxisMirrored();
view->viewManager()->
showFloatingMessage(
i18nc("floating message about mirroring",
"Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")),
QIcon(), 500, KisFloatingMessage::Low);
}
void KisCanvasController::mirrorCanvas(bool enable)
{
QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false);
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showMirrorStateOnCanvas();
}
void KisCanvasController::Private::showRotationValueOnCanvas()
{
qreal rotationAngle = coordinatesConverter->rotationAngle();
view->viewManager()->
showFloatingMessage(
i18nc("floating message about rotation", "Rotation: %1° ",
KritaUtils::prettyFormatReal(rotationAngle)),
QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
}
void KisCanvasController::rotateCanvas(qreal angle)
{
QPoint newOffset = m_d->coordinatesConverter->rotate(m_d->coordinatesConverter->widgetCenterPoint(), angle);
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showRotationValueOnCanvas();
}
void KisCanvasController::rotateCanvasRight15()
{
rotateCanvas(15.0);
}
void KisCanvasController::rotateCanvasLeft15()
{
rotateCanvas(-15.0);
}
+qreal KisCanvasController::rotation() const
+{
+ return m_d->coordinatesConverter->rotationAngle();
+}
+
void KisCanvasController::resetCanvasRotation()
{
QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint());
m_d->updateDocumentSizeAfterTransform();
setScrollBarValue(newOffset);
m_d->paintOpTransformationConnector->notifyTransformationChanged();
m_d->showRotationValueOnCanvas();
}
void KisCanvasController::slotToggleWrapAroundMode(bool value)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
if (!canvas()->canvasIsOpenGL() && value) {
m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n"
"To visualize wrap-around mode, enable OpenGL."), QIcon());
}
kritaCanvas->setWrapAroundViewingMode(value);
kritaCanvas->image()->setWrapAroundModePermitted(value);
}
bool KisCanvasController::wrapAroundMode() const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->wrapAroundViewingMode();
}
void KisCanvasController::slotToggleLevelOfDetailMode(bool value)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
kritaCanvas->setLodAllowedInCanvas(value);
bool result = levelOfDetailMode();
if (!value || result) {
m_d->view->viewManager()->showFloatingMessage(
i18n("Instant Preview Mode: %1", result ?
i18n("ON") : i18n("OFF")),
QIcon(), 500, KisFloatingMessage::Low);
} else {
QString reason;
if (!kritaCanvas->canvasIsOpenGL()) {
reason = i18n("Instant Preview is only supported with OpenGL activated");
}
else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ||
kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) {
QString filteringMode =
kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ?
i18n("Bilinear") : i18n("Nearest Neighbour");
reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode);
}
m_d->view->viewManager()->showFloatingMessage(
i18n("Failed activating Instant Preview mode!\n\n%1", reason),
QIcon(), 5000, KisFloatingMessage::Low);
}
}
bool KisCanvasController::levelOfDetailMode() const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->lodAllowedInCanvas();
}
diff --git a/libs/ui/canvas/kis_canvas_controller.h b/libs/ui/canvas/kis_canvas_controller.h
index c371e2616f..cc7122c2ab 100644
--- a/libs/ui/canvas/kis_canvas_controller.h
+++ b/libs/ui/canvas/kis_canvas_controller.h
@@ -1,67 +1,70 @@
/*
* Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CANVAS_CONTROLLER_H
#define KIS_CANVAS_CONTROLLER_H
#include <KoCanvasControllerWidget.h>
#include "kritaui_export.h"
#include "kis_types.h"
class KisView;
class KRITAUI_EXPORT KisCanvasController : public KoCanvasControllerWidget
{
Q_OBJECT
public:
KisCanvasController(QPointer<KisView>parent, KActionCollection * actionCollection);
~KisCanvasController();
virtual void setCanvas(KoCanvasBase *canvas);
virtual void changeCanvasWidget(QWidget *widget);
virtual void keyPressEvent(QKeyEvent *event);
virtual void wheelEvent(QWheelEvent *event);
virtual bool eventFilter(QObject *watched, QEvent *event);
virtual void updateDocumentSize(const QSize &sz, bool recalculateCenter);
virtual void activate();
+ QPointF currentCursorPosition() const override;
+
public:
using KoCanvasController::documentSize;
bool wrapAroundMode() const;
bool levelOfDetailMode() const;
public Q_SLOTS:
void mirrorCanvas(bool enable);
void rotateCanvas(qreal angle);
void rotateCanvasRight15();
void rotateCanvasLeft15();
+ qreal rotation() const;
void resetCanvasRotation();
void slotToggleWrapAroundMode(bool value);
void slotToggleLevelOfDetailMode(bool value);
Q_SIGNALS:
void documentSizeChanged();
private:
struct Private;
Private * const m_d;
};
#endif /* KIS_CANVAS_CONTROLLER_H */
diff --git a/libs/ui/canvas/kis_guides_manager.cpp b/libs/ui/canvas/kis_guides_manager.cpp
index bd251f4271..2ebf8150ac 100644
--- a/libs/ui/canvas/kis_guides_manager.cpp
+++ b/libs/ui/canvas/kis_guides_manager.cpp
@@ -1,743 +1,765 @@
/*
* 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_guides_manager.h"
#include <QMenu>
#include <QGuiApplication>
#include "kis_guides_decoration.h"
#include <KoRuler.h>
#include "kis_guides_config.h"
#include "kis_action_manager.h"
#include "kis_action.h"
#include "kis_signals_blocker.h"
#include "input/kis_input_manager.h"
#include "kis_coordinates_converter.h"
#include "kis_zoom_manager.h"
#include "kis_signal_auto_connection.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_algebra_2d.h"
#include <KoSnapGuide.h>
#include "kis_snap_line_strategy.h"
#include "kis_change_guides_command.h"
#include "kis_snap_config.h"
#include "kis_coordinates_converter.h"
#include "kis_canvas2.h"
+#include "kis_signal_compressor.h"
struct KisGuidesManager::Private
{
Private(KisGuidesManager *_q)
: q(_q),
decoration(0),
invalidGuide(Qt::Horizontal, -1),
currentGuide(invalidGuide),
cursorSwitched(false),
- dragStartGuidePos(0) {}
+ dragStartGuidePos(0),
+ updateDocumentCompressor(40, KisSignalCompressor::FIRST_ACTIVE),
+ shouldSetModified(false) {}
KisGuidesManager *q;
KisGuidesDecoration *decoration;
KisGuidesConfig guidesConfig;
KisSnapConfig snapConfig;
QPointer<KisView> view;
typedef QPair<Qt::Orientation, int> GuideHandle;
GuideHandle findGuide(const QPointF &docPos);
bool isGuideValid(const GuideHandle &h);
qreal guideValue(const GuideHandle &h);
void setGuideValue(const GuideHandle &h, qreal value);
void deleteGuide(const GuideHandle &h);
const GuideHandle invalidGuide;
bool updateCursor(const QPointF &docPos);
void initDragStart(const GuideHandle &guide,
const QPointF &dragStart,
qreal guideValue,
bool snapToStart);
bool mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers);
bool mouseReleaseHandler(const QPointF &docPos);
void updateSnappingStatus(const KisGuidesConfig &value);
QPointF alignToPixels(const QPointF docPoint);
QPointF getDocPointFromEvent(QEvent *event);
Qt::MouseButton getButtonFromEvent(QEvent *event);
QAction* createShortenedAction(const QString &text, const QString &parentId, QObject *parent);
void syncAction(const QString &actionName, bool value);
GuideHandle currentGuide;
bool cursorSwitched;
QCursor oldCursor;
QPointF dragStartDoc;
QPointF dragPointerOffset;
qreal dragStartGuidePos;
KisSignalAutoConnectionsStore viewConnections;
+
+ KisSignalCompressor updateDocumentCompressor;
+ bool shouldSetModified;
};
KisGuidesManager::KisGuidesManager(QObject *parent)
: QObject(parent),
m_d(new Private(this))
{
+ connect(&m_d->updateDocumentCompressor, SIGNAL(timeout()), SLOT(slotUploadConfigToDocument()));
}
KisGuidesManager::~KisGuidesManager()
{
}
void KisGuidesManager::setGuidesConfig(const KisGuidesConfig &config)
{
if (config == m_d->guidesConfig) return;
setGuidesConfigImpl(config, true);
}
void KisGuidesManager::slotDocumentRequestedConfig(const KisGuidesConfig &config)
{
if (config == m_d->guidesConfig) return;
setGuidesConfigImpl(config, false);
}
-void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified)
+void KisGuidesManager::slotUploadConfigToDocument()
{
- m_d->guidesConfig = value;
-
- if (m_d->decoration && value != m_d->decoration->guidesConfig()) {
- m_d->decoration->setVisible(value.showGuides());
- m_d->decoration->setGuidesConfig(value);
- }
+ const KisGuidesConfig &value = m_d->guidesConfig;
KisDocument *doc = m_d->view ? m_d->view->document() : 0;
if (doc) {
KisSignalsBlocker b(doc);
- if (emitModified) {
+ if (m_d->shouldSetModified) {
KUndo2Command *cmd = new KisChangeGuidesCommand(doc, value);
doc->addCommand(cmd);
} else {
doc->setGuidesConfig(value);
}
value.saveStaticData();
}
+ m_d->shouldSetModified = false;
+}
+
+void KisGuidesManager::setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified)
+{
+ m_d->guidesConfig = value;
+
+ if (m_d->decoration && value != m_d->decoration->guidesConfig()) {
+ m_d->decoration->setVisible(value.showGuides());
+ m_d->decoration->setGuidesConfig(value);
+ }
+
+ m_d->shouldSetModified |= emitModified;
+ m_d->updateDocumentCompressor.start();
+
const bool shouldFilterEvent =
value.showGuides() && !value.lockGuides() && value.hasGuides();
attachEventFilterImpl(shouldFilterEvent);
syncActionsStatus();
if (!m_d->isGuideValid(m_d->currentGuide)) {
m_d->updateSnappingStatus(value);
}
emit sigRequestUpdateGuidesConfig(m_d->guidesConfig);
}
void KisGuidesManager::attachEventFilterImpl(bool value)
{
if (!m_d->view) return;
KisInputManager *inputManager = m_d->view->globalInputManager();
if (inputManager) {
if (value) {
inputManager->attachPriorityEventFilter(this, 100);
} else {
inputManager->detachPriorityEventFilter(this);
}
}
}
void KisGuidesManager::Private::syncAction(const QString &actionName, bool value)
{
KisActionManager *actionManager = view->viewManager()->actionManager();
KisAction *action = actionManager->actionByName(actionName);
KIS_ASSERT_RECOVER_RETURN(action);
KisSignalsBlocker b(action);
action->setChecked(value);
}
void KisGuidesManager::syncActionsStatus()
{
if (!m_d->view) return;
m_d->syncAction("view_show_guides", m_d->guidesConfig.showGuides());
m_d->syncAction("view_lock_guides", m_d->guidesConfig.lockGuides());
m_d->syncAction("view_snap_to_guides", m_d->guidesConfig.snapToGuides());
m_d->syncAction("view_snap_orthogonal", m_d->snapConfig.orthogonal());
m_d->syncAction("view_snap_node", m_d->snapConfig.node());
m_d->syncAction("view_snap_extension", m_d->snapConfig.extension());
m_d->syncAction("view_snap_intersection", m_d->snapConfig.intersection());
m_d->syncAction("view_snap_bounding_box", m_d->snapConfig.boundingBox());
m_d->syncAction("view_snap_image_bounds", m_d->snapConfig.imageBounds());
m_d->syncAction("view_snap_image_center", m_d->snapConfig.imageCenter());
}
void KisGuidesManager::Private::updateSnappingStatus(const KisGuidesConfig &value)
{
if (!view) return;
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
KisSnapLineStrategy *guidesSnap = 0;
if (value.snapToGuides()) {
guidesSnap = new KisSnapLineStrategy(KoSnapGuide::GuideLineSnapping);
guidesSnap->setHorizontalLines(value.horizontalGuideLines());
guidesSnap->setVerticalLines(value.verticalGuideLines());
}
snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap);
snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, guidesSnap);
snapGuide->enableSnapStrategy(KoSnapGuide::OrthogonalSnapping, snapConfig.orthogonal());
snapGuide->enableSnapStrategy(KoSnapGuide::NodeSnapping, snapConfig.node());
snapGuide->enableSnapStrategy(KoSnapGuide::ExtensionSnapping, snapConfig.extension());
snapGuide->enableSnapStrategy(KoSnapGuide::IntersectionSnapping, snapConfig.intersection());
snapGuide->enableSnapStrategy(KoSnapGuide::BoundingBoxSnapping, snapConfig.boundingBox());
snapGuide->enableSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, snapConfig.imageBounds());
snapGuide->enableSnapStrategy(KoSnapGuide::DocumentCenterSnapping, snapConfig.imageCenter());
snapConfig.saveStaticData();
}
bool KisGuidesManager::showGuides() const
{
return m_d->guidesConfig.showGuides();
}
void KisGuidesManager::setShowGuides(bool value)
{
m_d->guidesConfig.setShowGuides(value);
setGuidesConfigImpl(m_d->guidesConfig);
}
bool KisGuidesManager::lockGuides() const
{
return m_d->guidesConfig.lockGuides();
}
void KisGuidesManager::setLockGuides(bool value)
{
m_d->guidesConfig.setLockGuides(value);
setGuidesConfigImpl(m_d->guidesConfig);
}
bool KisGuidesManager::snapToGuides() const
{
return m_d->guidesConfig.snapToGuides();
}
void KisGuidesManager::setSnapToGuides(bool value)
{
m_d->guidesConfig.setSnapToGuides(value);
setGuidesConfigImpl(m_d->guidesConfig);
}
void KisGuidesManager::setup(KisActionManager *actionManager)
{
KisAction *action = 0;
action = actionManager->createAction("view_show_guides");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setShowGuides(bool)));
action = actionManager->createAction("view_lock_guides");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setLockGuides(bool)));
action = actionManager->createAction("view_snap_to_guides");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapToGuides(bool)));
action = actionManager->createAction("show_snap_options_popup");
connect(action, SIGNAL(triggered()), this, SLOT(slotShowSnapOptions()));
action = actionManager->createAction("view_snap_orthogonal");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapOrthogonal(bool)));
action = actionManager->createAction("view_snap_node");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapNode(bool)));
action = actionManager->createAction("view_snap_extension");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapExtension(bool)));
action = actionManager->createAction("view_snap_intersection");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapIntersection(bool)));
action = actionManager->createAction("view_snap_bounding_box");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapBoundingBox(bool)));
action = actionManager->createAction("view_snap_image_bounds");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageBounds(bool)));
action = actionManager->createAction("view_snap_image_center");
connect(action, SIGNAL(toggled(bool)), this, SLOT(setSnapImageCenter(bool)));
m_d->updateSnappingStatus(m_d->guidesConfig);
syncActionsStatus();
}
void KisGuidesManager::setView(QPointer<KisView> view)
{
if (m_d->view) {
KoSnapGuide *snapGuide = m_d->view->canvasBase()->snapGuide();
snapGuide->overrideSnapStrategy(KoSnapGuide::GuideLineSnapping, 0);
snapGuide->enableSnapStrategy(KoSnapGuide::GuideLineSnapping, false);
+ if (m_d->updateDocumentCompressor.isActive()) {
+ m_d->updateDocumentCompressor.stop();
+ slotUploadConfigToDocument();
+ }
+
m_d->decoration = 0;
m_d->viewConnections.clear();
attachEventFilterImpl(false);
}
m_d->view = view;
if (m_d->view) {
KisGuidesDecoration* decoration = qobject_cast<KisGuidesDecoration*>(m_d->view->canvasBase()->decoration(GUIDES_DECORATION_ID).data());
if (!decoration) {
decoration = new KisGuidesDecoration(m_d->view);
m_d->view->canvasBase()->addDecoration(decoration);
}
m_d->decoration = decoration;
m_d->guidesConfig = m_d->view->document()->guidesConfig();
setGuidesConfigImpl(m_d->guidesConfig, false);
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)),
this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&)));
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->horizontalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)),
this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&)));
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationInProgress(Qt::Orientation, const QPoint&)),
this, SLOT(slotGuideCreationInProgress(Qt::Orientation, const QPoint&)));
m_d->viewConnections.addUniqueConnection(
m_d->view->zoomManager()->verticalRuler(), SIGNAL(guideCreationFinished(Qt::Orientation, const QPoint&)),
this, SLOT(slotGuideCreationFinished(Qt::Orientation, const QPoint&)));
m_d->viewConnections.addUniqueConnection(
m_d->view->document(), SIGNAL(sigGuidesConfigChanged(const KisGuidesConfig &)),
this, SLOT(slotDocumentRequestedConfig(const KisGuidesConfig &)));
}
}
KisGuidesManager::Private::GuideHandle
KisGuidesManager::Private::findGuide(const QPointF &docPos)
{
const int snapRadius = 16;
GuideHandle nearestGuide = invalidGuide;
qreal nearestRadius = std::numeric_limits<int>::max();
for (int i = 0; i < guidesConfig.horizontalGuideLines().size(); i++) {
const qreal guide = guidesConfig.horizontalGuideLines()[i];
const qreal radius = qAbs(docPos.y() - guide);
if (radius < snapRadius && radius < nearestRadius) {
nearestGuide = GuideHandle(Qt::Horizontal, i);
nearestRadius = radius;
}
}
for (int i = 0; i < guidesConfig.verticalGuideLines().size(); i++) {
const qreal guide = guidesConfig.verticalGuideLines()[i];
const qreal radius = qAbs(docPos.x() - guide);
if (radius < snapRadius && radius < nearestRadius) {
nearestGuide = GuideHandle(Qt::Vertical, i);
nearestRadius = radius;
}
}
return nearestGuide;
}
bool KisGuidesManager::Private::isGuideValid(const GuideHandle &h)
{
return h.second >= 0;
}
qreal KisGuidesManager::Private::guideValue(const GuideHandle &h)
{
return h.first == Qt::Horizontal ?
guidesConfig.horizontalGuideLines()[h.second] :
guidesConfig.verticalGuideLines()[h.second];
}
void KisGuidesManager::Private::setGuideValue(const GuideHandle &h, qreal value)
{
if (h.first == Qt::Horizontal) {
QList<qreal> guides = guidesConfig.horizontalGuideLines();
guides[h.second] = value;
guidesConfig.setHorizontalGuideLines(guides);
} else {
QList<qreal> guides = guidesConfig.verticalGuideLines();
guides[h.second] = value;
guidesConfig.setVerticalGuideLines(guides);
}
}
void KisGuidesManager::Private::deleteGuide(const GuideHandle &h)
{
if (h.first == Qt::Horizontal) {
QList<qreal> guides = guidesConfig.horizontalGuideLines();
guides.removeAt(h.second);
guidesConfig.setHorizontalGuideLines(guides);
} else {
QList<qreal> guides = guidesConfig.verticalGuideLines();
guides.removeAt(h.second);
guidesConfig.setVerticalGuideLines(guides);
}
}
bool KisGuidesManager::Private::updateCursor(const QPointF &docPos)
{
KisCanvas2 *canvas = view->canvasBase();
const GuideHandle guide = findGuide(docPos);
const bool guideValid = isGuideValid(guide);
if (guideValid && !cursorSwitched) {
oldCursor = canvas->canvasWidget()->cursor();
}
if (guideValid) {
cursorSwitched = true;
QCursor newCursor = guide.first == Qt::Horizontal ?
Qt::SizeVerCursor : Qt::SizeHorCursor;
canvas->canvasWidget()->setCursor(newCursor);
}
if (!guideValid && cursorSwitched) {
canvas->canvasWidget()->setCursor(oldCursor);
cursorSwitched = false;
}
return guideValid;
}
void KisGuidesManager::Private::initDragStart(const GuideHandle &guide,
const QPointF &dragStart,
qreal guideValue,
bool snapToStart)
{
currentGuide = guide;
dragStartDoc = dragStart;
dragStartGuidePos = guideValue;
dragPointerOffset =
guide.first == Qt::Horizontal ?
QPointF(0, dragStartGuidePos - dragStartDoc.y()) :
QPointF(dragStartGuidePos - dragStartDoc.x(), 0);
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
snapGuide->reset();
if (snapToStart) {
KisSnapLineStrategy *strategy = new KisSnapLineStrategy();
strategy->addLine(guide.first, guideValue);
snapGuide->addCustomSnapStrategy(strategy);
}
}
QPointF KisGuidesManager::Private::alignToPixels(const QPointF docPoint)
{
KisCanvas2 *canvas = view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
QPoint imagePoint = converter->documentToImage(docPoint).toPoint();
return converter->imageToDocument(imagePoint);
}
bool KisGuidesManager::Private::mouseMoveHandler(const QPointF &docPos, Qt::KeyboardModifiers modifiers)
{
if (isGuideValid(currentGuide)) {
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
const QPointF snappedPos = snapGuide->snap(docPos, dragPointerOffset, modifiers);
const QPointF offset = snappedPos - dragStartDoc;
const qreal newValue = dragStartGuidePos +
(currentGuide.first == Qt::Horizontal ?
offset.y() : offset.x());
setGuideValue(currentGuide, newValue);
q->setGuidesConfigImpl(guidesConfig);
}
return updateCursor(docPos);
}
bool KisGuidesManager::Private::mouseReleaseHandler(const QPointF &docPos)
{
bool result = false;
KisCanvas2 *canvas = view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
if (isGuideValid(currentGuide)) {
const QRectF docRect = converter->imageRectInDocumentPixels();
// TODO: enable work rect after we fix painting guides
// outside canvas in openGL mode
const QRectF workRect = KisAlgebra2D::blowRect(docRect, 0 /*0.2*/);
if (!workRect.contains(docPos)) {
deleteGuide(currentGuide);
q->setGuidesConfigImpl(guidesConfig);
/**
* When we delete a guide, it might happen that we are
* delting the last guide. Therefore we should eat the
* corresponding event so that the event filter would stop
* the filter processing.
*/
result = true;
}
currentGuide = invalidGuide;
dragStartDoc = QPointF();
dragPointerOffset = QPointF();
dragStartGuidePos = 0;
KoSnapGuide *snapGuide = view->canvasBase()->snapGuide();
snapGuide->reset();
updateSnappingStatus(guidesConfig);
}
return updateCursor(docPos) | result;
}
QPointF KisGuidesManager::Private::getDocPointFromEvent(QEvent *event)
{
QPointF result;
KisCanvas2 *canvas = view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
result = alignToPixels(converter->widgetToDocument(mouseEvent->pos()));
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
result = alignToPixels(converter->widgetToDocument(tabletEvent->pos()));
}
return result;
}
Qt::MouseButton KisGuidesManager::Private::getButtonFromEvent(QEvent *event)
{
Qt::MouseButton button = Qt::NoButton;
if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::MouseButtonPress ||
event->type() == QEvent::MouseButtonRelease) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
button = mouseEvent->button();
} else if (event->type() == QEvent::TabletMove ||
event->type() == QEvent::TabletPress ||
event->type() == QEvent::TabletRelease) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
button = tabletEvent->button();
}
return button;
}
bool KisGuidesManager::eventFilter(QObject *obj, QEvent *event)
{
if (!m_d->view || obj != m_d->view->canvasBase()->canvasWidget()) return false;
bool retval = false;
switch (event->type()) {
case QEvent::Enter:
case QEvent::Leave:
case QEvent::TabletMove:
case QEvent::MouseMove: {
const QPointF docPos = m_d->getDocPointFromEvent(event);
const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers();
retval = m_d->mouseMoveHandler(docPos, modifiers);
break;
}
case QEvent::TabletPress:
case QEvent::MouseButtonPress: {
if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break;
const QPointF docPos = m_d->getDocPointFromEvent(event);
const Private::GuideHandle guide = m_d->findGuide(docPos);
const bool guideValid = m_d->isGuideValid(guide);
if (guideValid) {
m_d->initDragStart(guide, docPos, m_d->guideValue(guide), true);
}
retval = m_d->updateCursor(docPos);
break;
}
case QEvent::TabletRelease:
case QEvent::MouseButtonRelease: {
if (m_d->getButtonFromEvent(event) != Qt::LeftButton) break;
const QPointF docPos = m_d->getDocPointFromEvent(event);
retval = m_d->mouseReleaseHandler(docPos);
break;
}
default:
break;
}
return !retval ? QObject::eventFilter(obj, event) : true;
}
void KisGuidesManager::slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos)
{
if (m_d->guidesConfig.lockGuides()) return;
KisCanvas2 *canvas = m_d->view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos);
const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos));
if (m_d->isGuideValid(m_d->currentGuide)) {
const Qt::KeyboardModifiers modifiers = qApp->keyboardModifiers();
m_d->mouseMoveHandler(docPos, modifiers);
} else {
m_d->guidesConfig.setShowGuides(true);
if (orientation == Qt::Horizontal) {
QList<qreal> guides = m_d->guidesConfig.horizontalGuideLines();
guides.append(docPos.y());
m_d->currentGuide.first = orientation;
m_d->currentGuide.second = guides.size() - 1;
m_d->guidesConfig.setHorizontalGuideLines(guides);
m_d->initDragStart(m_d->currentGuide, docPos, docPos.y(), false);
} else {
QList<qreal> guides = m_d->guidesConfig.verticalGuideLines();
guides.append(docPos.x());
m_d->currentGuide.first = orientation;
m_d->currentGuide.second = guides.size() - 1;
m_d->guidesConfig.setVerticalGuideLines(guides);
m_d->initDragStart(m_d->currentGuide, docPos, docPos.x(), false);
}
setGuidesConfigImpl(m_d->guidesConfig);
}
}
void KisGuidesManager::slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos)
{
Q_UNUSED(orientation);
if (m_d->guidesConfig.lockGuides()) return;
KisCanvas2 *canvas = m_d->view->canvasBase();
const KisCoordinatesConverter *converter = canvas->coordinatesConverter();
const QPointF widgetPos = canvas->canvasWidget()->mapFromGlobal(globalPos);
const QPointF docPos = m_d->alignToPixels(converter->widgetToDocument(widgetPos));
m_d->mouseReleaseHandler(docPos);
}
QAction* KisGuidesManager::Private::createShortenedAction(const QString &text, const QString &parentId, QObject *parent)
{
KisActionManager *actionManager = view->viewManager()->actionManager();
QAction *action = 0;
KisAction *parentAction = 0;
action = new QAction(text, parent);
action->setCheckable(true);
parentAction = actionManager->actionByName(parentId);
action->setChecked(parentAction->isChecked());
connect(action, SIGNAL(toggled(bool)), parentAction, SLOT(setChecked(bool)));
return action;
}
void KisGuidesManager::slotShowSnapOptions()
{
const QPoint pos = QCursor::pos();
QMenu menu;
menu.addSection(i18n("Snap to:"));
menu.addAction(m_d->createShortenedAction(i18n("Grid"), "view_snap_to_grid", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Guides"), "view_snap_to_guides", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Orthogonal"), "view_snap_orthogonal", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Node"), "view_snap_node", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Extension"), "view_snap_extension", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Intersection"), "view_snap_intersection", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Bounding Box"), "view_snap_bounding_box", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Image Bounds"), "view_snap_image_bounds", &menu));
menu.addAction(m_d->createShortenedAction(i18n("Image Center"), "view_snap_image_center", &menu));
menu.exec(pos);
}
void KisGuidesManager::setSnapOrthogonal(bool value)
{
m_d->snapConfig.setOrthogonal(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapNode(bool value)
{
m_d->snapConfig.setNode(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapExtension(bool value)
{
m_d->snapConfig.setExtension(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapIntersection(bool value)
{
m_d->snapConfig.setIntersection(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapBoundingBox(bool value)
{
m_d->snapConfig.setBoundingBox(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapImageBounds(bool value)
{
m_d->snapConfig.setImageBounds(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
void KisGuidesManager::setSnapImageCenter(bool value)
{
m_d->snapConfig.setImageCenter(value);
m_d->updateSnappingStatus(m_d->guidesConfig);
}
diff --git a/libs/ui/canvas/kis_guides_manager.h b/libs/ui/canvas/kis_guides_manager.h
index 0046e95fa4..fe9aede60b 100644
--- a/libs/ui/canvas/kis_guides_manager.h
+++ b/libs/ui/canvas/kis_guides_manager.h
@@ -1,81 +1,84 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_GUIDES_MANAGER_H
#define __KIS_GUIDES_MANAGER_H
#include <QScopedPointer>
#include <QObject>
#include "kritaui_export.h"
class KisView;
class KisActionManager;
class KisCanvasDecoration;
class KisGuidesConfig;
class KRITAUI_EXPORT KisGuidesManager : public QObject
{
Q_OBJECT
public:
KisGuidesManager(QObject *parent = 0);
~KisGuidesManager();
void setup(KisActionManager *actionManager);
void setView(QPointer<KisView> view);
bool showGuides() const;
bool lockGuides() const;
bool snapToGuides() const;
bool eventFilter(QObject *obj, QEvent *event);
Q_SIGNALS:
void sigRequestUpdateGuidesConfig(const KisGuidesConfig &config);
public Q_SLOTS:
void setGuidesConfig(const KisGuidesConfig &config);
void slotDocumentRequestedConfig(const KisGuidesConfig &config);
void setShowGuides(bool value);
void setLockGuides(bool value);
void setSnapToGuides(bool value);
void slotGuideCreationInProgress(Qt::Orientation orientation, const QPoint &globalPos);
void slotGuideCreationFinished(Qt::Orientation orientation, const QPoint &globalPos);
void slotShowSnapOptions();
void setSnapOrthogonal(bool value);
void setSnapNode(bool value);
void setSnapExtension(bool value);
void setSnapIntersection(bool value);
void setSnapBoundingBox(bool value);
void setSnapImageBounds(bool value);
void setSnapImageCenter(bool value);
+
+ void slotUploadConfigToDocument();
+
private:
void setGuidesConfigImpl(const KisGuidesConfig &value, bool emitModified = true);
void attachEventFilterImpl(bool value);
void syncActionsStatus();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_GUIDES_MANAGER_H */
diff --git a/libs/ui/canvas/kis_tool_proxy.cpp b/libs/ui/canvas/kis_tool_proxy.cpp
index 255511c2ae..dd0263bbd2 100644
--- a/libs/ui/canvas/kis_tool_proxy.cpp
+++ b/libs/ui/canvas/kis_tool_proxy.cpp
@@ -1,248 +1,255 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_proxy.h"
#include "kis_canvas2.h"
#include "input/kis_tablet_debugger.h"
#include <KoToolProxy_p.h>
KisToolProxy::KisToolProxy(KoCanvasBase *canvas, QObject *parent)
: KoToolProxy(canvas, parent),
m_isActionActivated(false),
m_lastAction(KisTool::Primary)
{
}
+void KisToolProxy::initializeImage(KisImageSP image)
+{
+ connect(image, SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection);
+ connect(image, SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection);
+ connect(image, SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection);
+}
+
QPointF KisToolProxy::tabletToDocument(const QPointF &globalPos)
{
const QPointF pos = globalPos - QPointF(canvas()->canvasWidget()->mapToGlobal(QPoint(0, 0)));
return widgetToDocument(pos);
}
QPointF KisToolProxy::widgetToDocument(const QPointF &widgetPoint) const
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->coordinatesConverter()->widgetToDocument(widgetPoint);
}
KoPointerEvent KisToolProxy::convertEventToPointerEvent(QEvent *event, const QPointF &docPoint, bool *result)
{
switch (event->type()) {
case QEvent::TabletPress:
case QEvent::TabletRelease:
case QEvent::TabletMove:
{
*result = true;
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
KoPointerEvent ev(tabletEvent, docPoint);
ev.setTabletButton(Qt::LeftButton);
return ev;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
case QEvent::MouseMove:
{
*result = true;
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
return KoPointerEvent(mouseEvent, docPoint);
}
default:
;
}
*result = false;
QMouseEvent fakeEvent(QEvent::MouseMove, QPoint(),
Qt::NoButton, Qt::NoButton,
Qt::NoModifier);
return KoPointerEvent(&fakeEvent, QPointF());
}
void KisToolProxy::forwardHoverEvent(QEvent *event)
{
switch (event->type()) {
case QEvent::TabletMove: {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
QPointF docPoint = widgetToDocument(tabletEvent->posF());
this->tabletEvent(tabletEvent, docPoint);
return;
}
case QEvent::MouseMove: {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
QPointF docPoint = widgetToDocument(mouseEvent->posF());
mouseMoveEvent(mouseEvent, docPoint);
return;
}
default: {
qWarning() << "forwardHoverEvent encountered unknown event type.";
return;
}
}
}
bool KisToolProxy::forwardEvent(ActionState state, KisTool::ToolAction action, QEvent *event, QEvent *originalEvent)
{
bool retval = true;
QTabletEvent *tabletEvent = dynamic_cast<QTabletEvent*>(event);
QTouchEvent *touchEvent = dynamic_cast<QTouchEvent*>(event);
QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
if (tabletEvent) {
QPointF docPoint = widgetToDocument(tabletEvent->posF());
tabletEvent->accept();
this->tabletEvent(tabletEvent, docPoint);
forwardToTool(state, action, tabletEvent, docPoint);
retval = tabletEvent->isAccepted();
}
else if (touchEvent) {
if (state == END && touchEvent->type() != QEvent::TouchEnd) {
//Fake a touch end if we are "upgrading" a single-touch gesture to a multi-touch gesture.
QTouchEvent fakeEvent(QEvent::TouchEnd, touchEvent->device(),
touchEvent->modifiers(), touchEvent->touchPointStates(),
touchEvent->touchPoints());
this->touchEvent(&fakeEvent);
} else {
this->touchEvent(touchEvent);
}
}
else if (mouseEvent) {
QPointF docPoint = widgetToDocument(mouseEvent->posF());
mouseEvent->accept();
if (mouseEvent->type() == QEvent::MouseButtonPress) {
mousePressEvent(mouseEvent, docPoint);
} else if (mouseEvent->type() == QEvent::MouseButtonDblClick) {
mouseDoubleClickEvent(mouseEvent, docPoint);
} else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
mouseReleaseEvent(mouseEvent, docPoint);
} else if (mouseEvent->type() == QEvent::MouseMove) {
mouseMoveEvent(mouseEvent, docPoint);
}
forwardToTool(state, action, originalEvent, docPoint);
retval = mouseEvent->isAccepted();
}
else if (event && event->type() == QEvent::KeyPress) {
QKeyEvent* kevent = static_cast<QKeyEvent*>(event);
keyPressEvent(kevent);
}
else if (event && event->type() == QEvent::KeyRelease) {
QKeyEvent* kevent = static_cast<QKeyEvent*>(event);
keyReleaseEvent(kevent);
}
return retval;
}
void KisToolProxy::forwardToTool(ActionState state, KisTool::ToolAction action, QEvent *event, const QPointF &docPoint)
{
bool eventValid = false;
KoPointerEvent ev = convertEventToPointerEvent(event, docPoint, &eventValid);
KisTool *activeTool = dynamic_cast<KisTool*>(priv()->activeTool);
if (!eventValid || !activeTool) return;
switch (state) {
case BEGIN:
if (action == KisTool::Primary) {
if (event->type() == QEvent::MouseButtonDblClick) {
activeTool->beginPrimaryDoubleClickAction(&ev);
} else {
activeTool->beginPrimaryAction(&ev);
}
} else {
if (event->type() == QEvent::MouseButtonDblClick) {
activeTool->beginAlternateDoubleClickAction(&ev, KisTool::actionToAlternateAction(action));
} else {
activeTool->beginAlternateAction(&ev, KisTool::actionToAlternateAction(action));
}
}
break;
case CONTINUE:
if (action == KisTool::Primary) {
activeTool->continuePrimaryAction(&ev);
} else {
activeTool->continueAlternateAction(&ev, KisTool::actionToAlternateAction(action));
}
break;
case END:
if (action == KisTool::Primary) {
activeTool->endPrimaryAction(&ev);
} else {
activeTool->endAlternateAction(&ev, KisTool::actionToAlternateAction(action));
}
break;
}
}
bool KisToolProxy::primaryActionSupportsHiResEvents() const
{
KisTool *activeTool = dynamic_cast<KisTool*>(const_cast<KisToolProxy*>(this)->priv()->activeTool);
return activeTool && activeTool->primaryActionSupportsHiResEvents();
}
void KisToolProxy::setActiveTool(KoToolBase *tool)
{
if (!tool) return;
if (m_isActionActivated) {
deactivateToolAction(m_lastAction);
KoToolProxy::setActiveTool(tool);
activateToolAction(m_lastAction);
} else {
KoToolProxy::setActiveTool(tool);
}
}
void KisToolProxy::activateToolAction(KisTool::ToolAction action)
{
KisTool *activeTool = dynamic_cast<KisTool*>(const_cast<KisToolProxy*>(this)->priv()->activeTool);
if (activeTool) {
if (action == KisTool::Primary) {
activeTool->activatePrimaryAction();
} else {
activeTool->activateAlternateAction(KisTool::actionToAlternateAction(action));
}
}
m_isActionActivated = true;
m_lastAction = action;
}
void KisToolProxy::deactivateToolAction(KisTool::ToolAction action)
{
KisTool *activeTool = dynamic_cast<KisTool*>(const_cast<KisToolProxy*>(this)->priv()->activeTool);
if (activeTool) {
if (action == KisTool::Primary) {
activeTool->deactivatePrimaryAction();
} else {
activeTool->deactivateAlternateAction(KisTool::actionToAlternateAction(action));
}
}
m_isActionActivated = false;
m_lastAction = action;
}
diff --git a/libs/ui/canvas/kis_tool_proxy.h b/libs/ui/canvas/kis_tool_proxy.h
index 08ca79dd04..97f7dfee84 100644
--- a/libs/ui/canvas/kis_tool_proxy.h
+++ b/libs/ui/canvas/kis_tool_proxy.h
@@ -1,70 +1,71 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_TOOL_PROXY_H
#define __KIS_TOOL_PROXY_H
#include <KoToolProxy.h>
#include <kis_tool.h>
class KisToolProxy : public KoToolProxy
{
public:
enum ActionState {
BEGIN, /**< Beginning an action */
CONTINUE, /**< Continuing an action */
END /**< Ending an action */
};
public:
KisToolProxy(KoCanvasBase *canvas, QObject *parent = 0);
+ void initializeImage(KisImageSP image);
void forwardHoverEvent(QEvent *event);
/**
* Forwards the event to the active tool and returns true if the
* event was not ignored. That is by default the event is
* considered accepted, but the tool can explicitly ignore it.
* @param state beginning, continuing, or ending the action.
* @param action alternate tool action requested.
* @param event the event being sent to the tool by the AbstractInputAction.
* @param originalEvent the original event received by the AbstractInputAction.
*/
bool forwardEvent(ActionState state, KisTool::ToolAction action, QEvent *event, QEvent *originalEvent);
bool primaryActionSupportsHiResEvents() const;
void setActiveTool(KoToolBase *tool);
void activateToolAction(KisTool::ToolAction action);
void deactivateToolAction(KisTool::ToolAction action);
private:
KoPointerEvent convertEventToPointerEvent(QEvent *event, const QPointF &docPoint, bool *result);
QPointF tabletToDocument(const QPointF &globalPos);
void forwardToTool(ActionState state, KisTool::ToolAction action, QEvent *event, const QPointF &docPoint);
protected:
QPointF widgetToDocument(const QPointF &widgetPoint) const;
private:
bool m_isActionActivated;
KisTool::ToolAction m_lastAction;
};
#endif /* __KIS_TOOL_PROXY_H */
diff --git a/libs/ui/dialogs/KisAnimationCacheUpdateProgressDialog.cpp b/libs/ui/dialogs/KisAnimationCacheUpdateProgressDialog.cpp
new file mode 100644
index 0000000000..f407736725
--- /dev/null
+++ b/libs/ui/dialogs/KisAnimationCacheUpdateProgressDialog.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 "KisAnimationCacheUpdateProgressDialog.h"
+
+#include <QEventLoop>
+#include <QProgressDialog>
+#include <QElapsedTimer>
+#include <QApplication>
+#include <QThread>
+#include <KisAnimationCacheRegenerator.h>
+#include "kis_animation_frame_cache.h"
+#include "kis_time_range.h"
+#include "kis_image.h"
+#include "KisViewManager.h"
+
+struct KisAnimationCacheUpdateProgressDialog::Private
+{
+ Private(int _busyWait, QWidget *parent)
+ : busyWait(_busyWait),
+ progressDialog(i18n("Regenerating cache..."), i18n("Cancel"), 0, 0, parent)
+ {
+ progressDialog.setWindowModality(Qt::ApplicationModal);
+ connect(&progressDialog, SIGNAL(canceled()), &regenerator, SLOT(cancelCurrentFrameRegeneration()));
+ }
+
+ int busyWait;
+ KisAnimationCacheRegenerator regenerator;
+
+ KisAnimationFrameCacheSP cache;
+ KisTimeRange playbackRange;
+ int dirtyFramesCount = 0;
+ int processedFramesCount = 0;
+ bool hasSomethingToDo = true;
+
+ QProgressDialog progressDialog;
+};
+
+KisAnimationCacheUpdateProgressDialog::KisAnimationCacheUpdateProgressDialog(int busyWait, QWidget *parent)
+ : QObject(parent),
+ m_d(new Private(busyWait, parent))
+{
+ connect(&m_d->regenerator, SIGNAL(sigFrameFinished()), SLOT(slotFrameFinished()));
+ connect(&m_d->regenerator, SIGNAL(sigFrameCancelled()), SLOT(slotFrameCancelled()));
+}
+
+KisAnimationCacheUpdateProgressDialog::~KisAnimationCacheUpdateProgressDialog()
+{
+}
+
+void KisAnimationCacheUpdateProgressDialog::regenerateRange(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange, KisViewManager *viewManager)
+{
+ m_d->cache = cache;
+ m_d->playbackRange = playbackRange;
+
+ m_d->dirtyFramesCount = m_d->regenerator.calcNumberOfDirtyFrame(m_d->cache, m_d->playbackRange);
+
+ m_d->progressDialog.setMaximum(m_d->dirtyFramesCount);
+
+ // HACK ALERT: since the slot is named 'finished', so it increments
+ // the preseccedFramesCount field on every call. And since
+ // this is a cold-start, we should decrement it in advance.
+ m_d->processedFramesCount = -1;
+ slotFrameFinished();
+
+ QElapsedTimer t;
+ t.start();
+
+ while (t.elapsed() < m_d->busyWait) {
+ QApplication::processEvents();
+
+ if (!m_d->hasSomethingToDo) {
+ break;
+ }
+
+ QThread::yieldCurrentThread();
+ }
+
+
+ if (m_d->hasSomethingToDo) {
+ m_d->progressDialog.exec();
+ }
+
+ KisImageSP image = cache->image();
+ viewManager->blockUntillOperationsFinishedForced(image);
+}
+
+void KisAnimationCacheUpdateProgressDialog::slotFrameFinished()
+{
+ m_d->processedFramesCount++;
+ int currentDirtyFrame = m_d->regenerator.calcFirstDirtyFrame(m_d->cache, m_d->playbackRange, KisTimeRange());
+
+ if (currentDirtyFrame >= 0) {
+ m_d->regenerator.startFrameRegeneration(currentDirtyFrame, m_d->cache);
+ } else {
+ m_d->hasSomethingToDo = false;
+ m_d->processedFramesCount = m_d->dirtyFramesCount;
+ }
+
+ m_d->progressDialog.setValue(m_d->processedFramesCount);
+}
+
+void KisAnimationCacheUpdateProgressDialog::slotFrameCancelled()
+{
+ m_d->hasSomethingToDo = false;
+ m_d->processedFramesCount = m_d->dirtyFramesCount;
+ m_d->progressDialog.setValue(m_d->processedFramesCount);
+}
+
diff --git a/libs/ui/dialogs/KisAnimationCacheUpdateProgressDialog.h b/libs/ui/dialogs/KisAnimationCacheUpdateProgressDialog.h
new file mode 100644
index 0000000000..5505d3953b
--- /dev/null
+++ b/libs/ui/dialogs/KisAnimationCacheUpdateProgressDialog.h
@@ -0,0 +1,51 @@
+/*
+ * 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 KISANIMATIONCACHEUPDATEPROGRESSDIALOG_H
+#define KISANIMATIONCACHEUPDATEPROGRESSDIALOG_H
+
+#include <QObject>
+#include <QScopedPointer>
+
+#include "kis_types.h"
+
+class KisTimeRange;
+class KisViewManager;
+
+
+class KisAnimationCacheUpdateProgressDialog : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisAnimationCacheUpdateProgressDialog(int busyWait = 200, QWidget *parent = 0);
+ ~KisAnimationCacheUpdateProgressDialog();
+
+ void regenerateRange(KisAnimationFrameCacheSP cache, const KisTimeRange &playbackRange, KisViewManager *viewManager);
+
+Q_SIGNALS:
+
+public Q_SLOTS:
+ void slotFrameFinished();
+ void slotFrameCancelled();
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KISANIMATIONCACHEUPDATEPROGRESSDIALOG_H
diff --git a/libs/ui/dialogs/kis_dlg_filter.cpp b/libs/ui/dialogs/kis_dlg_filter.cpp
index 15793d617d..17284fa533 100644
--- a/libs/ui/dialogs/kis_dlg_filter.cpp
+++ b/libs/ui/dialogs/kis_dlg_filter.cpp
@@ -1,237 +1,239 @@
/*
* 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 <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"
struct KisDlgFilter::Private {
Private()
: currentFilter(0)
, resizeCount(0)
, view(0)
{
}
KisFilterSP currentFilter;
Ui_FilterDialog uiFilterDialog;
KisNodeSP node;
int resizeCount;
KisViewManager *view;
KisFilterManager *filterManager;
};
KisDlgFilter::KisDlgFilter(KisViewManager *view, KisNodeSP node, KisFilterManager *filterManager, QWidget *parent) :
QDialog(parent),
d(new Private)
{
setModal(false);
d->uiFilterDialog.setupUi(this);
d->node = node;
d->view = view;
d->filterManager = filterManager;
d->uiFilterDialog.filterSelection->setView(view);
d->uiFilterDialog.filterSelection->showFilterGallery(KisConfig().showFilterGallery());
d->uiFilterDialog.pushButtonCreateMaskEffect->show();
connect(d->uiFilterDialog.pushButtonCreateMaskEffect, SIGNAL(pressed()), SLOT(createMask()));
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(rejected()), SLOT(slotOnReject()));
KConfigGroup group( KSharedConfig::openConfig(), "filterdialog");
d->uiFilterDialog.checkBoxPreview->setChecked(group.readEntry("showPreview", true));
restoreGeometry(KisConfig().readEntry("filterdialog/geometry", QByteArray()));
}
KisDlgFilter::~KisDlgFilter()
{
KisConfig().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("KisLayer")) {
config->setChannelFlags(qobject_cast<KisLayer*>(d->node.data())->channelFlags());
}
d->filterManager->apply(config);
}
void KisDlgFilter::updatePreview()
{
if (!d->uiFilterDialog.filterSelection->configuration()) return;
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().setShowFilterGallery(d->uiFilterDialog.filterSelection->isFilterGalleryVisible());
}
void KisDlgFilter::slotOnReject()
{
if (d->filterManager->isStrokeRunning()) {
d->filterManager->cancel();
}
KisConfig().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());
accept();
}
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->uiFilterDialog.pushButtonCreateMaskEffect->setEnabled(filter.isNull() ? false : filter->supportsAdjustmentLayers());
updatePreview();
}
void KisDlgFilter::resizeEvent(QResizeEvent* event)
{
QDialog::resizeEvent(event);
// // Workaround, after the initalisation 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_preferences.cc b/libs/ui/dialogs/kis_dlg_preferences.cc
index d021935844..4b72ddc9d1 100644
--- a/libs/ui/dialogs/kis_dlg_preferences.cc
+++ b/libs/ui/dialogs/kis_dlg_preferences.cc
@@ -1,1120 +1,1128 @@
/*
* preferencesdlg.cc - part of KImageShop
*
* Copyright (c) 1999 Michael Koch <koch@kde.org>
* Copyright (c) 2003-2011 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_dlg_preferences.h"
#include <opengl/kis_opengl.h>
#include <QBitmap>
#include <QCheckBox>
#include <QCursor>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QSlider>
#include <QToolButton>
#include <QThread>
#include <QDesktopServices>
#include <QGridLayout>
#include <QRadioButton>
#include <QGroupBox>
#include <QMdiArea>
#include <QMessageBox>
#include <QDesktopWidget>
#include <QFileDialog>
#include <QFormLayout>
#include <QSettings>
#include <KisDocument.h>
#include <KoColorProfile.h>
#include <KisApplication.h>
#include <KoFileDialog.h>
#include <KisPart.h>
#include <KoColorSpaceEngine.h>
#include <kis_icon.h>
#include <KoConfig.h>
#include "KoID.h"
#include <KoConfigAuthorPage.h>
#include <KoVBox.h>
#include <klocalizedstring.h>
#include <kundo2stack.h>
#include <KoResourcePaths.h>
#include "kis_action_registry.h"
#include "widgets/squeezedcombobox.h"
#include "kis_clipboard.h"
#include "widgets/kis_cmb_idlist.h"
#include "KoColorSpace.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorConversionTransformation.h"
#include "kis_cursor.h"
#include "kis_config.h"
#include "kis_canvas_resource_provider.h"
#include "kis_preference_set_registry.h"
#include "kis_color_manager.h"
#include "KisProofingConfiguration.h"
#include "kis_image_config.h"
#include "slider_and_spin_box_sync.h"
// for the performance update
#include <kis_cubic_curve.h>
#include "input/config/kis_input_configuration_page.h"
GeneralTab::GeneralTab(QWidget *_parent, const char *_name)
: WdgGeneralSettings(_parent, _name)
{
KisConfig cfg;
m_cmbCursorShape->addItem(i18n("No Cursor"));
m_cmbCursorShape->addItem(i18n("Tool Icon"));
m_cmbCursorShape->addItem(i18n("Arrow"));
m_cmbCursorShape->addItem(i18n("Small Circle"));
m_cmbCursorShape->addItem(i18n("Crosshair"));
m_cmbCursorShape->addItem(i18n("Triangle Righthanded"));
m_cmbCursorShape->addItem(i18n("Triangle Lefthanded"));
m_cmbCursorShape->addItem(i18n("Black Pixel"));
m_cmbCursorShape->addItem(i18n("White Pixel"));
m_cmbOutlineShape->addItem(i18n("No Outline"));
m_cmbOutlineShape->addItem(i18n("Circle Outline"));
m_cmbOutlineShape->addItem(i18n("Preview Outline"));
m_cmbOutlineShape->addItem(i18n("Tilt Outline"));
m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle());
m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle());
chkShowRootLayer->setChecked(cfg.showRootLayer());
int autosaveInterval = cfg.autoSaveInterval();
//convert to minutes
m_autosaveSpinBox->setValue(autosaveInterval / 60);
m_autosaveCheckBox->setChecked(autosaveInterval > 0);
m_undoStackSize->setValue(cfg.undoStackLimit());
m_backupFileCheckBox->setChecked(cfg.backupFile());
m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting());
m_hideSplashScreen->setChecked(cfg.hideSplashScreen());
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
m_chkNativeFileDialog->setChecked(!group.readEntry("DontUseNativeFileDialog", true));
intMaxBrushSize->setValue(cfg.readEntry("maximumBrushSize", 1000));
m_cmbMDIType->setCurrentIndex(cfg.readEntry<int>("mdi_viewmode", (int)QMdiArea::TabbedView));
m_chkRubberBand->setChecked(cfg.readEntry<int>("mdi_rubberband", cfg.useOpenGL()));
m_favoritePresetsSpinBox->setValue(cfg.favoritePresets());
KoColor mdiColor;
mdiColor.fromQColor(cfg.getMDIBackgroundColor());
m_mdiColor->setColor(mdiColor);
m_backgroundimage->setText(cfg.getMDIBackgroundImage());
m_chkCanvasMessages->setChecked(cfg.showCanvasMessages());
m_chkCompressKra->setChecked(cfg.compressKra());
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
m_chkHiDPI->setChecked(kritarc.value("EnableHiDPI", false).toBool());
m_chkSingleApplication->setChecked(kritarc.value("EnableSingleApplication", true).toBool());
m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker());
m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt());
m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport());
+ m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground());
connect(m_bnFileName, SIGNAL(clicked()), SLOT(getBackgroundImage()));
connect(clearBgImageButton, SIGNAL(clicked()), SLOT(clearBackgroundImage()));
}
void GeneralTab::setDefault()
{
KisConfig cfg;
m_cmbCursorShape->setCurrentIndex(cfg.newCursorStyle(true));
m_cmbOutlineShape->setCurrentIndex(cfg.newOutlineStyle(true));
chkShowRootLayer->setChecked(cfg.showRootLayer(true));
m_autosaveCheckBox->setChecked(cfg.autoSaveInterval(true) > 0);
//convert to minutes
m_autosaveSpinBox->setValue(cfg.autoSaveInterval(true) / 60);
m_undoStackSize->setValue(cfg.undoStackLimit(true));
m_backupFileCheckBox->setChecked(cfg.backupFile(true));
m_showOutlinePainting->setChecked(cfg.showOutlineWhilePainting(true));
m_hideSplashScreen->setChecked(cfg.hideSplashScreen(true));
m_chkNativeFileDialog->setChecked(false);
intMaxBrushSize->setValue(1000);
m_cmbMDIType->setCurrentIndex((int)QMdiArea::TabbedView);
m_chkRubberBand->setChecked(cfg.useOpenGL(true));
m_favoritePresetsSpinBox->setValue(cfg.favoritePresets(true));
KoColor mdiColor;
mdiColor.fromQColor(cfg.getMDIBackgroundColor(true));
m_mdiColor->setColor(mdiColor);
m_backgroundimage->setText(cfg.getMDIBackgroundImage(true));
m_chkCanvasMessages->setChecked(cfg.showCanvasMessages(true));
m_chkCompressKra->setChecked(cfg.compressKra(true));
m_chkHiDPI->setChecked(false);
m_chkSingleApplication->setChecked(true);
m_chkHiDPI->setChecked(true);
m_radioToolOptionsInDocker->setChecked(cfg.toolOptionsInDocker(true));
m_chkSwitchSelectionCtrlAlt->setChecked(cfg.switchSelectionCtrlAlt(true));
m_chkConvertOnImport->setChecked(cfg.convertToImageColorspaceOnImport(true));
+ m_chkCacheAnimatioInBackground->setChecked(cfg.calculateAnimationCacheInBackground(true));
}
CursorStyle GeneralTab::cursorStyle()
{
return (CursorStyle)m_cmbCursorShape->currentIndex();
}
OutlineStyle GeneralTab::outlineStyle()
{
return (OutlineStyle)m_cmbOutlineShape->currentIndex();
}
bool GeneralTab::showRootLayer()
{
return chkShowRootLayer->isChecked();
}
int GeneralTab::autoSaveInterval()
{
//convert to seconds
return m_autosaveCheckBox->isChecked() ? m_autosaveSpinBox->value() * 60 : 0;
}
int GeneralTab::undoStackSize()
{
return m_undoStackSize->value();
}
bool GeneralTab::showOutlineWhilePainting()
{
return m_showOutlinePainting->isChecked();
}
bool GeneralTab::hideSplashScreen()
{
return m_hideSplashScreen->isChecked();
}
int GeneralTab::mdiMode()
{
return m_cmbMDIType->currentIndex();
}
int GeneralTab::favoritePresets()
{
return m_favoritePresetsSpinBox->value();
}
bool GeneralTab::showCanvasMessages()
{
return m_chkCanvasMessages->isChecked();
}
bool GeneralTab::compressKra()
{
return m_chkCompressKra->isChecked();
}
bool GeneralTab::toolOptionsInDocker()
{
return m_radioToolOptionsInDocker->isChecked();
}
bool GeneralTab::switchSelectionCtrlAlt()
{
return m_chkSwitchSelectionCtrlAlt->isChecked();
}
bool GeneralTab::convertToImageColorspaceOnImport()
{
return m_chkConvertOnImport->isChecked();
}
+bool GeneralTab::calculateAnimationCacheInBackground()
+{
+ return m_chkCacheAnimatioInBackground->isChecked();
+}
+
void GeneralTab::getBackgroundImage()
{
KoFileDialog dialog(this, KoFileDialog::OpenFile, "BackgroundImages");
dialog.setCaption(i18n("Select a Background Image"));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setImageFilters();
QString fn = dialog.filename();
// dialog box was canceled or somehow no file was selected
if (fn.isEmpty()) {
return;
}
QImage image(fn);
if (image.isNull()) {
QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("%1 is not a valid image file!", fn));
}
else {
m_backgroundimage->setText(fn);
}
}
void GeneralTab::clearBackgroundImage()
{
// clearing the background image text will implicitly make the background color be used
m_backgroundimage->setText("");
}
#include "kactioncollection.h"
#include "KisActionsSnapshot.h"
ShortcutSettingsTab::ShortcutSettingsTab(QWidget *parent, const char *name)
: QWidget(parent)
{
setObjectName(name);
QGridLayout * l = new QGridLayout(this);
l->setMargin(0);
m_page = new WdgShortcutSettings(this);
l->addWidget(m_page, 0, 0);
m_snapshot.reset(new KisActionsSnapshot);
KActionCollection *collection =
KisPart::instance()->currentMainwindow()->actionCollection();
Q_FOREACH (QAction *action, collection->actions()) {
m_snapshot->addAction(action->objectName(), action);
}
QMap<QString, KActionCollection*> sortedCollections =
m_snapshot->actionCollections();
for (auto it = sortedCollections.constBegin(); it != sortedCollections.constEnd(); ++it) {
m_page->addCollection(it.value(), it.key());
}
}
ShortcutSettingsTab::~ShortcutSettingsTab()
{
}
void ShortcutSettingsTab::setDefault()
{
m_page->allDefault();
}
void ShortcutSettingsTab::saveChanges()
{
m_page->save();
KisActionRegistry::instance()->settingsPageSaved();
}
void ShortcutSettingsTab::cancelChanges()
{
m_page->undo();
}
ColorSettingsTab::ColorSettingsTab(QWidget *parent, const char *name)
: QWidget(parent)
{
setObjectName(name);
// XXX: Make sure only profiles that fit the specified color model
// are shown in the profile combos
QGridLayout * l = new QGridLayout(this);
l->setMargin(0);
m_page = new WdgColorSettings(this);
l->addWidget(m_page, 0, 0);
KisConfig cfg;
m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile());
connect(m_page->chkUseSystemMonitorProfile, SIGNAL(toggled(bool)), this, SLOT(toggleAllowMonitorProfileSelection(bool)));
m_page->cmbWorkingColorSpace->setIDList(KoColorSpaceRegistry::instance()->listKeys());
m_page->cmbWorkingColorSpace->setCurrent(cfg.workingColorSpace());
m_page->bnAddColorProfile->setIcon(KisIconUtils::loadIcon("document-open"));
m_page->bnAddColorProfile->setToolTip( i18n("Open Color Profile") );
connect(m_page->bnAddColorProfile, SIGNAL(clicked()), SLOT(installProfile()));
QFormLayout *monitorProfileGrid = new QFormLayout(m_page->monitorprofileholder);
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
QLabel *lbl = new QLabel(i18nc("The number of the screen", "Screen %1:", i + 1));
m_monitorProfileLabels << lbl;
SqueezedComboBox *cmb = new SqueezedComboBox();
cmb->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
monitorProfileGrid->addRow(lbl, cmb);
m_monitorProfileWidgets << cmb;
}
refillMonitorProfiles(KoID("RGBA", ""));
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) {
m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i));
}
}
m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation());
m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization());
KisImageConfig cfgImage;
KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration();
m_page->sldAdaptationState->setMaximum(20);
m_page->sldAdaptationState->setMinimum(0);
m_page->sldAdaptationState->setValue((int)proofingConfig->adaptationState*20);
//probably this should become the screenprofile?
KoColor ga(KoColorSpaceRegistry::instance()->rgb8());
ga.fromKoColor(proofingConfig->warningColor);
m_page->gamutAlarm->setColor(ga);
const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,
proofingConfig->proofingDepth,
proofingConfig->proofingProfile);
if (proofingSpace) {
m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace);
}
m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent);
m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation));
m_pasteBehaviourGroup.addButton(m_page->radioPasteWeb, PASTE_ASSUME_WEB);
m_pasteBehaviourGroup.addButton(m_page->radioPasteMonitor, PASTE_ASSUME_MONITOR);
m_pasteBehaviourGroup.addButton(m_page->radioPasteAsk, PASTE_ASK);
QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour());
Q_ASSERT(button);
if (button) {
button->setChecked(true);
}
m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent());
toggleAllowMonitorProfileSelection(cfg.useSystemMonitorProfile());
}
void ColorSettingsTab::installProfile()
{
KoFileDialog dialog(this, KoFileDialog::OpenFiles, "OpenDocumentICC");
dialog.setCaption(i18n("Install Color Profiles"));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation));
dialog.setMimeTypeFilters(QStringList() << "application/vnd.iccprofile", "application/vnd.iccprofile");
QStringList profileNames = dialog.filenames();
KoColorSpaceEngine *iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
Q_ASSERT(iccEngine);
QString saveLocation = KoResourcePaths::saveLocation("icc_profiles");
Q_FOREACH (const QString &profileName, profileNames) {
if (!QFile::copy(profileName, saveLocation + QFileInfo(profileName).fileName())) {
qWarning() << "Could not install profile!" << saveLocation + QFileInfo(profileName).fileName();
continue;
}
iccEngine->addProfile(saveLocation + QFileInfo(profileName).fileName());
}
KisConfig cfg;
refillMonitorProfiles(KoID("RGBA", ""));
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) {
m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i));
}
}
}
void ColorSettingsTab::toggleAllowMonitorProfileSelection(bool useSystemProfile)
{
if (useSystemProfile) {
KisConfig cfg;
QStringList devices = KisColorManager::instance()->devices();
if (devices.size() == QApplication::desktop()->screenCount()) {
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileWidgets[i]->clear();
QString monitorForScreen = cfg.monitorForScreen(i, devices[i]);
Q_FOREACH (const QString &device, devices) {
m_monitorProfileLabels[i]->setText(i18nc("The display/screen we got from Qt", "Screen %1:", i + 1));
m_monitorProfileWidgets[i]->addSqueezedItem(KisColorManager::instance()->deviceName(device), device);
if (devices[i] == monitorForScreen) {
m_monitorProfileWidgets[i]->setCurrentIndex(i);
}
}
}
}
}
else {
KisConfig cfg;
refillMonitorProfiles(KoID("RGBA", ""));
for(int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (m_monitorProfileWidgets[i]->contains(cfg.monitorProfile(i))) {
m_monitorProfileWidgets[i]->setCurrent(cfg.monitorProfile(i));
}
}
}
}
void ColorSettingsTab::setDefault()
{
m_page->cmbWorkingColorSpace->setCurrent("RGBA");
refillMonitorProfiles(KoID("RGBA", ""));
KisConfig cfg;
KisImageConfig cfgImage;
KisProofingConfigurationSP proofingConfig = cfgImage.defaultProofingconfiguration();
const KoColorSpace *proofingSpace = KoColorSpaceRegistry::instance()->colorSpace(proofingConfig->proofingModel,proofingConfig->proofingDepth,proofingConfig->proofingProfile);
if (proofingSpace) {
m_page->proofingSpaceSelector->setCurrentColorSpace(proofingSpace);
}
m_page->cmbProofingIntent->setCurrentIndex((int)proofingConfig->intent);
m_page->ckbProofBlackPoint->setChecked(proofingConfig->conversionFlags.testFlag(KoColorConversionTransformation::BlackpointCompensation));
m_page->sldAdaptationState->setValue(0);
//probably this should become the screenprofile?
KoColor ga(KoColorSpaceRegistry::instance()->rgb8());
ga.fromKoColor(proofingConfig->warningColor);
m_page->gamutAlarm->setColor(ga);
m_page->chkBlackpoint->setChecked(cfg.useBlackPointCompensation(true));
m_page->chkAllowLCMSOptimization->setChecked(cfg.allowLCMSOptimization(true));
m_page->cmbMonitorIntent->setCurrentIndex(cfg.monitorRenderIntent(true));
m_page->chkUseSystemMonitorProfile->setChecked(cfg.useSystemMonitorProfile(true));
QAbstractButton *button = m_pasteBehaviourGroup.button(cfg.pasteBehaviour(true));
Q_ASSERT(button);
if (button) {
button->setChecked(true);
}
}
void ColorSettingsTab::refillMonitorProfiles(const KoID & s)
{
const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s.id());
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileWidgets[i]->clear();
}
if (!csf)
return;
QMap<QString, const KoColorProfile *> profileList;
Q_FOREACH(const KoColorProfile *profile, KoColorSpaceRegistry::instance()->profilesFor(csf)) {
profileList[profile->name()] = profile;
}
Q_FOREACH (const KoColorProfile *profile, profileList.values()) {
//qDebug() << "Profile" << profile->name() << profile->isSuitableForDisplay() << csf->defaultProfile();
if (profile->isSuitableForDisplay()) {
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileWidgets[i]->addSqueezedItem(profile->name());
}
}
}
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
m_monitorProfileLabels[i]->setText(i18nc("The number of the screen", "Screen %1:", i + 1));
m_monitorProfileWidgets[i]->setCurrent(csf->defaultProfile());
}
}
//---------------------------------------------------------------------------------------------------
void TabletSettingsTab::setDefault()
{
KisCubicCurve curve;
curve.fromString(DEFAULT_CURVE_STRING);
m_page->pressureCurve->setCurve(curve);
}
TabletSettingsTab::TabletSettingsTab(QWidget* parent, const char* name): QWidget(parent)
{
setObjectName(name);
QGridLayout * l = new QGridLayout(this);
l->setMargin(0);
m_page = new WdgTabletSettings(this);
l->addWidget(m_page, 0, 0);
KisConfig cfg;
KisCubicCurve curve;
curve.fromString( cfg.pressureTabletCurve() );
m_page->pressureCurve->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
m_page->pressureCurve->setCurve(curve);
}
//---------------------------------------------------------------------------------------------------
#include "kis_acyclic_signal_connector.h"
int getTotalRAM() {
KisImageConfig cfg;
return cfg.totalRAM();
}
int PerformanceTab::realTilesRAM()
{
return intMemoryLimit->value() - intPoolLimit->value();
}
PerformanceTab::PerformanceTab(QWidget *parent, const char *name)
: WdgPerformanceSettings(parent, name)
{
KisImageConfig cfg;
const int totalRAM = cfg.totalRAM();
lblTotalMemory->setText(i18n("%1 MiB", totalRAM));
sliderMemoryLimit->setSuffix(i18n(" %"));
sliderMemoryLimit->setRange(1, 100, 2);
sliderMemoryLimit->setSingleStep(0.01);
sliderPoolLimit->setSuffix(i18n(" %"));
sliderPoolLimit->setRange(0, 20, 2);
sliderMemoryLimit->setSingleStep(0.01);
sliderUndoLimit->setSuffix(i18n(" %"));
sliderUndoLimit->setRange(0, 50, 2);
sliderMemoryLimit->setSingleStep(0.01);
intMemoryLimit->setMinimumWidth(80);
intPoolLimit->setMinimumWidth(80);
intUndoLimit->setMinimumWidth(80);
SliderAndSpinBoxSync *sync1 =
new SliderAndSpinBoxSync(sliderMemoryLimit,
intMemoryLimit,
getTotalRAM);
sync1->slotParentValueChanged();
m_syncs << sync1;
SliderAndSpinBoxSync *sync2 =
new SliderAndSpinBoxSync(sliderPoolLimit,
intPoolLimit,
std::bind(&KisIntParseSpinBox::value,
intMemoryLimit));
connect(intMemoryLimit, SIGNAL(valueChanged(int)), sync2, SLOT(slotParentValueChanged()));
sync2->slotParentValueChanged();
m_syncs << sync2;
SliderAndSpinBoxSync *sync3 =
new SliderAndSpinBoxSync(sliderUndoLimit,
intUndoLimit,
std::bind(&PerformanceTab::realTilesRAM,
this));
connect(intPoolLimit, SIGNAL(valueChanged(int)), sync3, SLOT(slotParentValueChanged()));
sync3->slotParentValueChanged();
m_syncs << sync3;
sliderSwapSize->setSuffix(i18n(" GiB"));
sliderSwapSize->setRange(1, 64);
intSwapSize->setRange(1, 64);
KisAcyclicSignalConnector *swapSizeConnector = new KisAcyclicSignalConnector(this);
swapSizeConnector->connectForwardInt(sliderSwapSize, SIGNAL(valueChanged(int)),
intSwapSize, SLOT(setValue(int)));
swapSizeConnector->connectBackwardInt(intSwapSize, SIGNAL(valueChanged(int)),
sliderSwapSize, SLOT(setValue(int)));
lblSwapFileLocation->setText(cfg.swapDir());
connect(bnSwapFile, SIGNAL(clicked()), SLOT(selectSwapDir()));
load(false);
}
PerformanceTab::~PerformanceTab()
{
qDeleteAll(m_syncs);
}
void PerformanceTab::load(bool requestDefault)
{
KisImageConfig cfg;
sliderMemoryLimit->setValue(cfg.memoryHardLimitPercent(requestDefault));
sliderPoolLimit->setValue(cfg.memoryPoolLimitPercent(requestDefault));
sliderUndoLimit->setValue(cfg.memorySoftLimitPercent(requestDefault));
chkPerformanceLogging->setChecked(cfg.enablePerfLog(requestDefault));
chkProgressReporting->setChecked(cfg.enableProgressReporting(requestDefault));
sliderSwapSize->setValue(cfg.maxSwapSize(requestDefault) / 1024);
lblSwapFileLocation->setText(cfg.swapDir(requestDefault));
{
KisConfig cfg2;
chkOpenGLLogging->setChecked(cfg2.enableOpenGLDebugging(requestDefault));
chkDisableVectorOptimizations->setChecked(cfg2.enableAmdVectorizationWorkaround(requestDefault));
}
}
void PerformanceTab::save()
{
KisImageConfig cfg;
cfg.setMemoryHardLimitPercent(sliderMemoryLimit->value());
cfg.setMemorySoftLimitPercent(sliderUndoLimit->value());
cfg.setMemoryPoolLimitPercent(sliderPoolLimit->value());
cfg.setEnablePerfLog(chkPerformanceLogging->isChecked());
cfg.setEnableProgressReporting(chkProgressReporting->isChecked());
cfg.setMaxSwapSize(sliderSwapSize->value() * 1024);
cfg.setSwapDir(lblSwapFileLocation->text());
{
KisConfig cfg2;
cfg2.setEnableOpenGLDebugging(chkOpenGLLogging->isChecked());
cfg2.setEnableAmdVectorizationWorkaround(chkDisableVectorOptimizations->isChecked());
}
}
void PerformanceTab::selectSwapDir()
{
KisImageConfig cfg;
QString swapDir = cfg.swapDir();
swapDir = QFileDialog::getExistingDirectory(0, i18nc("@title:window", "Select a swap directory"), swapDir);
lblSwapFileLocation->setText(swapDir);
}
//---------------------------------------------------------------------------------------------------
#include "KoColor.h"
DisplaySettingsTab::DisplaySettingsTab(QWidget *parent, const char *name)
: WdgDisplaySettings(parent, name)
{
KisConfig cfg;
if (!KisOpenGL::hasOpenGL()) {
grpOpenGL->setEnabled(false);
grpOpenGL->setChecked(false);
chkUseTextureBuffer->setEnabled(false);
chkDisableVsync->setEnabled(false);
cmbFilterMode->setEnabled(false);
} else {
grpOpenGL->setEnabled(true);
grpOpenGL->setChecked(cfg.useOpenGL());
chkUseTextureBuffer->setEnabled(cfg.useOpenGL());
chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer());
chkDisableVsync->setVisible(cfg.showAdvancedOpenGLSettings());
chkDisableVsync->setEnabled(cfg.useOpenGL());
chkDisableVsync->setChecked(cfg.disableVSync());
cmbFilterMode->setEnabled(cfg.useOpenGL());
cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode());
// Don't show the high quality filtering mode if it's not available
if (!KisOpenGL::supportsLoD()) {
cmbFilterMode->removeItem(3);
}
}
if (qApp->applicationName() == "kritasketch" || qApp->applicationName() == "kritagemini") {
grpOpenGL->setVisible(false);
grpOpenGL->setMaximumHeight(0);
}
KoColor c;
c.fromQColor(cfg.selectionOverlayMaskColor());
c.setOpacity(1.0);
btnSelectionOverlayColor->setColor(c);
sldSelectionOverlayOpacity->setRange(0.0, 1.0, 2);
sldSelectionOverlayOpacity->setSingleStep(0.05);
sldSelectionOverlayOpacity->setValue(cfg.selectionOverlayMaskColor().alphaF());
intCheckSize->setValue(cfg.checkSize());
chkMoving->setChecked(cfg.scrollCheckers());
KoColor ck1(KoColorSpaceRegistry::instance()->rgb8());
ck1.fromQColor(cfg.checkersColor1());
colorChecks1->setColor(ck1);
KoColor ck2(KoColorSpaceRegistry::instance()->rgb8());
ck2.fromQColor(cfg.checkersColor2());
colorChecks2->setColor(ck2);
KoColor cb(KoColorSpaceRegistry::instance()->rgb8());
cb.fromQColor(cfg.canvasBorderColor());
canvasBorder->setColor(cb);
hideScrollbars->setChecked(cfg.hideScrollbars());
chkCurveAntialiasing->setChecked(cfg.antialiasCurves());
chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline());
chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor());
chkHidePopups->setChecked(cfg.hidePopups());
connect(grpOpenGL, SIGNAL(toggled(bool)), SLOT(slotUseOpenGLToggled(bool)));
}
void DisplaySettingsTab::setDefault()
{
KisConfig cfg;
if (!KisOpenGL::hasOpenGL()) {
grpOpenGL->setEnabled(false);
grpOpenGL->setChecked(false);
chkUseTextureBuffer->setEnabled(false);
chkDisableVsync->setEnabled(false);
cmbFilterMode->setEnabled(false);
}
else {
grpOpenGL->setEnabled(true);
grpOpenGL->setChecked(cfg.useOpenGL(true));
chkUseTextureBuffer->setChecked(cfg.useOpenGLTextureBuffer(true));
chkUseTextureBuffer->setEnabled(true);
chkDisableVsync->setEnabled(true);
chkDisableVsync->setChecked(cfg.disableVSync(true));
cmbFilterMode->setEnabled(true);
cmbFilterMode->setCurrentIndex(cfg.openGLFilteringMode(true));
}
chkMoving->setChecked(cfg.scrollCheckers(true));
intCheckSize->setValue(cfg.checkSize(true));
KoColor ck1(KoColorSpaceRegistry::instance()->rgb8());
ck1.fromQColor(cfg.checkersColor1(true));
colorChecks1->setColor(ck1);
KoColor ck2(KoColorSpaceRegistry::instance()->rgb8());
ck2.fromQColor(cfg.checkersColor2(true));
colorChecks2->setColor(ck2);
KoColor cvb(KoColorSpaceRegistry::instance()->rgb8());
cvb.fromQColor(cfg.canvasBorderColor(true));
canvasBorder->setColor(cvb);
hideScrollbars->setChecked(cfg.hideScrollbars(true));
chkCurveAntialiasing->setChecked(cfg.antialiasCurves(true));
chkSelectionOutlineAntialiasing->setChecked(cfg.antialiasSelectionOutline(true));
chkChannelsAsColor->setChecked(cfg.showSingleChannelAsColor(true));
chkHidePopups->setChecked(cfg.hidePopups(true));
}
void DisplaySettingsTab::slotUseOpenGLToggled(bool isChecked)
{
chkUseTextureBuffer->setEnabled(isChecked);
chkDisableVsync->setEnabled(isChecked);
cmbFilterMode->setEnabled(isChecked);
}
//---------------------------------------------------------------------------------------------------
FullscreenSettingsTab::FullscreenSettingsTab(QWidget* parent) : WdgFullscreenSettingsBase(parent)
{
KisConfig cfg;
chkDockers->setChecked(cfg.hideDockersFullscreen());
chkMenu->setChecked(cfg.hideMenuFullscreen());
chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen());
chkStatusbar->setChecked(cfg.hideStatusbarFullscreen());
chkTitlebar->setChecked(cfg.hideTitlebarFullscreen());
chkToolbar->setChecked(cfg.hideToolbarFullscreen());
}
void FullscreenSettingsTab::setDefault()
{
KisConfig cfg;
chkDockers->setChecked(cfg.hideDockersFullscreen(true));
chkMenu->setChecked(cfg.hideMenuFullscreen(true));
chkScrollbars->setChecked(cfg.hideScrollbarsFullscreen(true));
chkStatusbar->setChecked(cfg.hideStatusbarFullscreen(true));
chkTitlebar->setChecked(cfg.hideTitlebarFullscreen(true));
chkToolbar->setChecked(cfg.hideToolbarFullscreen(true));
}
//---------------------------------------------------------------------------------------------------
KisDlgPreferences::KisDlgPreferences(QWidget* parent, const char* name)
: KPageDialog(parent)
{
Q_UNUSED(name);
setWindowTitle(i18n("Configure Krita"));
setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
button(QDialogButtonBox::Ok)->setDefault(true);
setFaceType(KPageDialog::List);
// General
KoVBox *vbox = new KoVBox();
KPageWidgetItem *page = new KPageWidgetItem(vbox, i18n("General"));
page->setObjectName("general");
page->setHeader(i18n("General"));
page->setIcon(KisIconUtils::loadIcon("go-home"));
addPage(page);
m_general = new GeneralTab(vbox);
// Shortcuts
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Keyboard Shortcuts"));
page->setObjectName("shortcuts");
page->setHeader(i18n("Shortcuts"));
page->setIcon(KisIconUtils::loadIcon("document-export"));
addPage(page);
m_shortcutSettings = new ShortcutSettingsTab(vbox);
connect(this, SIGNAL(accepted()), m_shortcutSettings, SLOT(saveChanges()));
connect(this, SIGNAL(rejected()), m_shortcutSettings, SLOT(cancelChanges()));
// Canvas input settings
m_inputConfiguration = new KisInputConfigurationPage();
page = addPage(m_inputConfiguration, i18n("Canvas Input Settings"));
page->setHeader(i18n("Canvas Input"));
page->setObjectName("canvasinput");
page->setIcon(KisIconUtils::loadIcon("configure"));
// Display
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Display"));
page->setObjectName("display");
page->setHeader(i18n("Display"));
page->setIcon(KisIconUtils::loadIcon("preferences-desktop-display"));
addPage(page);
m_displaySettings = new DisplaySettingsTab(vbox);
// Color
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Color Management"));
page->setObjectName("colormanagement");
page->setHeader(i18n("Color"));
page->setIcon(KisIconUtils::loadIcon("preferences-desktop-color"));
addPage(page);
m_colorSettings = new ColorSettingsTab(vbox);
// Performance
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Performance"));
page->setObjectName("performance");
page->setHeader(i18n("Performance"));
page->setIcon(KisIconUtils::loadIcon("applications-system"));
addPage(page);
m_performanceSettings = new PerformanceTab(vbox);
// Tablet
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Tablet settings"));
page->setObjectName("tablet");
page->setHeader(i18n("Tablet"));
page->setIcon(KisIconUtils::loadIcon("document-edit"));
addPage(page);
m_tabletSettings = new TabletSettingsTab(vbox);
// full-screen mode
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, i18n("Canvas-only settings"));
page->setObjectName("canvasonly");
page->setHeader(i18n("Canvas-only"));
page->setIcon(KisIconUtils::loadIcon("folder-pictures"));
addPage(page);
m_fullscreenSettings = new FullscreenSettingsTab(vbox);
// Author profiles
m_authorPage = new KoConfigAuthorPage();
page = addPage(m_authorPage, i18nc("@title:tab Author page", "Author" ));
page->setObjectName("author");
page->setHeader(i18n("Author"));
page->setIcon(KisIconUtils::loadIcon("im-user"));
QPushButton *restoreDefaultsButton = button(QDialogButtonBox::RestoreDefaults);
connect(this, SIGNAL(accepted()), m_inputConfiguration, SLOT(saveChanges()));
connect(this, SIGNAL(rejected()), m_inputConfiguration, SLOT(revertChanges()));
KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
Q_FOREACH (KisAbstractPreferenceSetFactory *preferenceSetFactory, preferenceSetRegistry->values()) {
KisPreferenceSet* preferenceSet = preferenceSetFactory->createPreferenceSet();
vbox = new KoVBox();
page = new KPageWidgetItem(vbox, preferenceSet->name());
page->setHeader(preferenceSet->header());
page->setIcon(preferenceSet->icon());
addPage(page);
preferenceSet->setParent(vbox);
preferenceSet->loadPreferences();
connect(restoreDefaultsButton, SIGNAL(clicked(bool)), preferenceSet, SLOT(loadDefaultPreferences()), Qt::UniqueConnection);
connect(this, SIGNAL(accepted()), preferenceSet, SLOT(savePreferences()), Qt::UniqueConnection);
}
connect(restoreDefaultsButton, SIGNAL(clicked(bool)), this, SLOT(slotDefault()));
}
KisDlgPreferences::~KisDlgPreferences()
{
}
void KisDlgPreferences::slotDefault()
{
if (currentPage()->objectName() == "general") {
m_general->setDefault();
}
else if (currentPage()->objectName() == "shortcuts") {
m_shortcutSettings->setDefault();
}
else if (currentPage()->objectName() == "display") {
m_displaySettings->setDefault();
}
else if (currentPage()->objectName() == "colormanagement") {
m_colorSettings->setDefault();
}
else if (currentPage()->objectName() == "performance") {
m_performanceSettings->load(true);
}
else if (currentPage()->objectName() == "tablet") {
m_tabletSettings->setDefault();
}
else if (currentPage()->objectName() == "canvasonly") {
m_fullscreenSettings->setDefault();
}
else if (currentPage()->objectName() == "canvasinput") {
m_inputConfiguration->setDefaults();
}
}
bool KisDlgPreferences::editPreferences()
{
KisDlgPreferences* dialog;
dialog = new KisDlgPreferences();
bool baccept = (dialog->exec() == Accepted);
if (baccept) {
// General settings
KisConfig cfg;
cfg.setNewCursorStyle(dialog->m_general->cursorStyle());
cfg.setNewOutlineStyle(dialog->m_general->outlineStyle());
cfg.setShowRootLayer(dialog->m_general->showRootLayer());
cfg.setShowOutlineWhilePainting(dialog->m_general->showOutlineWhilePainting());
cfg.setHideSplashScreen(dialog->m_general->hideSplashScreen());
+ cfg.setCalculateAnimationCacheInBackground(dialog->m_general->calculateAnimationCacheInBackground());
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
group.writeEntry("DontUseNativeFileDialog", !dialog->m_general->m_chkNativeFileDialog->isChecked());
cfg.writeEntry<int>("maximumBrushSize", dialog->m_general->intMaxBrushSize->value());
cfg.writeEntry<int>("mdi_viewmode", dialog->m_general->mdiMode());
cfg.setMDIBackgroundColor(dialog->m_general->m_mdiColor->color().toQColor());
cfg.setMDIBackgroundImage(dialog->m_general->m_backgroundimage->text());
cfg.setAutoSaveInterval(dialog->m_general->autoSaveInterval());
cfg.setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked());
cfg.setShowCanvasMessages(dialog->m_general->showCanvasMessages());
cfg.setCompressKra(dialog->m_general->compressKra());
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("EnableHiDPI", dialog->m_general->m_chkHiDPI->isChecked());
kritarc.setValue("EnableSingleApplication", dialog->m_general->m_chkSingleApplication->isChecked());
cfg.setToolOptionsInDocker(dialog->m_general->toolOptionsInDocker());
cfg.setSwitchSelectionCtrlAlt(dialog->m_general->switchSelectionCtrlAlt());
cfg.setConvertToImageColorspaceOnImport(dialog->m_general->convertToImageColorspaceOnImport());
KisPart *part = KisPart::instance();
if (part) {
Q_FOREACH (QPointer<KisDocument> doc, part->documents()) {
if (doc) {
doc->setAutoSaveDelay(dialog->m_general->autoSaveInterval());
doc->setBackupFile(dialog->m_general->m_backupFileCheckBox->isChecked());
doc->undoStack()->setUndoLimit(dialog->m_general->undoStackSize());
}
}
}
cfg.setUndoStackLimit(dialog->m_general->undoStackSize());
cfg.setFavoritePresets(dialog->m_general->favoritePresets());
// Color settings
cfg.setUseSystemMonitorProfile(dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked());
for (int i = 0; i < QApplication::desktop()->screenCount(); ++i) {
if (dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked()) {
int currentIndex = dialog->m_colorSettings->m_monitorProfileWidgets[i]->currentIndex();
QString monitorid = dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemData(currentIndex).toString();
cfg.setMonitorForScreen(i, monitorid);
}
else {
cfg.setMonitorProfile(i,
dialog->m_colorSettings->m_monitorProfileWidgets[i]->itemHighlighted(),
dialog->m_colorSettings->m_page->chkUseSystemMonitorProfile->isChecked());
}
}
cfg.setWorkingColorSpace(dialog->m_colorSettings->m_page->cmbWorkingColorSpace->currentItem().id());
KisImageConfig cfgImage;
cfgImage.setDefaultProofingConfig(dialog->m_colorSettings->m_page->proofingSpaceSelector->currentColorSpace(),
dialog->m_colorSettings->m_page->cmbProofingIntent->currentIndex(),
dialog->m_colorSettings->m_page->ckbProofBlackPoint->isChecked(),
dialog->m_colorSettings->m_page->gamutAlarm->color(),
(double)dialog->m_colorSettings->m_page->sldAdaptationState->value()/20);
cfg.setUseBlackPointCompensation(dialog->m_colorSettings->m_page->chkBlackpoint->isChecked());
cfg.setAllowLCMSOptimization(dialog->m_colorSettings->m_page->chkAllowLCMSOptimization->isChecked());
cfg.setPasteBehaviour(dialog->m_colorSettings->m_pasteBehaviourGroup.checkedId());
cfg.setRenderIntent(dialog->m_colorSettings->m_page->cmbMonitorIntent->currentIndex());
// Tablet settings
cfg.setPressureTabletCurve( dialog->m_tabletSettings->m_page->pressureCurve->curve().toString() );
dialog->m_performanceSettings->save();
if (!cfg.useOpenGL() && dialog->m_displaySettings->grpOpenGL->isChecked())
cfg.setCanvasState("TRY_OPENGL");
cfg.setUseOpenGL(dialog->m_displaySettings->grpOpenGL->isChecked());
cfg.setUseOpenGLTextureBuffer(dialog->m_displaySettings->chkUseTextureBuffer->isChecked());
cfg.setOpenGLFilteringMode(dialog->m_displaySettings->cmbFilterMode->currentIndex());
cfg.setDisableVSync(dialog->m_displaySettings->chkDisableVsync->isChecked());
cfg.setCheckSize(dialog->m_displaySettings->intCheckSize->value());
cfg.setScrollingCheckers(dialog->m_displaySettings->chkMoving->isChecked());
cfg.setCheckersColor1(dialog->m_displaySettings->colorChecks1->color().toQColor());
cfg.setCheckersColor2(dialog->m_displaySettings->colorChecks2->color().toQColor());
cfg.setCanvasBorderColor(dialog->m_displaySettings->canvasBorder->color().toQColor());
cfg.setHideScrollbars(dialog->m_displaySettings->hideScrollbars->isChecked());
KoColor c = dialog->m_displaySettings->btnSelectionOverlayColor->color();
c.setOpacity(dialog->m_displaySettings->sldSelectionOverlayOpacity->value());
cfg.setSelectionOverlayMaskColor(c.toQColor());
cfg.setAntialiasCurves(dialog->m_displaySettings->chkCurveAntialiasing->isChecked());
cfg.setAntialiasSelectionOutline(dialog->m_displaySettings->chkSelectionOutlineAntialiasing->isChecked());
cfg.setShowSingleChannelAsColor(dialog->m_displaySettings->chkChannelsAsColor->isChecked());
cfg.setHidePopups(dialog->m_displaySettings->chkHidePopups->isChecked());
cfg.setHideDockersFullscreen(dialog->m_fullscreenSettings->chkDockers->checkState());
cfg.setHideMenuFullscreen(dialog->m_fullscreenSettings->chkMenu->checkState());
cfg.setHideScrollbarsFullscreen(dialog->m_fullscreenSettings->chkScrollbars->checkState());
cfg.setHideStatusbarFullscreen(dialog->m_fullscreenSettings->chkStatusbar->checkState());
cfg.setHideTitlebarFullscreen(dialog->m_fullscreenSettings->chkTitlebar->checkState());
cfg.setHideToolbarFullscreen(dialog->m_fullscreenSettings->chkToolbar->checkState());
dialog->m_authorPage->apply();
}
delete dialog;
return baccept;
}
diff --git a/libs/ui/dialogs/kis_dlg_preferences.h b/libs/ui/dialogs/kis_dlg_preferences.h
index db24fc2f3d..6ade17541e 100644
--- a/libs/ui/dialogs/kis_dlg_preferences.h
+++ b/libs/ui/dialogs/kis_dlg_preferences.h
@@ -1,330 +1,333 @@
/*
* preferencesdlg.h - part of KImageShop^WKrita
*
* Copyright (c) 1999 Michael Koch <koch@kde.org>
* Copyright (c) 2003-2011 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_DLG_PREFERENCES_H_
#define _KIS_DLG_PREFERENCES_H_
#include <QWidget>
#include <kpagedialog.h>
#include "kis_global.h"
#include "widgets/squeezedcombobox.h"
#include "ui_wdggeneralsettings.h"
#include "ui_wdgdisplaysettings.h"
#include "ui_wdgcolorsettings.h"
#include "ui_wdgtabletsettings.h"
#include "ui_wdgperformancesettings.h"
#include "ui_wdgfullscreensettings.h"
#include "KisShortcutsDialog.h"
class KoID;
class KisInputConfigurationPage;
class KoConfigAuthorPage;
/**
* "General"-tab for preferences dialog
*/
class WdgGeneralSettings : public QWidget, public Ui::WdgGeneralSettings
{
Q_OBJECT
public:
WdgGeneralSettings(QWidget *parent, const char *name) : QWidget(parent) {
setObjectName(name);
setupUi(this);
chkShowRootLayer->setVisible(false);
}
};
class GeneralTab : public WdgGeneralSettings
{
Q_OBJECT
public:
GeneralTab(QWidget *parent = 0, const char *name = 0);
CursorStyle cursorStyle();
OutlineStyle outlineStyle();
bool showRootLayer();
int autoSaveInterval();
void setDefault();
int undoStackSize();
bool showOutlineWhilePainting();
bool hideSplashScreen();
int mdiMode();
int favoritePresets();
bool showCanvasMessages();
bool compressKra();
bool toolOptionsInDocker();
bool switchSelectionCtrlAlt();
bool convertToImageColorspaceOnImport();
+
+ bool calculateAnimationCacheInBackground();
+
private Q_SLOTS:
void getBackgroundImage();
void clearBackgroundImage();
};
/**
* "Shortcuts" tab for preferences dialog
*/
class WdgShortcutSettings : public KisShortcutsDialog
{
Q_OBJECT
public:
WdgShortcutSettings(QWidget *parent)
: KisShortcutsDialog(KisShortcutsEditor::AllActions,
KisShortcutsEditor::LetterShortcutsAllowed,
parent)
{ }
};
class KisActionsSnapshot;
class ShortcutSettingsTab : public QWidget
{
Q_OBJECT
public:
ShortcutSettingsTab(QWidget *parent = 0, const char *name = 0);
~ShortcutSettingsTab();
public:
void setDefault();
WdgShortcutSettings *m_page;
QScopedPointer<KisActionsSnapshot> m_snapshot;
public Q_SLOTS:
void saveChanges();
void cancelChanges();
};
/**
* "Color" tab for preferences dialog
*/
class WdgColorSettings : public QWidget, public Ui::WdgColorSettings
{
Q_OBJECT
public:
WdgColorSettings(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class ColorSettingsTab : public QWidget
{
Q_OBJECT
public:
ColorSettingsTab(QWidget *parent = 0, const char *name = 0);
private Q_SLOTS:
void refillMonitorProfiles(const KoID & s);
void installProfile();
void toggleAllowMonitorProfileSelection(bool useSystemProfile);
public:
void setDefault();
WdgColorSettings *m_page;
QButtonGroup m_pasteBehaviourGroup;
QList<QLabel*> m_monitorProfileLabels;
QList<SqueezedComboBox*> m_monitorProfileWidgets;
};
//=======================
class WdgTabletSettings : public QWidget, public Ui::WdgTabletSettings {
Q_OBJECT
public:
WdgTabletSettings(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class TabletSettingsTab : public QWidget {
Q_OBJECT
public:
TabletSettingsTab(QWidget *parent = 0, const char *name = 0);
public:
void setDefault();
WdgTabletSettings *m_page;
};
//=======================
/**
* "Performance"-tab for preferences dialog
*/
class SliderAndSpinBoxSync;
class WdgPerformanceSettings : public QWidget, public Ui::WdgPerformanceSettings
{
Q_OBJECT
public:
WdgPerformanceSettings(QWidget *parent, const char *name) : QWidget(parent) {
setObjectName(name); setupUi(this);
}
};
class PerformanceTab : public WdgPerformanceSettings
{
Q_OBJECT
public:
PerformanceTab(QWidget *parent = 0, const char *name = 0);
~PerformanceTab();
void load(bool requestDefault);
void save();
private Q_SLOTS:
void selectSwapDir();
private:
int realTilesRAM();
private:
QVector<SliderAndSpinBoxSync*> m_syncs;
};
//=======================
class WdgDisplaySettings : public QWidget, public Ui::WdgDisplaySettings
{
Q_OBJECT
public:
WdgDisplaySettings(QWidget *parent, const char *name) : QWidget(parent) {
setObjectName(name); setupUi(this);
}
};
/**
* Display settings tab for preferences dialog
*/
class DisplaySettingsTab : public WdgDisplaySettings
{
Q_OBJECT
public:
DisplaySettingsTab(QWidget *parent = 0, const char *name = 0);
public:
void setDefault();
protected Q_SLOTS:
void slotUseOpenGLToggled(bool isChecked);
public:
};
//=======================
/**
* Full screen settings tab for preferences dialog
*/
class WdgFullscreenSettingsBase : public QWidget, public Ui::WdgFullscreenSettings
{
Q_OBJECT
public:
WdgFullscreenSettingsBase(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class FullscreenSettingsTab : public WdgFullscreenSettingsBase
{
Q_OBJECT
public:
FullscreenSettingsTab(QWidget *parent);
public:
void setDefault();
};
//=======================
/**
* Preferences dialog of KImageShop^WKrayon^WKrita
*/
class KisDlgPreferences : public KPageDialog
{
Q_OBJECT
public:
static bool editPreferences();
protected:
KisDlgPreferences(QWidget *parent = 0, const char *name = 0);
~KisDlgPreferences();
protected:
GeneralTab *m_general;
ShortcutSettingsTab *m_shortcutSettings;
ColorSettingsTab *m_colorSettings;
PerformanceTab *m_performanceSettings;
DisplaySettingsTab *m_displaySettings;
TabletSettingsTab *m_tabletSettings;
FullscreenSettingsTab *m_fullscreenSettings;
KisInputConfigurationPage *m_inputConfiguration;
KoConfigAuthorPage *m_authorPage;
protected Q_SLOTS:
void slotDefault();
};
#endif
diff --git a/libs/ui/flake/kis_shape_controller.cpp b/libs/ui/flake/kis_shape_controller.cpp
index d108bf5937..03cacef629 100644
--- a/libs/ui/flake/kis_shape_controller.cpp
+++ b/libs/ui/flake/kis_shape_controller.cpp
@@ -1,225 +1,249 @@
/*
* 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_controller.h"
#include <klocalizedstring.h>
#include <KoShape.h>
#include <KoShapeContainer.h>
#include <KoShapeManager.h>
#include <KoCanvasBase.h>
#include <KoToolManager.h>
#include <KisView.h>
#include <KoSelection.h>
#include <KoShapeLayer.h>
#include <KoPathShape.h>
#include <KoColorSpaceConstants.h>
#include <KoCanvasController.h>
#include "kis_node_manager.h"
#include "kis_shape_selection.h"
#include "kis_selection.h"
#include "kis_selection_component.h"
#include "kis_adjustment_layer.h"
#include "kis_clone_layer.h"
#include "canvas/kis_canvas2.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_group_layer.h"
#include "kis_node_shape.h"
#include "kis_node_shapes_graph.h"
#include "kis_name_server.h"
#include "kis_mask.h"
#include "kis_shape_layer.h"
#include "KisViewManager.h"
#include "kis_node.h"
#include <KoDocumentResourceManager.h>
#include <KoDataCenterBase.h>
#include <commands/kis_image_layer_add_command.h>
#include <kis_undo_adapter.h>
+#include "KoSelectedShapesProxy.h"
struct KisShapeController::Private
{
public:
KisDocument *doc;
KisNameServer *nameServer;
KisNodeShapesGraph shapesGraph;
};
KisShapeController::KisShapeController(KisDocument *doc, KisNameServer *nameServer)
: KisDummiesFacadeBase(doc)
, m_d(new Private())
{
m_d->doc = doc;
m_d->nameServer = nameServer;
resourceManager()->setUndoStack(doc->undoStack());
}
KisShapeController::~KisShapeController()
{
KisNodeDummy *node = m_d->shapesGraph.rootDummy();
if (node) {
m_d->shapesGraph.removeNode(node->node());
}
delete m_d;
}
void KisShapeController::addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis)
{
KisNodeShape *newShape =
m_d->shapesGraph.addNode(node, parent, aboveThis);
// XXX: what are we going to do with this shape?
Q_UNUSED(newShape);
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
if (shapeLayer) {
/**
* Forward signals for global shape manager
* \see comment in the constructor of KisCanvas2
*/
connect(shapeLayer, SIGNAL(selectionChanged()),
SIGNAL(selectionChanged()));
connect(shapeLayer->shapeManager(), SIGNAL(selectionContentChanged()),
SIGNAL(selectionContentChanged()));
connect(shapeLayer, SIGNAL(currentLayerChanged(const KoShapeLayer*)),
SIGNAL(currentLayerChanged(const KoShapeLayer*)));
}
}
void KisShapeController::removeNodeImpl(KisNodeSP node)
{
KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
if (shapeLayer) {
shapeLayer->disconnect(this);
}
m_d->shapesGraph.removeNode(node);
}
bool KisShapeController::hasDummyForNode(KisNodeSP node) const
{
return m_d->shapesGraph.containsNode(node);
}
KisNodeDummy* KisShapeController::dummyForNode(KisNodeSP node) const
{
return m_d->shapesGraph.nodeToDummy(node);
}
KisNodeDummy* KisShapeController::rootDummy() const
{
return m_d->shapesGraph.rootDummy();
}
int KisShapeController::dummiesCount() const
{
return m_d->shapesGraph.shapesCount();
}
static inline bool belongsToShapeSelection(KoShape* shape) {
return dynamic_cast<KisShapeSelectionMarker*>(shape->userData());
}
-void KisShapeController::addShape(KoShape* shape)
+void KisShapeController::addShapes(const QList<KoShape*> shapes)
{
- if (!image()) return;
-
- /**
- * Krita layers have their own creation path.
- * It goes through slotNodeAdded()
- */
- Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID &&
- shape->shapeId() != KIS_SHAPE_LAYER_ID);
-
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty());
KisCanvas2 *canvas = dynamic_cast<KisCanvas2*>(KoToolManager::instance()->activeCanvasController()->canvas());
- Q_ASSERT(canvas);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(canvas);
+
+ const KoShape *baseShapeParent = shapes.first()->parent();
+ const bool baseBelongsToSelection = belongsToShapeSelection(shapes.first());
+ bool allSameParent = true;
+ bool allSameBelongsToShapeSelection = true;
+ bool hasNullParent = false;
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ hasNullParent |= !shape->parent();
+ allSameParent &= shape->parent() == baseShapeParent;
+ allSameBelongsToShapeSelection &= belongsToShapeSelection(shape) == baseBelongsToSelection;
+ }
- if (belongsToShapeSelection(shape)) {
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!baseBelongsToSelection || allSameBelongsToShapeSelection);
- KisSelectionSP selection = canvas->viewManager()->selection();
- if (selection) {
- if (!selection->shapeSelection()) {
- selection->setShapeSelection(new KisShapeSelection(image(), selection));
+ if (!allSameParent || hasNullParent) {
+ if (baseBelongsToSelection && allSameBelongsToShapeSelection) {
+ KisSelectionSP selection = canvas->viewManager()->selection();
+ if (selection) {
+ if (!selection->shapeSelection()) {
+ selection->setShapeSelection(new KisShapeSelection(image(), selection));
+ }
+ KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
+
+ Q_FOREACH(KoShape *shape, shapes) {
+ shapeSelection->addShape(shape);
+ }
}
- KisShapeSelection * shapeSelection = static_cast<KisShapeSelection*>(selection->shapeSelection());
- shapeSelection->addShape(shape);
- }
+ } else {
+ KisShapeLayer *shapeLayer =
+ dynamic_cast<KisShapeLayer*>(
+ canvas->selectedShapesProxy()->selection()->activeLayer());
- } else {
- KisShapeLayer *shapeLayer = dynamic_cast<KisShapeLayer*>(shape->parent());
+ if (!shapeLayer) {
+ shapeLayer = new KisShapeLayer(this, image(),
+ i18n("Vector Layer %1", m_d->nameServer->number()),
+ OPACITY_OPAQUE_U8);
- if (!shapeLayer) {
- shapeLayer = new KisShapeLayer(this, image(),
- i18n("Vector Layer %1", m_d->nameServer->number()),
- OPACITY_OPAQUE_U8);
+ image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount()));
+ }
- image()->undoAdapter()->addCommand(new KisImageLayerAddCommand(image(), shapeLayer, image()->rootLayer(), image()->rootLayer()->childCount()));
+ Q_FOREACH(KoShape *shape, shapes) {
+ shapeLayer->addShape(shape);
+ }
}
-
- shapeLayer->addShape(shape);
}
m_d->doc->setModified(true);
}
void KisShapeController::removeShape(KoShape* shape)
{
/**
* Krita layers have their own destruction path.
* It goes through slotRemoveNode()
*/
Q_ASSERT(shape->shapeId() != KIS_NODE_SHAPE_ID &&
shape->shapeId() != KIS_SHAPE_LAYER_ID);
shape->setParent(0);
m_d->doc->setModified(true);
}
+QRectF KisShapeController::documentRectInPixels() const
+{
+ return m_d->doc->image()->bounds();
+}
+
+qreal KisShapeController::pixelsPerInch() const
+{
+ return m_d->doc->image()->xRes() * 72.0;
+}
+
void KisShapeController::setInitialShapeForCanvas(KisCanvas2 *canvas)
{
if (!image()) return;
KisNodeSP rootNode = image()->root();
if (m_d->shapesGraph.containsNode(rootNode)) {
Q_ASSERT(canvas);
Q_ASSERT(canvas->shapeManager());
KoSelection *selection = canvas->shapeManager()->selection();
if (selection && m_d->shapesGraph.nodeToShape(rootNode)) {
selection->select(m_d->shapesGraph.nodeToShape(rootNode));
KoToolManager::instance()->switchToolRequested(KoToolManager::instance()->preferredToolForSelection(selection->selectedShapes()));
}
}
}
KoShapeLayer* KisShapeController::shapeForNode(KisNodeSP node) const
{
if (node) {
return m_d->shapesGraph.nodeToShape(node);
}
return 0;
}
diff --git a/libs/ui/flake/kis_shape_controller.h b/libs/ui/flake/kis_shape_controller.h
index b25e38d1cb..4dabf118cd 100644
--- a/libs/ui/flake/kis_shape_controller.h
+++ b/libs/ui/flake/kis_shape_controller.h
@@ -1,84 +1,87 @@
/*
* 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_CONTROLLER
#define KIS_SHAPE_CONTROLLER
#include <QMap>
#include "kis_dummies_facade_base.h"
#include <KoShapeBasedDocumentBase.h>
class KisNodeDummy;
class KoShapeLayer;
class KisCanvas2;
class KisDocument;
class KisNameServer;
/**
* KisShapeController keeps track of new layers, shapes, masks and
* selections -- everything that needs to be wrapped as a shape for
* the tools to work on.
*/
class KRITAUI_EXPORT KisShapeController : public KisDummiesFacadeBase, public KoShapeBasedDocumentBase
{
Q_OBJECT
public:
KisShapeController(KisDocument *doc, KisNameServer *nameServer);
~KisShapeController();
bool hasDummyForNode(KisNodeSP node) const;
KisNodeDummy* dummyForNode(KisNodeSP layer) const;
KisNodeDummy* rootDummy() const;
int dummiesCount() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
void setInitialShapeForCanvas(KisCanvas2 *canvas);
private:
void addNodeImpl(KisNodeSP node, KisNodeSP parent, KisNodeSP aboveThis);
void removeNodeImpl(KisNodeSP node);
Q_SIGNALS:
/**
* These three signals are forwarded from the local shape manager of
* KisShapeLayer. This is done because we switch KoShapeManager and
* therefore KoSelection in KisCanvas2, so we need to connect local
* managers to the UI as well.
*
* \see comment in the constructor of KisCanvas2
*/
void selectionChanged();
void selectionContentChanged();
void currentLayerChanged(const KoShapeLayer*);
public:
- void addShape(KoShape* shape);
- void removeShape(KoShape* shape);
+ void addShapes(const QList<KoShape*> shapes) override;
+ void removeShape(KoShape* shape) override;
+
+ QRectF documentRectInPixels() const override;
+ qreal pixelsPerInch() const override;
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 13ba102e9c..f882411b74 100644
--- a/libs/ui/flake/kis_shape_layer.cc
+++ b/libs/ui/flake/kis_shape_layer.cc
@@ -1,631 +1,629 @@
/*
* 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 <QTemporaryFile>
#include <kis_debug.h>
#include <kis_icon.h>
-#include <KoElementReference.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
-#include <KoDataCenterBase.h>
#include <KisDocument.h>
-#include <KoEmbeddedDocumentSaver.h>
-#include <KoGenStyles.h>
-#include <KoImageCollection.h>
#include <KoUnit.h>
#include <KoOdf.h>
#include <KoOdfReadStore.h>
#include <KoOdfStylesReader.h>
-#include <KoOdfWriteStore.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 <KoShapeBasedDocumentBase.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 <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 "kis_shape_layer_paste.h"
-
-
#include <SimpleShapeContainerModel.h>
class ShapeLayerContainerModel : public SimpleShapeContainerModel
{
public:
ShapeLayerContainerModel(KisShapeLayer *parent)
: q(parent)
{}
void add(KoShape *child) override {
SimpleShapeContainerModel::add(child);
- q->shapeManager()->addShape(child);
}
void remove(KoShape *child) override {
- q->shapeManager()->remove(child);
SimpleShapeContainerModel::remove(child);
}
+ void shapeHasBeenAddedToHierarchy(KoShape *shape, KoShapeContainer *addedToSubtree) override {
+ q->shapeManager()->addShape(shape);
+ SimpleShapeContainerModel::shapeHasBeenAddedToHierarchy(shape, addedToSubtree);
+ }
+
+ void shapeToBeRemovedFromHierarchy(KoShape *shape, KoShapeContainer *removedFromSubtree) override {
+ q->shapeManager()->remove(shape);
+ SimpleShapeContainerModel::shapeToBeRemovedFromHierarchy(shape, removedFromSubtree);
+ }
+
private:
KisShapeLayer *q;
};
struct KisShapeLayer::Private
{
public:
Private()
: canvas(0)
, controller(0)
, x(0)
, y(0)
{}
KisPaintDeviceSP paintDevice;
KisShapeLayerCanvas * canvas;
KoShapeBasedDocumentBase* controller;
int x;
int y;
};
KisShapeLayer::KisShapeLayer(KoShapeBasedDocumentBase* controller,
KisImageWSP image,
const QString &name,
quint8 opacity)
: KisExternalLayer(image, name, opacity),
KoShapeLayer(new ShapeLayerContainerModel(this)),
m_d(new Private())
{
initShapeLayer(controller);
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& rhs)
: KisShapeLayer(rhs, rhs.m_d->controller)
{
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller)
: KisExternalLayer(_rhs)
, KoShapeLayer(new ShapeLayerContainerModel(this)) //no _rhs here otherwise both layer have the same KoShapeContainerModel
, m_d(new Private())
{
// Make sure our new layer is visible otherwise the shapes cannot be painted.
setVisible(true);
initShapeLayer(controller);
- KoShapeOdfSaveHelper saveHelper(_rhs.shapes());
- KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
- QMimeData* mimeData = drag.mimeData();
-
- Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text)));
-
- KisShapeLayerShapePaste paste(this, m_d->controller);
- bool success = paste.paste(KoOdf::Text, mimeData);
- Q_ASSERT(success);
- Q_UNUSED(success); // for release build
+ Q_FOREACH (KoShape *shape, _rhs.shapes()) {
+ addShape(shape->cloneShape());
+ }
}
KisShapeLayer::KisShapeLayer(const KisShapeLayer& _rhs, const KisShapeLayer &_addShapes)
: KisExternalLayer(_rhs)
, KoShapeLayer(new ShapeLayerContainerModel(this)) //no _merge here otherwise both layer have the same KoShapeContainerModel
, m_d(new Private())
{
// Make sure our new layer is visible otherwise the shapes cannot be painted.
setVisible(true);
initShapeLayer(_rhs.m_d->controller);
// copy in _rhs's shapes
- {
- KoShapeOdfSaveHelper saveHelper(_rhs.shapes());
- KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
- QMimeData* mimeData = drag.mimeData();
-
- Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text)));
-
- KisShapeLayerShapePaste paste(this, m_d->controller);
- bool success = paste.paste(KoOdf::Text, mimeData);
- Q_ASSERT(success);
- Q_UNUSED(success); // for release build
+ Q_FOREACH (KoShape *shape, _rhs.shapes()) {
+ addShape(shape->cloneShape());
}
// copy in _addShapes's shapes
- {
- KoShapeOdfSaveHelper saveHelper(_addShapes.shapes());
- KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
- QMimeData* mimeData = drag.mimeData();
-
- Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text)));
-
- KisShapeLayerShapePaste paste(this, m_d->controller);
- bool success = paste.paste(KoOdf::Text, mimeData);
- Q_ASSERT(success);
- Q_UNUSED(success); // for release build
+ Q_FOREACH (KoShape *shape, _addShapes.shapes()) {
+ addShape(shape->cloneShape());
}
}
KisShapeLayer::~KisShapeLayer()
{
/**
* Small hack alert: we should avoid updates on shape deletion
*/
m_d->canvas->prepareForDestroying();
Q_FOREACH (KoShape *shape, shapes()) {
shape->setParent(0);
delete shape;
}
delete m_d->canvas;
delete m_d;
}
void KisShapeLayer::initShapeLayer(KoShapeBasedDocumentBase* controller)
{
setSupportsLodMoves(false);
setShapeId(KIS_SHAPE_LAYER_ID);
KIS_ASSERT_RECOVER_NOOP(this->image());
m_d->paintDevice = new KisPaintDevice(image()->colorSpace());
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(this->image()));
m_d->paintDevice->setParentNode(this);
m_d->canvas = new KisShapeLayerCanvas(this, image());
m_d->canvas->setProjection(m_d->paintDevice);
m_d->canvas->moveToThread(this->thread());
m_d->controller = controller;
m_d->canvas->shapeManager()->selection()->disconnect(this);
- connect(m_d->canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
- connect(m_d->canvas->shapeManager()->selection(), SIGNAL(currentLayerChanged(const KoShapeLayer*)),
+ connect(m_d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
+ connect(m_d->canvas->selectedShapesProxy(), SIGNAL(currentLayerChanged(const KoShapeLayer*)),
this, SIGNAL(currentLayerChanged(const KoShapeLayer*)));
connect(this, SIGNAL(sigMoveShapes(const QPointF&)), SLOT(slotMoveShapes(const QPointF&)));
}
bool KisShapeLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
void KisShapeLayer::setImage(KisImageWSP _image)
{
KisLayer::setImage(_image);
m_d->canvas->setImage(_image);
m_d->paintDevice->convertTo(_image->colorSpace());
m_d->paintDevice->setDefaultBounds(new KisDefaultBounds(_image));
}
KisLayerSP KisShapeLayer::createMergedLayerTemplate(KisLayerSP prevLayer)
{
KisShapeLayer *prevShape = dynamic_cast<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;
}
-
-void KisShapeLayer::slotMoveShapes(const QPointF &diff)
+namespace {
+void filterTransformableShapes(QList<KoShape*> &shapes)
{
- QList<QPointF> prevPos;
- QList<QPointF> newPos;
+ auto it = shapes.begin();
+ while (it != shapes.end()) {
+ if (shapes.size() == 1) break;
- QList<KoShape*> shapes;
- Q_FOREACH (KoShape* shape, shapeManager()->shapes()) {
- if (!dynamic_cast<KoShapeGroup*>(shape)) {
- shapes.append(shape);
+ if ((*it)->inheritsTransformFromAny(shapes)) {
+ it = shapes.erase(it);
+ } else {
+ ++it;
}
}
- Q_FOREACH (KoShape* shape, shapes) {
- QPointF pos = shape->position();
- prevPos << pos;
- newPos << pos + diff;
+}
+}
+
+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;
+ }
+ }
}
- KoShapeMoveCommand cmd(shapes, prevPos, newPos);
+ 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)
{
KisExternalLayer::setVisible(visible, isLoading);
}
-bool KisShapeLayer::saveLayer(KoStore * store) const
+void KisShapeLayer::forceUpdateTimedNode()
{
+ m_d->canvas->forceRepaint();
+}
- KoOdfWriteStore odfStore(store);
- KoXmlWriter* manifestWriter = odfStore.manifestWriter("application/vnd.oasis.opendocument.graphics");
- KoEmbeddedDocumentSaver embeddedSaver;
- KoDocumentBase::SavingContext documentContext(odfStore, embeddedSaver);
+#include "SvgWriter.h"
+#include "SvgParser.h"
+#include <QXmlStreamReader>
- if (!store->open("content.xml"))
+bool KisShapeLayer::saveShapesToStore(KoStore *store, QList<KoShape *> shapes, const QSizeF &sizeInPt)
+{
+ if (!store->open("content.svg")) {
return false;
-
- KoStoreDevice storeDev(store);
- KoXmlWriter * docWriter = KoOdfWriteStore::createOasisXmlWriter(&storeDev, "office:document-content");
-
- // for office:master-styles
-// QTemporaryFile masterStyles;
-// masterStyles.open();
-// KoXmlWriter masterStylesTmpWriter(&masterStyles, 1);
-
- KoPageLayout page;
- page.format = KoPageFormat::defaultFormat();
- QRectF rc = boundingRect();
- page.width = rc.width();
- page.height = rc.height();
- if (page.width > page.height) {
- page.orientation = KoPageFormat::Landscape;
- } else {
- page.orientation = KoPageFormat::Portrait;
}
- KoGenStyles mainStyles;
- KoGenStyle pageLayout = page.saveOdf();
- QString layoutName = mainStyles.insert(pageLayout, "PL");
- KoGenStyle masterPage(KoGenStyle::MasterPageStyle);
- masterPage.addAttribute("style:page-layout-name", layoutName);
- mainStyles.insert(masterPage, "Default", KoGenStyles::DontAddNumberToName);
+ KoStoreDevice storeDev(store);
+ storeDev.open(QIODevice::WriteOnly);
- QTemporaryFile contentTmpFile;
- contentTmpFile.open();
- KoXmlWriter contentTmpWriter(&contentTmpFile, 1);
+ qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
- contentTmpWriter.startElement("office:body");
- contentTmpWriter.startElement("office:drawing");
+ SvgWriter writer(shapes, sizeInPt);
+ writer.save(storeDev);
- KoShapeSavingContext shapeContext(contentTmpWriter, mainStyles, documentContext.embeddedSaver);
+ if (!store->close()) {
+ return false;
+ }
- shapeContext.xmlWriter().startElement("draw:page");
- shapeContext.xmlWriter().addAttribute("draw:name", "");
+ return true;
+}
- KoElementReference elementRef("page", 1);
- elementRef.saveOdf(&shapeContext.xmlWriter(), KoElementReference::DrawId);
+QList<KoShape *> KisShapeLayer::createShapesFromSvg(QIODevice *device, const QString &baseXmlDir, const QRectF &rectInPixels, qreal resolutionPPI, KoDocumentResourceManager *resourceManager, QSizeF *fragmentSize)
+{
+ QXmlStreamReader reader(device);
+ reader.setNamespaceProcessing(false);
+
+ QString errorMsg;
+ int errorLine = 0;
+ int errorColumn;
+
+ KoXmlDocument doc;
+ bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn);
+ if (!ok) {
+ errKrita << "Parsing error in " << "contents.svg" << "! Aborting!" << endl
+ << " In line: " << errorLine << ", column: " << errorColumn << endl
+ << " Error message: " << errorMsg << endl;
+ errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3"
+ , errorLine , errorColumn , errorMsg);
+ }
- shapeContext.xmlWriter().addAttribute("draw:master-page-name", "Default");
+ SvgParser parser(resourceManager);
+ parser.setXmlBaseDir(baseXmlDir);
+ parser.setResolution(rectInPixels /* px */, resolutionPPI /* ppi */);
+ return parser.parseSvg(doc.documentElement(), fragmentSize);
+}
- saveOdf(shapeContext);
- shapeContext.xmlWriter().endElement(); // draw:page
+bool KisShapeLayer::saveLayer(KoStore * store) const
+{
+ // FIXME: we handle xRes() only!
- contentTmpWriter.endElement(); // office:drawing
- contentTmpWriter.endElement(); // office:body
+ const QSizeF sizeInPx = image()->bounds().size();
+ const QSizeF sizeInPt(sizeInPx.width() / image()->xRes(), sizeInPx.height() / image()->yRes());
- mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, docWriter);
+ return saveShapesToStore(store, this->shapes(), sizeInPt);
+}
- // And now we can copy over the contents from the tempfile to the real one
- contentTmpFile.seek(0);
- docWriter->addCompleteElement(&contentTmpFile);
+bool KisShapeLayer::loadSvg(QIODevice *device, const QString &baseXmlDir)
+{
+ QSizeF fragmentSize; // unused!
+ KisImageSP image = this->image();
- docWriter->endElement(); // Root element
- docWriter->endDocument();
- delete docWriter;
+ // FIXME: we handle xRes() only!
+ KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(image->xRes(), image->yRes()));
+ const qreal resolutionPPI = 72.0 * image->xRes();
- if (!store->close())
- return false;
+ QList<KoShape*> shapes =
+ createShapesFromSvg(device, baseXmlDir,
+ image->bounds(), resolutionPPI,
+ m_d->controller->resourceManager(),
+ &fragmentSize);
- embeddedSaver.saveEmbeddedDocuments(documentContext);
+ Q_FOREACH (KoShape *shape, shapes) {
+ addShape(shape);
+ }
- manifestWriter->addManifestEntry("content.xml", "text/xml");
+ return true;
+}
- if (! mainStyles.saveOdfStylesDotXml(store, manifestWriter)) {
+bool KisShapeLayer::loadLayer(KoStore* store)
+{
+ if (!store) {
+ warnKrita << i18n("No store backend");
return false;
}
- manifestWriter->addManifestEntry("settings.xml", "text/xml");
+ if (store->open("content.svg")) {
+ KoStoreDevice storeDev(store);
+ storeDev.open(QIODevice::ReadOnly);
- if (! shapeContext.saveDataCenter(documentContext.odfStore.store(),
- documentContext.odfStore.manifestWriter()))
- return false;
+ loadSvg(&storeDev, "");
- // Write out manifest file
- if (!odfStore.closeManifestWriter()) {
- dbgImage << "closing manifestWriter failed";
- return false;
- }
+ store->close();
- return true;
-}
+ return true;
+ }
-bool KisShapeLayer::loadLayer(KoStore* store)
-{
KoOdfReadStore odfStore(store);
QString errorMessage;
odfStore.loadAndParse(errorMessage);
if (!errorMessage.isEmpty()) {
warnKrita << errorMessage;
return false;
}
KoXmlElement contents = odfStore.contentDoc().documentElement();
// dbgKrita <<"Start loading OASIS document..." << contents.text();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement();
KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body"));
if (body.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
return false;
}
body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing");
if (body.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
return false;
}
KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page"));
if (page.isNull()) {
//setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
return false;
}
KoXmlElement * master = 0;
if (odfStore.styles().masterPages().contains("Standard"))
master = odfStore.styles().masterPages().value("Standard");
else if (odfStore.styles().masterPages().contains("Default"))
master = odfStore.styles().masterPages().value("Default");
else if (! odfStore.styles().masterPages().empty())
master = odfStore.styles().masterPages().begin().value();
if (master) {
const KoXmlElement *style = odfStore.styles().findStyle(
master->attributeNS(KoXmlNS::style, "page-layout-name", QString()));
KoPageLayout pageLayout;
pageLayout.loadOdf(*style);
setSize(QSizeF(pageLayout.width, pageLayout.height));
}
// We work fine without a master page
KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
context.setManifestFile(QString("tar:/") + odfStore.store()->currentPath() + "META-INF/manifest.xml");
KoShapeLoadingContext shapeContext(context, m_d->controller->resourceManager());
KoXmlElement layerElement;
forEachElement(layerElement, context.stylesReader().layerSet()) {
// FIXME: investigate what is this
// KoShapeLayer * l = new KoShapeLayer();
if (!loadOdf(layerElement, shapeContext)) {
dbgKrita << "Could not load vector layer!";
return false;
}
}
KoXmlElement child;
forEachElement(child, page) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext);
if (shape) {
addShape(shape);
}
}
return true;
}
void KisShapeLayer::resetCache()
{
m_d->paintDevice->clear();
QList<KoShape*> shapes = m_d->canvas->shapeManager()->shapes();
Q_FOREACH (const KoShape* shape, shapes) {
shape->update();
}
}
-KUndo2Command* KisShapeLayer::crop(const QRect & rect) {
+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 = m_d->canvas->shapeManager()->shapes();
- if(shapes.isEmpty()) return 0;
+ 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);
-
- // 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 localTransform = globalTransform * realTransform * globalTransform.inverted();
- newTransformations.append(localTransform*oldTransform);
- }
+
+ QTransform globalTransform = shape->absoluteTransformation(0);
+ QTransform localTransform = globalTransform * realTransform * globalTransform.inverted();
+ newTransformations.append(localTransform * oldTransform);
KoShapeShadow *shadow = 0;
if (shape->shadow()) {
shadow = new KoShapeShadow(*shape->shadow());
shadow->setOffset(transformBaseScale * shadow->offset());
shadow->setBlur(transformBaseScale * shadow->blur());
}
newShadows.append(shadow);
}
KUndo2Command *parentCommand = new KUndo2Command();
new KoShapeTransformCommand(shapes,
oldTransformations,
newTransformations,
parentCommand);
new KoShapeShadowCommand(shapes,
newShadows,
parentCommand);
return parentCommand;
}
diff --git a/libs/ui/flake/kis_shape_layer.h b/libs/ui/flake/kis_shape_layer.h
index 5ae0b82657..ef466c3d06 100644
--- a/libs/ui/flake/kis_shape_layer.h
+++ b/libs/ui/flake/kis_shape_layer.h
@@ -1,150 +1,181 @@
/*
* Copyright (c) 2006 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 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 KIS_SHAPE_LAYER_H_
#define KIS_SHAPE_LAYER_H_
#include <KoShapeLayer.h>
#include <kis_types.h>
#include <kis_external_layer_iface.h>
#include <kritaui_export.h>
+#include <KisDelayedUpdateNodeInterface.h>
class QRect;
class QIcon;
class QRect;
class QString;
class KoShapeManager;
class KoStore;
class KoViewConverter;
class KoShapeBasedDocumentBase;
+class KoDocumentResourceManager;
const QString KIS_SHAPE_LAYER_ID = "KisShapeLayer";
/**
A KisShapeLayer contains any number of non-krita flakes, such as
path shapes, text shapes and anything else people come up with.
The KisShapeLayer has a shapemanager and a canvas of its own. The
canvas paints onto the projection, and the projection is what we
render in Krita. This means that no matter how many views you have,
you cannot have a different view on your shapes per view.
XXX: what about removing shapes?
*/
-class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer
+class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer, public KisDelayedUpdateNodeInterface
{
Q_OBJECT
public:
KisShapeLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity);
KisShapeLayer(const KisShapeLayer& _rhs);
KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller);
/**
* Merge constructor.
*
* Creates a new layer as a merge of two existing layers.
*
* This is used by createMergedLayer()
*/
KisShapeLayer(const KisShapeLayer& _merge, const KisShapeLayer &_addShapes);
virtual ~KisShapeLayer();
private:
void initShapeLayer(KoShapeBasedDocumentBase* controller);
public:
KisNodeSP clone() const override {
return new KisShapeLayer(*this);
}
bool allowAsChild(KisNodeSP) const override;
void setImage(KisImageWSP image) override;
virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer) override;
virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) override;
public:
// KoShape overrides
bool isSelectable() const {
return false;
}
void setParent(KoShapeContainer *parent);
// KisExternalLayer implementation
QIcon icon() const override;
void resetCache() override;
KisPaintDeviceSP original() const override;
KisPaintDeviceSP paintDevice() const override;
qint32 x() const override;
qint32 y() const override;
void setX(qint32) override;
void setY(qint32) override;
bool accept(KisNodeVisitor&) override;
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) override;
KoShapeManager *shapeManager() const;
+ static bool saveShapesToStore(KoStore *store, QList<KoShape*> shapes, const QSizeF &sizeInPt);
+
+ static QList<KoShape *> createShapesFromSvg(QIODevice *device,
+ const QString &baseXmlDir,
+ const QRectF &rectInPixels,
+ qreal resolutionPPI,
+ KoDocumentResourceManager *resourceManager,
+ QSizeF *fragmentSize);
+
+
+
bool saveLayer(KoStore * store) const;
bool loadLayer(KoStore* store);
KUndo2Command* crop(const QRect & rect) override;
KUndo2Command* transform(const QTransform &transform) override;
bool visible(bool recursive = false) const override;
void setVisible(bool visible, bool isLoading = false) override;
+ /**
+ * Forces a repaint of a shape layer without waiting for an event loop
+ * calling a delayed timer update. If you want to see the result of the shape
+ * layer right here and right now, you should do:
+ *
+ * shapeLayer->setDirty();
+ * shapeLayer->image()->waitForDone();
+ * shapeLayer->forceUpdateTimedNode();
+ * shapeLayer->image()->waitForDone();
+ *
+ */
+ void forceUpdateTimedNode() override;
+
protected:
using KoShape::isVisible;
+ bool loadSvg(QIODevice *device, const QString &baseXmlDir);
+
friend class ShapeLayerContainerModel;
KoViewConverter* converter() const;
Q_SIGNALS:
/**
* These signals are forwarded from the local shape manager
* This is done because we switch KoShapeManager and therefore
* KoSelection in KisCanvas2, so we need to connect local managers
* to the UI as well.
*
* \see comment in the constructor of KisCanvas2
*/
void selectionChanged();
void currentLayerChanged(const KoShapeLayer *layer);
Q_SIGNALS:
/**
* A signal + slot to synchronize UI and image
* threads. Image thread emits the signal, UI
* thread performes the action
*/
void sigMoveShapes(const QPointF &diff);
private Q_SLOTS:
void slotMoveShapes(const QPointF &diff);
+private:
+ QList<KoShape*> shapesToBeTransformed();
+
private:
struct Private;
Private * const m_d;
};
#endif
diff --git a/libs/ui/flake/kis_shape_layer_canvas.cpp b/libs/ui/flake/kis_shape_layer_canvas.cpp
index ca4ed20e05..da76b05f5d 100644
--- a/libs/ui/flake/kis_shape_layer_canvas.cpp
+++ b/libs/ui/flake/kis_shape_layer_canvas.cpp
@@ -1,180 +1,195 @@
/*
* 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>
+
//#define DEBUG_REPAINT
KisShapeLayerCanvas::KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image)
: KoCanvasBase(0)
, m_isDestroying(false)
, m_viewConverter(new KisImageViewConverter(image))
, m_shapeManager(new KoShapeManager(this))
+ , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
, m_projection(0)
, m_parentLayer(parent)
{
m_shapeManager->selection()->setActiveLayer(parent);
connect(this, SIGNAL(forwardRepaint()), SLOT(repaint()), Qt::QueuedConnection);
}
KisShapeLayerCanvas::~KisShapeLayerCanvas()
{
- delete m_shapeManager;
}
void KisShapeLayerCanvas::setImage(KisImageWSP image)
{
m_viewConverter->setImage(image);
}
void KisShapeLayerCanvas::prepareForDestroying()
{
m_isDestroying = true;
}
void KisShapeLayerCanvas::gridSize(QPointF *offset, QSizeF *spacing) const
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
Q_UNUSED(offset);
Q_UNUSED(spacing);
}
bool KisShapeLayerCanvas::snapToGrid() const
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
return false;
}
void KisShapeLayerCanvas::addCommand(KUndo2Command *)
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
}
KoShapeManager *KisShapeLayerCanvas::shapeManager() const
{
- return m_shapeManager;
+ return m_shapeManager.data();
+}
+
+KoSelectedShapesProxy *KisShapeLayerCanvas::selectedShapesProxy() const
+{
+ return m_selectedShapesProxy.data();
}
#ifdef DEBUG_REPAINT
# include <stdlib.h>
#endif
void KisShapeLayerCanvas::updateCanvas(const QRectF& rc)
{
dbgUI << "KisShapeLayerCanvas::updateCanvas()" << rc;
//image is 0, if parentLayer is being deleted so don't update
if (!m_parentLayer->image() || m_isDestroying) {
return;
}
QRect r = m_viewConverter->documentToView(rc).toRect();
r.adjust(-2, -2, 2, 2); // for antialias
{
QMutexLocker locker(&m_dirtyRegionMutex);
m_dirtyRegion += r;
qreal x, y;
m_viewConverter->zoom(&x, &y);
}
emit forwardRepaint();
}
void KisShapeLayerCanvas::repaint()
{
QRect r;
{
QMutexLocker locker(&m_dirtyRegionMutex);
r = m_dirtyRegion.boundingRect();
m_dirtyRegion = QRegion();
}
if (r.isEmpty()) return;
r = r.intersected(m_parentLayer->image()->bounds());
QImage image(r.width(), r.height(), QImage::Format_ARGB32);
image.fill(0);
QPainter p(&image);
p.setRenderHint(QPainter::Antialiasing);
p.setRenderHint(QPainter::TextAntialiasing);
p.translate(-r.x(), -r.y());
p.setClipRect(r);
#ifdef DEBUG_REPAINT
QColor color = QColor(random() % 255, random() % 255, random() % 255);
p.fillRect(r, color);
#endif
m_shapeManager->paint(p, *m_viewConverter, false);
p.end();
KisPaintDeviceSP dev = new KisPaintDevice(m_projection->colorSpace());
dev->convertFromQImage(image, 0);
KisPainter::copyAreaOptimized(r.topLeft(), dev, m_projection, QRect(QPoint(), r.size()));
m_parentLayer->setDirty(r);
}
KoToolProxy * KisShapeLayerCanvas::toolProxy() const
{
// Q_ASSERT(false); // This should never be called as this canvas should have no tools.
return 0;
}
KoViewConverter* KisShapeLayerCanvas::viewConverter() const
{
return m_viewConverter.data();
}
QWidget* KisShapeLayerCanvas::canvasWidget()
{
return 0;
}
const QWidget* KisShapeLayerCanvas::canvasWidget() const
{
return 0;
}
KoUnit KisShapeLayerCanvas::unit() const
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
return KoUnit(KoUnit::Point);
}
+void KisShapeLayerCanvas::forceRepaint()
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread());
+ repaint();
+}
+
diff --git a/libs/ui/flake/kis_shape_layer_canvas.h b/libs/ui/flake/kis_shape_layer_canvas.h
index a497222a25..845cba8cbb 100644
--- a/libs/ui/flake/kis_shape_layer_canvas.h
+++ b/libs/ui/flake/kis_shape_layer_canvas.h
@@ -1,87 +1,91 @@
/*
* 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>
class KoShapeManager;
class KoToolProxy;
class KoViewConverter;
class KUndo2Command;
class QWidget;
class KoUnit;
class KisImageViewConverter;
/**
* 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 KoCanvasBase
{
Q_OBJECT
public:
KisShapeLayerCanvas(KisShapeLayer *parent, KisImageWSP image);
virtual ~KisShapeLayerCanvas();
/// This canvas won't render onto a widget, but a projection
void setProjection(KisPaintDeviceSP projection) {
m_projection = projection;
}
void setImage(KisImageWSP image);
void prepareForDestroying();
void gridSize(QPointF *offset, QSizeF *spacing) const;
bool snapToGrid() const;
void addCommand(KUndo2Command *command);
- KoShapeManager *shapeManager() const;
+ KoShapeManager *shapeManager() const override;
+ KoSelectedShapesProxy *selectedShapesProxy() const override;
void updateCanvas(const QRectF& rc);
KoToolProxy * toolProxy() const;
KoViewConverter* viewConverter() const;
QWidget* canvasWidget();
const QWidget* canvasWidget() const;
KoUnit unit() const;
virtual void updateInputMethodInfo() {}
virtual void setCursor(const QCursor &) {}
+ void forceRepaint();
+
private Q_SLOTS:
void repaint();
Q_SIGNALS:
void forwardRepaint();
private:
bool m_isDestroying;
QScopedPointer<KisImageViewConverter> m_viewConverter;
- KoShapeManager * m_shapeManager;
+ QScopedPointer<KoShapeManager> m_shapeManager;
+ QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
KisPaintDeviceSP m_projection;
KisShapeLayer *m_parentLayer;
QRegion m_dirtyRegion;
QMutex m_dirtyRegionMutex;
};
#endif
diff --git a/libs/ui/flake/kis_shape_layer_paste.h b/libs/ui/flake/kis_shape_layer_paste.h
deleted file mode 100644
index f557f4c965..0000000000
--- a/libs/ui/flake/kis_shape_layer_paste.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (c) 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.
- */
-#ifndef KIS_SHAPE_LAYER_PASTE_H
-#define KIS_SHAPE_LAYER_PASTE_H
-#include <KoDrag.h>
-#include <KoOdf.h>
-#include <KoOdfLoadingContext.h>
-#include <KoOdfPaste.h>
-#include <KoShapeBasedDocumentBase.h>
-#include <KoShapeOdfSaveHelper.h>
-#include "kis_shape_layer.h"
-#include "kis_shape_selection.h"
-
-class KisShapeLayerShapePaste : public KoOdfPaste
-{
-public:
- KisShapeLayerShapePaste(KoShapeLayer* _container, KoShapeBasedDocumentBase* _controller)
- : m_container(_container)
- , m_controller(_controller) {
- }
-
- virtual ~KisShapeLayerShapePaste() {
- }
-
- virtual bool process(const KoXmlElement & body, KoOdfReadStore & odfStore) {
- KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
- KoShapeLoadingContext context(loadingContext, m_controller ? m_controller->resourceManager() : 0);
- KoXmlElement child;
- forEachElement(child, body) {
- KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, context);
- if (shape) {
- KisShapeLayer* shapeLayer = dynamic_cast<KisShapeLayer*>(m_container);
- if (shapeLayer) {
- //don't update as the setDirty call would create shared pointer that would delete the layer
- shapeLayer->addShape(shape);
- } else {
- KisShapeSelection* shapeSelection = dynamic_cast<KisShapeSelection*>(m_container);
- shapeSelection->addShape(shape);
- }
- }
- }
- return true;
- }
-private:
- KoShapeLayer* m_container;
- KoShapeBasedDocumentBase* m_controller;
-};
-#endif
diff --git a/libs/ui/flake/kis_shape_selection.cpp b/libs/ui/flake/kis_shape_selection.cpp
index fe79692ee2..63e26909e7 100644
--- a/libs/ui/flake/kis_shape_selection.cpp
+++ b/libs/ui/flake/kis_shape_selection.cpp
@@ -1,453 +1,384 @@
/*
* 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 <QTemporaryFile>
#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_shape_layer_paste.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(KisImageWSP image, KisSelectionWSP selection)
: KoShapeLayer(m_model = new KisShapeSelectionModel(image, selection, this))
, m_image(image)
{
Q_ASSERT(m_image);
setShapeId("KisShapeSelection");
setSelectable(false);
m_converter = new KisImageViewConverter(image);
m_canvas = new KisShapeSelectionCanvas();
m_canvas->shapeManager()->addShape(this);
m_model->setObjectName("KisShapeSelectionModel");
m_model->moveToThread(image->thread());
m_canvas->setObjectName("KisShapeSelectionCanvas");
m_canvas->moveToThread(image->thread());
}
KisShapeSelection::~KisShapeSelection()
{
m_model->setShapeSelection(0);
delete m_canvas;
delete m_converter;
}
KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection)
: KoShapeLayer(m_model = new KisShapeSelectionModel(rhs.m_image, selection, this))
{
m_image = rhs.m_image;
m_converter = new KisImageViewConverter(m_image);
m_canvas = new KisShapeSelectionCanvas();
m_canvas->shapeManager()->addShape(this);
- KoShapeOdfSaveHelper saveHelper(rhs.shapes());
- KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
- QMimeData* mimeData = drag.mimeData();
-
- Q_ASSERT(mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text)));
-
- KisShapeLayerShapePaste paste(this, 0);
- bool success = paste.paste(KoOdf::Text, mimeData);
- Q_ASSERT(success);
- if (!success) {
- warnUI << "Could not paste vector layer";
+ Q_FOREACH (KoShape *shape, rhs.shapes()) {
+ this->addShape(shape->cloneShape());
}
}
KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection)
{
return new KisShapeSelection(*this, selection);
}
bool KisShapeSelection::saveSelection(KoStore * store) const
{
-
- KoOdfWriteStore odfStore(store);
- KoXmlWriter* manifestWriter = odfStore.manifestWriter("application/vnd.oasis.opendocument.graphics");
- KoEmbeddedDocumentSaver embeddedSaver;
- KisDocument::SavingContext documentContext(odfStore, embeddedSaver);
-
- if (!store->open("content.xml"))
- return false;
-
- KoStoreDevice storeDev(store);
- KoXmlWriter * docWriter = KoOdfWriteStore::createOasisXmlWriter(&storeDev, "office:document-content");
-
- // for office:master-styles
- QTemporaryFile masterStyles;
- masterStyles.open();
- KoXmlWriter masterStylesTmpWriter(&masterStyles, 1);
-
- KoPageLayout page;
- page.format = KoPageFormat::defaultFormat();
- QRectF rc = boundingRect();
- page.width = rc.width();
- page.height = rc.height();
- if (page.width > page.height) {
- page.orientation = KoPageFormat::Landscape;
- } else {
- page.orientation = KoPageFormat::Portrait;
- }
-
- KoGenStyles mainStyles;
- KoGenStyle pageLayout = page.saveOdf();
- QString layoutName = mainStyles.insert(pageLayout, "PL");
- KoGenStyle masterPage(KoGenStyle::MasterPageStyle);
- masterPage.addAttribute("style:page-layout-name", layoutName);
- mainStyles.insert(masterPage, "Default", KoGenStyles::DontAddNumberToName);
-
- QTemporaryFile contentTmpFile;
- contentTmpFile.open();
- KoXmlWriter contentTmpWriter(&contentTmpFile, 1);
-
- contentTmpWriter.startElement("office:body");
- contentTmpWriter.startElement("office:drawing");
-
- KoShapeSavingContext shapeContext(contentTmpWriter, mainStyles, documentContext.embeddedSaver);
-
- shapeContext.xmlWriter().startElement("draw:page");
- shapeContext.xmlWriter().addAttribute("draw:name", "");
-
- KoElementReference elementRef;
- elementRef.saveOdf(&shapeContext.xmlWriter(), KoElementReference::DrawId);
-
- shapeContext.xmlWriter().addAttribute("draw:master-page-name", "Default");
-
- saveOdf(shapeContext);
-
- shapeContext.xmlWriter().endElement(); // draw:page
+ const QSizeF sizeInPx = m_image->bounds().size();
+ const QSizeF sizeInPt(sizeInPx.width() / m_image->xRes(), sizeInPx.height() / m_image->yRes());
- contentTmpWriter.endElement(); // office:drawing
- contentTmpWriter.endElement(); // office:body
-
- mainStyles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, docWriter);
+ return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt);
+}
- // And now we can copy over the contents from the tempfile to the real one
- contentTmpFile.seek(0);
- docWriter->addCompleteElement(&contentTmpFile);
+bool KisShapeSelection::loadSelection(KoStore* store)
+{
+ QSizeF fragmentSize; // unused!
- docWriter->endElement(); // Root element
- docWriter->endDocument();
- delete docWriter;
+ // 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();
- if (!store->close())
- return false;
+ QList<KoShape*> shapes;
- manifestWriter->addManifestEntry("content.xml", "text/xml");
+ if (store->open("content.svg")) {
+ KoStoreDevice storeDev(store);
+ storeDev.open(QIODevice::ReadOnly);
- if (! mainStyles.saveOdfStylesDotXml(store, manifestWriter)) {
- return false;
- }
+ shapes = KisShapeLayer::createShapesFromSvg(&storeDev,
+ "", m_image->bounds(),
+ resolutionPPI, m_canvas->shapeController()->resourceManager(),
+ &fragmentSize);
- manifestWriter->addManifestEntry("settings.xml", "text/xml");
+ store->close();
- if (! shapeContext.saveDataCenter(documentContext.odfStore.store(), documentContext.odfStore.manifestWriter()))
- return false;
+ Q_FOREACH (KoShape *shape, shapes) {
+ addShape(shape);
+ }
- // Write out manifest file
- if (!odfStore.closeManifestWriter()) {
- dbgImage << "closing manifestWriter failed";
- return false;
+ return true;
}
- return true;
-}
-bool KisShapeSelection::loadSelection(KoStore* store)
-{
KoOdfReadStore odfStore(store);
QString errorMessage;
odfStore.loadAndParse(errorMessage);
if (!errorMessage.isEmpty()) {
dbgKrita << errorMessage;
return false;
}
KoXmlElement contents = odfStore.contentDoc().documentElement();
// dbgKrita <<"Start loading OASIS document..." << contents.text();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().localName();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().namespaceURI();
// dbgKrita <<"Start loading OASIS contents..." << contents.lastChild().isElement();
KoXmlElement body(KoXml::namedItemNS(contents, KoXmlNS::office, "body"));
if (body.isNull()) {
dbgKrita << "No office:body found!";
//setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
return false;
}
body = KoXml::namedItemNS(body, KoXmlNS::office, "drawing");
if (body.isNull()) {
dbgKrita << "No office:drawing found!";
//setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
return false;
}
KoXmlElement page(KoXml::namedItemNS(body, KoXmlNS::draw, "page"));
if (page.isNull()) {
dbgKrita << "No office:drawing found!";
//setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
return false;
}
KoXmlElement * master = 0;
if (odfStore.styles().masterPages().contains("Standard"))
master = odfStore.styles().masterPages().value("Standard");
else if (odfStore.styles().masterPages().contains("Default"))
master = odfStore.styles().masterPages().value("Default");
else if (! odfStore.styles().masterPages().empty())
master = odfStore.styles().masterPages().begin().value();
if (master) {
const KoXmlElement *style = odfStore.styles().findStyle(
master->attributeNS(KoXmlNS::style, "page-layout-name", QString()));
KoPageLayout pageLayout;
pageLayout.loadOdf(*style);
setSize(QSizeF(pageLayout.width, pageLayout.height));
} else {
dbgKrita << "No master page found!";
return false;
}
KoOdfLoadingContext context(odfStore.styles(), odfStore.store());
KoShapeLoadingContext shapeContext(context, 0);
KoXmlElement layerElement;
forEachElement(layerElement, context.stylesReader().layerSet()) {
if (!loadOdf(layerElement, shapeContext)) {
dbgKrita << "Could not load vector layer!";
return false;
}
}
KoXmlElement child;
forEachElement(child, page) {
KoShape * shape = KoShapeRegistry::instance()->createShapeFromOdf(child, shapeContext);
if (shape) {
addShape(shape);
}
}
return true;
-
}
void KisShapeSelection::setUpdatesEnabled(bool enabled)
{
m_model->setUpdatesEnabled(enabled);
}
bool KisShapeSelection::updatesEnabled() const
{
return m_model->updatesEnabled();
}
KUndo2Command* KisShapeSelection::resetToEmpty()
{
return new KisTakeAllShapesCommand(this, true);
}
bool KisShapeSelection::isEmpty() const
{
return !m_model->count();
}
QPainterPath KisShapeSelection::outlineCache() const
{
return m_outline;
}
bool KisShapeSelection::outlineCacheValid() const
{
return true;
}
void KisShapeSelection::recalculateOutlineCache()
{
QList<KoShape*> shapesList = shapes();
QPainterPath outline;
Q_FOREACH (KoShape * shape, shapesList) {
QTransform shapeMatrix = shape->absoluteTransformation(0);
outline = outline.united(shapeMatrix.map(shape->outline()));
}
QTransform resolutionMatrix;
resolutionMatrix.scale(m_image->xRes(), m_image->yRes());
m_outline = resolutionMatrix.map(outline);
}
void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection)
{
Q_ASSERT(projection);
Q_ASSERT(m_image);
QRectF boundingRect = outlineCache().boundingRect();
renderSelection(projection, boundingRect.toAlignedRect());
}
void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r)
{
Q_ASSERT(projection);
renderSelection(projection, r);
}
void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& r)
{
Q_ASSERT(projection);
Q_ASSERT(m_image);
const qint32 MASK_IMAGE_WIDTH = 256;
const qint32 MASK_IMAGE_HEIGHT = 256;
QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
QPainter maskPainter(&polygonMaskImage);
maskPainter.setRenderHint(QPainter::Antialiasing, true);
// Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) {
for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) {
maskPainter.fillRect(polygonMaskImage.rect(), Qt::black);
maskPainter.translate(-x, -y);
maskPainter.fillPath(outlineCache(), Qt::white);
maskPainter.translate(x, y);
qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH);
qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT);
KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight));
do {
(*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y));
} while (it.nextPixel());
}
}
}
KoShapeManager* KisShapeSelection::shapeManager() const
{
return m_canvas->shapeManager();
}
KisShapeSelectionFactory::KisShapeSelectionFactory()
: KoShapeFactoryBase("KisShapeSelection", "selection shape container")
{
setHidden(true);
}
void KisShapeSelection::moveX(qint32 x)
{
Q_FOREACH (KoShape* shape, shapeManager()->shapes()) {
if (shape != this) {
QPointF pos = shape->position();
shape->setPosition(QPointF(pos.x() + x/m_image->xRes(), pos.y()));
}
}
}
void KisShapeSelection::moveY(qint32 y)
{
Q_FOREACH (KoShape* shape, shapeManager()->shapes()) {
if (shape != this) {
QPointF pos = shape->position();
shape->setPosition(QPointF(pos.x(), pos.y() + y/m_image->yRes()));
}
}
}
// TODO same code as in vector layer, refactor!
KUndo2Command* KisShapeSelection::transform(const QTransform &transform) {
QList<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 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 6c26a8da20..c21b7e26e3 100644
--- a/libs/ui/flake/kis_shape_selection.h
+++ b/libs/ui/flake/kis_shape_selection.h
@@ -1,128 +1,131 @@
/*
* 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 KoShapeLayer, public KisSelectionComponent
{
KisShapeSelection(const KisShapeSelection& rhs);
public:
KisShapeSelection(KisImageWSP image, KisSelectionWSP selection);
virtual ~KisShapeSelection();
KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection);
KisSelectionComponent* clone(KisSelection* selection);
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
*/
virtual void renderToProjection(KisPaintDeviceSP projection);
virtual void renderToProjection(KisPaintDeviceSP projection, const QRect& r);
KUndo2Command* resetToEmpty();
bool isEmpty() const;
QPainterPath outlineCache() const;
bool outlineCacheValid() const;
void recalculateOutlineCache();
KoShapeManager *shapeManager() const;
void moveX(qint32 x);
void moveY(qint32 y);
KUndo2Command* transform(const QTransform &transform);
protected:
virtual void paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintcontext);
private:
friend class KisTakeAllShapesCommand;
void setUpdatesEnabled(bool enabled);
bool updatesEnabled() const;
private:
void renderSelection(KisPaintDeviceSP projection, const QRect& r);
KisImageWSP m_image;
QPainterPath m_outline;
KisImageViewConverter* m_converter;
KisShapeSelectionCanvas* m_canvas;
KisShapeSelectionModel* m_model;
friend class KisShapeSelectionModel;
};
class KRITAUI_EXPORT KisShapeSelectionFactory : public KoShapeFactoryBase
{
public:
KisShapeSelectionFactory();
~KisShapeSelectionFactory() {}
virtual KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const {
Q_UNUSED(documentResources);
return 0;
}
virtual bool supports(const KoXmlElement & e, KoShapeLoadingContext &context) const {
Q_UNUSED(e);
Q_UNUSED(context);
return false;
}
};
#endif
diff --git a/libs/ui/flake/kis_shape_selection_canvas.cpp b/libs/ui/flake/kis_shape_selection_canvas.cpp
index e0cbe5aa8f..c8a431510f 100644
--- a/libs/ui/flake/kis_shape_selection_canvas.cpp
+++ b/libs/ui/flake/kis_shape_selection_canvas.cpp
@@ -1,93 +1,99 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* 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_shape_selection_canvas.h"
#include <QPainter>
#include <KoShapeManager.h>
+#include <KoSelectedShapesProxySimple.h>
#include <KoUnit.h>
KisShapeSelectionCanvas::KisShapeSelectionCanvas()
: KoCanvasBase(0)
, m_shapeManager(new KoShapeManager(this))
+ , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
{
}
KisShapeSelectionCanvas::~KisShapeSelectionCanvas()
{
- delete m_shapeManager;
}
void KisShapeSelectionCanvas::gridSize(QPointF *offset, QSizeF *spacing) const
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
Q_UNUSED(offset);
Q_UNUSED(spacing);
}
bool KisShapeSelectionCanvas::snapToGrid() const
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
return false;
}
void KisShapeSelectionCanvas::addCommand(KUndo2Command *)
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
}
KoShapeManager *KisShapeSelectionCanvas::shapeManager() const
{
- return m_shapeManager;
+ return m_shapeManager.data();
+}
+
+KoSelectedShapesProxy *KisShapeSelectionCanvas::selectedShapesProxy() const
+{
+ return m_selectedShapesProxy.data();
}
void KisShapeSelectionCanvas::updateCanvas(const QRectF& rc)
{
Q_UNUSED(rc);
}
KoToolProxy * KisShapeSelectionCanvas::toolProxy() const
{
// Q_ASSERT(false); // This should never be called as this canvas should have no tools.
return 0;
}
KoViewConverter *KisShapeSelectionCanvas::viewConverter() const
{
return 0;
}
QWidget* KisShapeSelectionCanvas::canvasWidget()
{
return 0;
}
const QWidget* KisShapeSelectionCanvas::canvasWidget() const
{
return 0;
}
KoUnit KisShapeSelectionCanvas::unit() const
{
Q_ASSERT(false); // This should never be called as this canvas should have no tools.
return KoUnit(KoUnit::Point);
}
diff --git a/libs/ui/flake/kis_shape_selection_canvas.h b/libs/ui/flake/kis_shape_selection_canvas.h
index 7fa652d6dc..f50a2410b7 100644
--- a/libs/ui/flake/kis_shape_selection_canvas.h
+++ b/libs/ui/flake/kis_shape_selection_canvas.h
@@ -1,60 +1,63 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
* 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_CANVAS_H
#define KIS_SHAPE_SELECTION_CANVAS_H
+#include <QScopedPointer>
#include <KoCanvasBase.h>
#include <kis_types.h>
class KoShapeManager;
class KoToolProxy;
class KoViewConverter;
class KUndo2Command;
class QWidget;
class KoUnit;
/**
* Dummy canvas just to have a shapemanager for the shape selection
*/
class KisShapeSelectionCanvas : public KoCanvasBase
{
Q_OBJECT
public:
KisShapeSelectionCanvas();
virtual ~KisShapeSelectionCanvas();
void gridSize(QPointF *offset, QSizeF *spacing) const;
bool snapToGrid() const;
void addCommand(KUndo2Command *command);
- KoShapeManager *shapeManager() const;
+ KoShapeManager *shapeManager() const override;
+ KoSelectedShapesProxy *selectedShapesProxy() const override;
void updateCanvas(const QRectF& rc);
KoToolProxy * toolProxy() const;
KoViewConverter *viewConverter() const;
QWidget* canvasWidget();
const QWidget* canvasWidget() const;
KoUnit unit() const;
virtual void updateInputMethodInfo() {}
virtual void setCursor(const QCursor &) {}
private:
- KoShapeManager * m_shapeManager;
+ QScopedPointer<KoShapeManager> m_shapeManager;
+ QScopedPointer<KoSelectedShapesProxy> m_selectedShapesProxy;
};
#endif
diff --git a/libs/ui/flake/kis_shape_selection_model.cpp b/libs/ui/flake/kis_shape_selection_model.cpp
index 994ac5b45f..3225b05b77 100644
--- a/libs/ui/flake/kis_shape_selection_model.cpp
+++ b/libs/ui/flake/kis_shape_selection_model.cpp
@@ -1,191 +1,193 @@
/*
* 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_shape_selection_model.h"
#include "kis_debug.h"
#include <KoShapeContainer.h>
#include <KoShapeBackground.h>
#include <KoShapeManager.h>
#include "kis_shape_selection.h"
#include "kis_selection.h"
#include "kis_image.h"
#include "kis_update_selection_job.h"
KisShapeSelectionModel::KisShapeSelectionModel(KisImageWSP image, KisSelectionWSP selection, KisShapeSelection* shapeSelection)
: m_image(image)
, m_parentSelection(selection)
, m_shapeSelection(shapeSelection)
, m_updateSignalCompressor(new KisSignalCompressor(300, KisSignalCompressor::POSTPONE, this))
, m_updatesEnabled(true)
, m_fullUpdateRequested(false)
{
connect(m_updateSignalCompressor, SIGNAL(timeout()), SLOT(startUpdateJob()));
}
KisShapeSelectionModel::~KisShapeSelectionModel()
{
m_image = 0;
m_parentSelection = 0;
}
void KisShapeSelectionModel::requestUpdate(const QRect &updateRect)
{
m_shapeSelection->recalculateOutlineCache();
if (m_updatesEnabled) {
m_fullUpdateRequested |= updateRect.isEmpty();
m_updateRect = !m_fullUpdateRequested ? m_updateRect | updateRect : QRect();
m_updateSignalCompressor->start();
}
}
void KisShapeSelectionModel::startUpdateJob()
{
if (m_image.isValid()) {
m_image->addSpontaneousJob(new KisUpdateSelectionJob(m_parentSelection, m_updateRect));
}
m_updateRect = QRect();
m_fullUpdateRequested = false;
}
void KisShapeSelectionModel::add(KoShape *child)
{
if (!m_shapeSelection) return;
if (m_shapeMap.contains(child))
return;
- child->setStroke(0);
+ child->setStroke(KoShapeStrokeModelSP());
child->setBackground( QSharedPointer<KoShapeBackground>(0));
m_shapeMap.insert(child, child->boundingRect());
m_shapeSelection->shapeManager()->addShape(child);
QRect updateRect = child->boundingRect().toAlignedRect();
if (m_image.isValid()) {
QTransform matrix;
matrix.scale(m_image->xRes(), m_image->yRes());
updateRect = matrix.mapRect(updateRect);
}
if (m_shapeMap.count() == 1) {
// The shape is the first one, so the shape selection just got created
// Pixel selection provides no longer the datamanager of the selection
// so update the whole selection
requestUpdate(QRect());
} else {
requestUpdate(updateRect);
}
}
void KisShapeSelectionModel::remove(KoShape *child)
{
if (!m_shapeMap.contains(child)) return;
QRect updateRect = child->boundingRect().toAlignedRect();
m_shapeMap.remove(child);
if (m_shapeSelection) {
m_shapeSelection->shapeManager()->remove(child);
}
if (m_image.isValid()) {
QTransform matrix;
matrix.scale(m_image->xRes(), m_image->yRes());
updateRect = matrix.mapRect(updateRect);
if (m_shapeSelection) { // No m_shapeSelection indicates the selection is being deleted
requestUpdate(updateRect);
}
}
}
void KisShapeSelectionModel::setUpdatesEnabled(bool enabled)
{
m_updatesEnabled = enabled;
}
bool KisShapeSelectionModel::updatesEnabled() const
{
return m_updatesEnabled;
}
void KisShapeSelectionModel::setClipped(const KoShape *child, bool clipping)
{
Q_UNUSED(child);
Q_UNUSED(clipping);
}
bool KisShapeSelectionModel::isClipped(const KoShape *child) const
{
Q_UNUSED(child);
return false;
}
void KisShapeSelectionModel::setInheritsTransform(const KoShape *shape, bool inherit)
{
Q_UNUSED(shape);
Q_UNUSED(inherit);
}
bool KisShapeSelectionModel::inheritsTransform(const KoShape *shape) const
{
Q_UNUSED(shape);
return false;
}
int KisShapeSelectionModel::count() const
{
return m_shapeMap.count();
}
QList<KoShape*> KisShapeSelectionModel::shapes() const
{
return QList<KoShape*>(m_shapeMap.keys());
}
void KisShapeSelectionModel::containerChanged(KoShapeContainer *, KoShape::ChangeType)
{
}
void KisShapeSelectionModel::childChanged(KoShape * child, KoShape::ChangeType type)
{
if (!m_shapeSelection) return;
+
+ // TODO: check if still needed
if (type == KoShape::ParentChanged) return;
QRectF changedRect = m_shapeMap[child];
changedRect = changedRect.united(child->boundingRect());
m_shapeMap[child] = child->boundingRect();
if (m_image.isValid()) {
QTransform matrix;
matrix.scale(m_image->xRes(), m_image->yRes());
changedRect = matrix.mapRect(changedRect);
}
requestUpdate(changedRect.toAlignedRect());
}
bool KisShapeSelectionModel::isChildLocked(const KoShape *child) const
{
return child->isGeometryProtected() || child->parent()->isGeometryProtected();
}
void KisShapeSelectionModel::setShapeSelection(KisShapeSelection* selection)
{
m_shapeSelection = selection;
}
diff --git a/libs/ui/forms/wdggeneralsettings.ui b/libs/ui/forms/wdggeneralsettings.ui
index 9a32182829..9581309781 100644
--- a/libs/ui/forms/wdggeneralsettings.ui
+++ b/libs/ui/forms/wdggeneralsettings.ui
@@ -1,683 +1,690 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgGeneralSettings</class>
<widget class="QWidget" name="WdgGeneralSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>759</width>
<height>468</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>552</width>
<height>295</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<layout class="QGridLayout" name="gridLayout">
<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>Cursor</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<layout class="QFormLayout" name="formLayout_2">
<property name="horizontalSpacing">
<number>10</number>
</property>
<property name="verticalSpacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="textLabel1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Cursor Shape:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_cmbCursorShape"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="textLabel1_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Outline Shape:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="m_cmbOutlineShape"/>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="m_showOutlinePainting">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Show brush outline while painting</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Window</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Multiple Document Mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="m_cmbMDIType">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Subwindows</string>
</property>
</item>
<item>
<property name="text">
<string>Tabs</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Background Image (overrides color):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0">
<item>
<widget class="QLabel" name="m_backgroundimage">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="m_bnFileName">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearBgImageButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Window Background:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisColorButton" name="m_mdiColor">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Don't show contents when moving sub-windows:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="m_chkRubberBand">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Show on-canvas popup messages:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="m_chkCanvasMessages">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" 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>
<item row="5" column="1">
<widget class="QCheckBox" name="m_chkHiDPI">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Enable Hi-DPI support:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="m_chkSingleApplication">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_r98">
<property name="text">
<string>Allow only one instance of Krita:</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="Tools">
<attribute name="title">
<string>Tools</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Tool Options Location (needs restart)</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="m_radioToolOptionsInDocker">
<property name="text">
<string>In Doc&amp;ker</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="m_radioToolOptionsInToolbar">
<property name="text">
<string>In Tool&amp;bar</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_chkSwitchSelectionCtrlAlt">
<property name="text">
<string>Switch Control/Alt Selection Modifiers</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="Miscellaneous">
<attribute name="title">
<string>Miscellaneous</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="m_autosaveCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Autosave every:</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisIntParseSpinBox" name="m_autosaveSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="suffix">
<string> min</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>1440</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="value">
<number>15</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="m_chkCompressKra">
<property name="text">
<string>Compress .kra files more (slows loading/saving)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="m_backupFileCheckBox">
<property name="text">
<string>Create backup file </string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="m_chkConvertOnImport">
<property name="text">
<string>On importing images as layers, convert to the image colorspace</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Undo stack size:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="KisIntParseSpinBox" name="m_undoStackSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Number of Palette Presets</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="KisIntParseSpinBox" name="m_favoritePresetsSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>75</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>30</number>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QCheckBox" name="chkShowRootLayer">
<property name="text">
<string>Show root layer</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QCheckBox" name="m_hideSplashScreen">
<property name="text">
<string>Hide splash screen on startup</string>
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="m_chkNativeFileDialog">
<property name="toolTip">
<string>Warning: if you enable this setting and the file dialogs do weird stuff, do not report a bug.</string>
</property>
<property name="text">
<string>Enable native file dialogs (warning: may not work correctly on some systems)</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Maximum brush size:</string>
</property>
</widget>
</item>
<item row="15" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSpinBox" name="intMaxBrushSize">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>The maximum diameter of a brush in pixels.</string>
</property>
<property name="suffix">
<string comment="pixel">px</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>(Needs restart)</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="17" column="1">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>504</width>
<height>13</height>
</size>
</property>
</spacer>
</item>
+ <item row="16" column="1">
+ <widget class="QCheckBox" name="m_chkCacheAnimatioInBackground">
+ <property name="text">
+ <string>Recalculate animation cache in background</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
- <customwidget>
- <class>KisColorButton</class>
- <extends>QPushButton</extends>
- <header>kis_color_button.h</header>
- </customwidget>
<customwidget>
<class>KisIntParseSpinBox</class>
<extends>QSpinBox</extends>
<header>kis_int_parse_spin_box.h</header>
</customwidget>
+ <customwidget>
+ <class>KisColorButton</class>
+ <extends>QPushButton</extends>
+ <header>kis_color_button.h</header>
+ </customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/forms/wdgstopgradienteditor.ui b/libs/ui/forms/wdgstopgradienteditor.ui
index 5dc6ea2b51..32e61154b8 100644
--- a/libs/ui/forms/wdgstopgradienteditor.ui
+++ b/libs/ui/forms/wdgstopgradienteditor.ui
@@ -1,114 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KisWdgStopGradientEditor</class>
<widget class="QWidget" name="KisWdgStopGradientEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>500</width>
- <height>250</height>
+ <width>368</width>
+ <height>167</height>
</rect>
</property>
- <property name="minimumSize">
- <size>
- <width>500</width>
- <height>250</height>
- </size>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
- <widget class="QLabel" name="label">
+ <widget class="QLabel" name="lblName">
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="nameedit"/>
</item>
<item>
<widget class="QToolButton" name="buttonReverse">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
- <widget class="KisStopGradientSliderWidget" name="gradientSlider" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>1</verstretch>
- </sizepolicy>
- </property>
- <property name="focusPolicy">
- <enum>Qt::ClickFocus</enum>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="KisStopGradientSliderWidget" name="gradientSlider" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::ClickFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QToolButton" name="buttonReverseSecond">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
</item>
<item>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="stopLabel">
+ <property name="text">
+ <string>Stop: </string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="KisColorButton" name="colorButton">
<property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>30</height>
- </size>
- </property>
<property name="font">
<font>
<family>Sans Serif</family>
<pointsize>9</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>false</underline>
<strikeout>false</strikeout>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
</widget>
</item>
+ <item>
+ <widget class="KisDoubleSliderSpinBox" name="opacitySlider" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
- <item>
- <widget class="KisDoubleSliderSpinBox" name="opacitySlider" native="true"/>
- </item>
</layout>
</widget>
<customwidgets>
+ <customwidget>
+ <class>KisDoubleSliderSpinBox</class>
+ <extends>QWidget</extends>
+ <header location="global">kis_slider_spin_box.h</header>
+ <container>1</container>
+ </customwidget>
<customwidget>
<class>KisColorButton</class>
<extends>QPushButton</extends>
<header>kis_color_button.h</header>
</customwidget>
<customwidget>
<class>KisStopGradientSliderWidget</class>
<extends>QWidget</extends>
<header>kis_stopgradient_slider_widget.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>KisDoubleSliderSpinBox</class>
- <extends>QWidget</extends>
- <header>kis_slider_spin_box.h</header>
- <container>1</container>
- </customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/libs/ui/input/kis_change_primary_setting_action.cpp b/libs/ui/input/kis_change_primary_setting_action.cpp
index 98b5d9552d..95bf5c16ab 100644
--- a/libs/ui/input/kis_change_primary_setting_action.cpp
+++ b/libs/ui/input/kis_change_primary_setting_action.cpp
@@ -1,87 +1,87 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_change_primary_setting_action.h"
#include <klocalizedstring.h>
#include "kis_input_manager.h"
#include "kis_canvas2.h"
#include "kis_tool_proxy.h"
#include <QApplication>
#include "kis_cursor.h"
KisChangePrimarySettingAction::KisChangePrimarySettingAction()
: KisAbstractInputAction("Change Primary Setting")
{
setName(i18n("Change Primary Setting"));
setDescription(i18n("The <i>Change Primary Setting</i> action changes a tool's \"Primary Setting\", for example the brush size for the brush tool."));
}
KisChangePrimarySettingAction::~KisChangePrimarySettingAction()
{
}
void KisChangePrimarySettingAction::activate(int shortcut)
{
Q_UNUSED(shortcut);
inputManager()->toolProxy()->activateToolAction(KisTool::AlternateChangeSize);
}
void KisChangePrimarySettingAction::deactivate(int shortcut)
{
Q_UNUSED(shortcut);
inputManager()->toolProxy()->deactivateToolAction(KisTool::AlternateChangeSize);
}
int KisChangePrimarySettingAction::priority() const
{
return 8;
}
void KisChangePrimarySettingAction::begin(int shortcut, QEvent *event)
{
KisAbstractInputAction::begin(shortcut, event);
if (event) {
QMouseEvent targetEvent(QEvent::MouseButtonPress, eventPosF(event), Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier);
inputManager()->toolProxy()->forwardEvent(KisToolProxy::BEGIN, KisTool::AlternateChangeSize, &targetEvent, event);
}
}
void KisChangePrimarySettingAction::end(QEvent *event)
{
if (event) {
QMouseEvent targetEvent(QEvent::MouseButtonRelease, eventPosF(event), Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier);
inputManager()->toolProxy()->forwardEvent(KisToolProxy::END, KisTool::AlternateChangeSize, &targetEvent, event);
}
KisAbstractInputAction::end(event);
}
void KisChangePrimarySettingAction::inputEvent(QEvent* event)
{
if (event && (event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove)) {
- QMouseEvent targetEvent(QEvent::MouseButtonRelease, eventPos(event), Qt::NoButton, Qt::LeftButton, Qt::ShiftModifier);
+ QMouseEvent targetEvent(QEvent::MouseMove, eventPos(event), Qt::NoButton, Qt::LeftButton, Qt::ShiftModifier);
inputManager()->toolProxy()->forwardEvent(KisToolProxy::CONTINUE, KisTool::AlternateChangeSize, &targetEvent, event);
}
}
diff --git a/libs/ui/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp
index a88d3075f1..41ff1bd069 100644
--- a/libs/ui/input/kis_input_manager.cpp
+++ b/libs/ui/input/kis_input_manager.cpp
@@ -1,611 +1,633 @@
/* This file is part of the KDE project
*
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
* Copyright (C) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_input_manager.h"
#include <kis_debug.h>
#include <QQueue>
#include <klocalizedstring.h>
#include <QApplication>
#include <KoToolManager.h>
#include "kis_tool_proxy.h"
#include <kis_config.h>
#include <kis_canvas2.h>
#include <KisViewManager.h>
#include <kis_image.h>
#include <kis_canvas_resource_provider.h>
#include <kis_favorite_resource_manager.h>
#include "kis_abstract_input_action.h"
#include "kis_tool_invocation_action.h"
#include "kis_pan_action.h"
#include "kis_alternate_invocation_action.h"
#include "kis_rotate_canvas_action.h"
#include "kis_zoom_action.h"
#include "kis_show_palette_action.h"
#include "kis_change_primary_setting_action.h"
#include "kis_shortcut_matcher.h"
#include "kis_stroke_shortcut.h"
#include "kis_single_action_shortcut.h"
#include "kis_touch_shortcut.h"
#include "kis_input_profile.h"
#include "kis_input_profile_manager.h"
#include "kis_shortcut_configuration.h"
#include <input/kis_tablet_debugger.h>
#include <kis_signal_compressor.h>
#include "kis_extended_modifiers_mapper.h"
#include "kis_input_manager_p.h"
template <typename T>
uint qHash(QPointer<T> value) {
return reinterpret_cast<quintptr>(value.data());
}
#define start_ignore_cursor_events() d->blockMouseEvents()
#define stop_ignore_cursor_events() d->allowMouseEvents()
#define break_if_should_ignore_cursor_events() if (d->ignoringQtCursorEvents()) break;
#define break_if_tablet_active() if (d->tabletActive) break;
// Touch rejection: if touch is disabled on canvas, no need to block mouse press events
#define touch_start_block_press_events() d->touchHasBlockedPressEvents = d->disableTouchOnCanvas;
#define touch_stop_block_press_events() d->touchHasBlockedPressEvents = false;
#define break_if_touch_blocked_press_events() if (d->touchHasBlockedPressEvents) break;
#define touch_eat_one_mouse_press() if (d->disableTouchOnCanvas) d->eatOneMousePress();
KisInputManager::KisInputManager(QObject *parent)
: QObject(parent), d(new Private(this))
{
d->setupActions();
+ connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)),
+ SLOT(slotAboutToChangeTool()));
connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)),
SLOT(slotToolChanged()));
connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent()));
QApplication::instance()->
installEventFilter(new Private::ProximityNotifier(d, this));
}
KisInputManager::~KisInputManager()
{
delete d;
}
void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas)
{
d->canvasSwitcher.addCanvas(canvas);
}
void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas)
{
d->canvasSwitcher.removeCanvas(canvas);
}
void KisInputManager::toggleTabletLogger()
{
KisTabletDebugger::instance()->toggleDebugging();
}
void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority)
{
Private::PriorityList::iterator begin = d->priorityEventFilter.begin();
Private::PriorityList::iterator it = begin;
Private::PriorityList::iterator end = d->priorityEventFilter.end();
it = std::find_if(begin, end,
[filter] (const Private::PriorityPair &a) { return a.second == filter; });
if (it != end) return;
it = std::find_if(begin, end,
[priority] (const Private::PriorityPair &a) { return a.first > priority; });
d->priorityEventFilter.insert(it, qMakePair(priority, filter));
d->priorityEventFilterSeqNo++;
}
void KisInputManager::detachPriorityEventFilter(QObject *filter)
{
Private::PriorityList::iterator it = d->priorityEventFilter.begin();
Private::PriorityList::iterator end = d->priorityEventFilter.end();
it = std::find_if(it, end,
[filter] (const Private::PriorityPair &a) { return a.second == filter; });
if (it != end) {
d->priorityEventFilter.erase(it);
}
}
void KisInputManager::setupAsEventFilter(QObject *receiver)
{
if (d->eventsReceiver) {
d->eventsReceiver->removeEventFilter(this);
}
d->eventsReceiver = receiver;
if (d->eventsReceiver) {
d->eventsReceiver->installEventFilter(this);
}
}
void KisInputManager::stopIgnoringEvents()
{
stop_ignore_cursor_events();
}
void KisInputManager::slotFocusOnEnter(bool value)
{
Q_UNUSED(value);
// not used anymore
}
#if defined (__clang__)
#pragma GCC diagnostic ignored "-Wswitch"
#endif
bool KisInputManager::eventFilter(QObject* object, QEvent* event)
{
if (object != d->eventsReceiver) return false;
if (d->eventEater.eventFilter(object, event)) return false;
if (!d->matcher.hasRunningShortcut()) {
int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo;
for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) {
const QPointer<QObject> &filter = it->second;
if (filter.isNull()) {
it = d->priorityEventFilter.erase(it);
d->priorityEventFilterSeqNo++;
savedPriorityEventFilterSeqNo++;
continue;
}
if (filter->eventFilter(object, event)) return true;
/**
* If the filter removed itself from the filters list or
* added something there, just exit the loop
*/
if (d->priorityEventFilterSeqNo != savedPriorityEventFilterSeqNo) {
return true;
}
++it;
}
// KoToolProxy needs to pre-process some events to ensure the
// global shortcuts (not the input manager's ones) are not
// executed, in particular, this line will accept events when the
// tool is in text editing, preventing shortcut triggering
d->toolProxy->processEvent(event);
}
// Continue with the actual switch statement...
return eventFilterImpl(event);
}
template <class Event>
bool KisInputManager::compressMoveEventCommon(Event *event)
{
/**
* We construct a copy of this event object, so we must ensure it
* has a correct type.
*/
static_assert(std::is_same<Event, QMouseEvent>::value ||
std::is_same<Event, QTabletEvent>::value,
"event should be a mouse or a tablet event");
bool retval = false;
/**
* Compress the events if the tool doesn't need high resolution input
*/
if ((event->type() == QEvent::MouseMove ||
event->type() == QEvent::TabletMove) &&
(!d->matcher.supportsHiResInputEvents() ||
d->testingCompressBrushEvents)) {
d->compressedMoveEvent.reset(new Event(*event));
d->moveEventCompressor.start();
/**
* On Linux Qt eats the rest of unneeded events if we
* ignore the first of the chunk of tablet events. So
* generally we should never activate this feature. Only
* for testing purposes!
*/
if (d->testingAcceptCompressedTabletEvents) {
event->setAccepted(true);
}
retval = true;
} else {
slotCompressedMoveEvent();
retval = d->handleCompressedTabletEvent(event);
}
return retval;
}
bool KisInputManager::eventFilterImpl(QEvent * event)
{
// TODO: Handle touch events correctly.
bool retval = false;
switch (event->type()) {
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick: {
d->debugEvent<QMouseEvent, true>(event);
//Block mouse press events on Genius tablets
break_if_tablet_active();
break_if_should_ignore_cursor_events();
break_if_touch_blocked_press_events();
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(mouseEvent->button(), mouseEvent);
}
//Reset signal compressor to prevent processing events before press late
d->resetCompressor();
event->setAccepted(retval);
break;
}
case QEvent::MouseButtonRelease: {
d->debugEvent<QMouseEvent, true>(event);
break_if_should_ignore_cursor_events();
break_if_touch_blocked_press_events();
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent);
event->setAccepted(retval);
break;
}
case QEvent::ShortcutOverride: {
d->debugEvent<QKeyEvent, false>(event);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
if (!keyEvent->isAutoRepeat()) {
retval = d->matcher.keyPressed(key);
} else {
retval = d->matcher.autoRepeatedKeyPressed(key);
}
/**
* Workaround for temporary switching of tools by
* KoCanvasControllerWidget. We don't need this switch because
* we handle it ourselves.
*/
retval |= !d->forwardAllEventsToTool &&
(keyEvent->key() == Qt::Key_Space ||
keyEvent->key() == Qt::Key_Escape);
break;
}
case QEvent::KeyRelease: {
d->debugEvent<QKeyEvent, false>(event);
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (!keyEvent->isAutoRepeat()) {
Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent);
retval = d->matcher.keyReleased(key);
}
break;
}
case QEvent::MouseMove: {
d->debugEvent<QMouseEvent, true>(event);
break_if_should_ignore_cursor_events();
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
retval = compressMoveEventCommon(mouseEvent);
break;
}
case QEvent::Wheel: {
d->debugEvent<QWheelEvent, false>(event);
QWheelEvent *wheelEvent = static_cast<QWheelEvent*>(event);
KisSingleActionShortcut::WheelAction action;
/**
* Ignore delta 0 events on OSX, since they are triggered by tablet
* proximity when using Wacom devices.
*/
#ifdef Q_OS_OSX
if(wheelEvent->delta() == 0) {
retval = true;
break;
}
#endif
if(wheelEvent->orientation() == Qt::Horizontal) {
if(wheelEvent->delta() < 0) {
action = KisSingleActionShortcut::WheelRight;
}
else {
action = KisSingleActionShortcut::WheelLeft;
}
}
else {
if(wheelEvent->delta() > 0) {
action = KisSingleActionShortcut::WheelUp;
}
else {
action = KisSingleActionShortcut::WheelDown;
}
}
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.wheelEvent(action, wheelEvent);
break;
}
case QEvent::Enter:
d->debugEvent<QEvent, false>(event);
d->containsPointer = true;
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
stop_ignore_cursor_events();
touch_stop_block_press_events();
d->matcher.enterEvent();
break;
case QEvent::Leave:
d->debugEvent<QEvent, false>(event);
d->containsPointer = false;
/**
* We won't get a TabletProximityLeave event when the tablet
* is hovering above some other widget, so restore cursor
* events processing right now.
*/
stop_ignore_cursor_events();
touch_stop_block_press_events();
d->matcher.leaveEvent();
break;
case QEvent::FocusIn:
d->debugEvent<QEvent, false>(event);
KisAbstractInputAction::setInputManager(this);
//Clear all state so we don't have half-matched shortcuts dangling around.
d->matcher.reinitialize();
{ // Emulate pressing of the key that are already pressed
KisExtendedModifiersMapper mapper;
Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers();
Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) {
QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers);
eventFilterImpl(&kevent);
}
}
stop_ignore_cursor_events();
break;
+
+ case QEvent::FocusOut: {
+ d->debugEvent<QEvent, false>(event);
+ KisAbstractInputAction::setInputManager(this);
+
+ QPointF currentLocalPos =
+ canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
+
+ d->matcher.lostFocusEvent(currentLocalPos);
+
+ break;
+ }
case QEvent::TabletRelease: {
#ifdef Q_OS_OSX
stop_ignore_cursor_events();
#endif
// break_if_touch_blocked_press_events();
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent);
retval = true;
event->setAccepted(true);
break;
}
case QEvent::TabletMove: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
retval = compressMoveEventCommon(tabletEvent);
/**
* The flow of tablet events means the tablet is in the
* proximity area, so activate it even when the
* TabletEnterProximity event was missed (may happen when
* changing focus of the window with tablet in the proximity
* area)
*/
start_ignore_cursor_events();
break;
}
case QEvent::TabletPress: {
d->debugEvent<QTabletEvent, false>(event);
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
if (d->tryHidePopupPalette()) {
retval = true;
} else {
//Make sure the input actions know we are active.
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent);
}
event->setAccepted(true);
retval = true;
start_ignore_cursor_events();
//Reset signal compressor to prevent processing events before press late
d->resetCompressor();
d->eatOneMousePress();
break;
}
case QEvent::TouchBegin:
touch_start_block_press_events();
touch_eat_one_mouse_press();
if (d->tryHidePopupPalette()) {
retval = true;
} else {
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.touchBeginEvent(static_cast<QTouchEvent*>(event));
event->accept();
}
break;
case QEvent::TouchUpdate: {
QTouchEvent *tevent = static_cast<QTouchEvent*>(event);
#ifdef Q_OS_OSX
int count = 0;
Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) {
if (point.state() != Qt::TouchPointReleased) {
count++;
}
}
if (count < 2 && tevent->touchPoints().length() > count) {
touch_stop_block_press_events();
d->saveTouchEvent(tevent);
retval = d->matcher.touchEndEvent(tevent);
delete d->lastTouchEvent;
d->lastTouchEvent = 0;
} else {
#endif
touch_start_block_press_events();
KisAbstractInputAction::setInputManager(this);
retval = d->matcher.touchUpdateEvent(tevent);
#ifdef Q_OS_OSX
}
#endif
event->accept();
break;
}
case QEvent::TouchEnd:
touch_stop_block_press_events();
d->saveTouchEvent(static_cast<QTouchEvent*>(event));
retval = d->matcher.touchEndEvent(static_cast<QTouchEvent*>(event));
event->accept();
delete d->lastTouchEvent;
d->lastTouchEvent = 0;
break;
default:
break;
}
return !retval ? d->processUnhandledEvent(event) : true;
}
void KisInputManager::slotCompressedMoveEvent()
{
if (d->compressedMoveEvent) {
// touch_stop_block_press_events();
(void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data());
d->compressedMoveEvent.reset();
dbgKrita << "Compressed move event received.";
} else {
dbgKrita << "Unexpected empty move event";
}
}
KisCanvas2* KisInputManager::canvas() const
{
return d->canvas;
}
KisToolProxy* KisInputManager::toolProxy() const
{
return d->toolProxy;
}
QTouchEvent *KisInputManager::lastTouchEvent() const
{
return d->lastTouchEvent;
}
+void KisInputManager::slotAboutToChangeTool()
+{
+ QPointF currentLocalPos =
+ canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
+
+ d->matcher.lostFocusEvent(currentLocalPos);
+}
+
void KisInputManager::slotToolChanged()
{
KoToolManager *toolManager = KoToolManager::instance();
KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId());
if (tool && tool->isInTextMode()) {
d->forwardAllEventsToTool = true;
d->matcher.suppressAllActions(true);
d->maskSyntheticEvents(tool->maskSyntheticEvents());
} else {
d->forwardAllEventsToTool = false;
d->matcher.suppressAllActions(false);
}
}
QPointF KisInputManager::widgetToDocument(const QPointF& position)
{
const QPointF half = QPointF(.5f, .5f);
QPointF pixel = position + half;
return d->canvas->coordinatesConverter()->widgetToDocument(pixel);
}
void KisInputManager::profileChanged()
{
d->matcher.clearShortcuts();
KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile();
if (profile) {
const QList<KisShortcutConfiguration*> shortcuts = profile->allShortcuts();
for (KisShortcutConfiguration * const shortcut : shortcuts) {
dbgUI << "Adding shortcut" << shortcut->keys() << "for action" << shortcut->action()->name();
switch(shortcut->type()) {
case KisShortcutConfiguration::KeyCombinationType:
d->addKeyShortcut(shortcut->action(), shortcut->mode(), shortcut->keys());
break;
case KisShortcutConfiguration::MouseButtonType:
d->addStrokeShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->buttons());
break;
case KisShortcutConfiguration::MouseWheelType:
d->addWheelShortcut(shortcut->action(), shortcut->mode(), shortcut->keys(), shortcut->wheel());
break;
case KisShortcutConfiguration::GestureType:
d->addTouchShortcut(shortcut->action(), shortcut->mode(), shortcut->gesture());
break;
default:
break;
}
}
}
else {
dbgKrita << "No Input Profile Found: canvas interaction will be impossible";
}
}
diff --git a/libs/ui/input/kis_input_manager.h b/libs/ui/input/kis_input_manager.h
index 50edf6c539..8d64613d0c 100644
--- a/libs/ui/input/kis_input_manager.h
+++ b/libs/ui/input/kis_input_manager.h
@@ -1,127 +1,128 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_INPUTMANAGER_H
#define KIS_INPUTMANAGER_H
#include <QObject>
#include <kritaui_export.h>
class QPointF;
class QTouchEvent;
class KisToolProxy;
class KisCanvas2;
/**
* \brief Central object to manage canvas input.
*
* The Input Manager class manages all canvas input. It is created
* by KisCanvas2 and processes all events related to input sent to the
* canvas.
*
* The Input Manager keeps track of a set of actions and a set of
* shortcuts. The actions are pre-defined while the shortcuts are
* set from configuration.
*
* For each event, it will try to determine if there is a shortcut that
* matches the input. It will then activate this action and pass all
* consecutive events on to this action.
*
* \sa KisAbstractInputAction
*/
class KRITAUI_EXPORT KisInputManager : public QObject
{
Q_OBJECT
public:
/**
* Constructor.
*/
KisInputManager(QObject *parent);
/**
* Destructor.
*/
~KisInputManager();
void addTrackedCanvas(KisCanvas2 *canvas);
void removeTrackedCanvas(KisCanvas2 *canvas);
void toggleTabletLogger();
/**
* Installs the input manager as an event filter for \p receiver.
* Please note that KisInputManager is supposed to handle events
* for a single receiver only. This is defined by the fact that it
* resends some of the events back through the Qt's queue to the
* reciever. That is why the input manager will assert when it gets
* an event with wrong destination.
*/
void setupAsEventFilter(QObject *receiver);
/**
* Event filter method. Overridden from QObject.
*/
bool eventFilter(QObject* object, QEvent* event );
void attachPriorityEventFilter(QObject *filter, int priority = 0);
void detachPriorityEventFilter(QObject *filter);
/**
* Return the canvas this input manager is associated with.
*/
KisCanvas2 *canvas() const;
/**
* The tool proxy of the current application.
*/
KisToolProxy *toolProxy() const;
/**
* Touch events are special, too.
*
* \return a touch event if there was one, otherwise 0
*/
QTouchEvent *lastTouchEvent() const;
/**
* Convert a widget position to a document position.
*/
QPointF widgetToDocument(const QPointF &position);
public Q_SLOTS:
void stopIgnoringEvents();
void slotFocusOnEnter(bool value);
private Q_SLOTS:
+ void slotAboutToChangeTool();
void slotToolChanged();
void profileChanged();
void slotCompressedMoveEvent();
private:
bool eventFilterImpl(QEvent * event);
template <class Event>
bool compressMoveEventCommon(Event *event);
private:
class Private;
Private* const d;
};
#endif // KIS_INPUTMANAGER_H
diff --git a/libs/ui/input/kis_shortcut_matcher.cpp b/libs/ui/input/kis_shortcut_matcher.cpp
index 08206ee9e4..db255e050f 100644
--- a/libs/ui/input/kis_shortcut_matcher.cpp
+++ b/libs/ui/input/kis_shortcut_matcher.cpp
@@ -1,531 +1,555 @@
/*
* 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_shortcut_matcher.h"
#include <QEvent>
#include <QMouseEvent>
#include <QTabletEvent>
#include "kis_abstract_input_action.h"
#include "kis_stroke_shortcut.h"
#include "kis_touch_shortcut.h"
#ifdef DEBUG_MATCHER
#include <kis_debug.h>
#define DEBUG_ACTION(text) dbgInput << __FUNCTION__ << "-" << text;
#define DEBUG_SHORTCUT(text, shortcut) dbgInput << __FUNCTION__ << "-" << text << "act:" << shortcut->action()->name();
#define DEBUG_KEY(text) dbgInput << __FUNCTION__ << "-" << text << "keys:" << m_d->keys;
#define DEBUG_BUTTON_ACTION(text, button) dbgInput << __FUNCTION__ << "-" << text << "button:" << button << "btns:" << m_d->buttons << "keys:" << m_d->keys;
#define DEBUG_EVENT_ACTION(text, event) if (event) {dbgInput << __FUNCTION__ << "-" << text << "type:" << event->type();}
#else
#define DEBUG_ACTION(text)
#define DEBUG_KEY(text)
#define DEBUG_SHORTCUT(text, shortcut)
#define DEBUG_BUTTON_ACTION(text, button)
#define DEBUG_EVENT_ACTION(text, event)
#endif
class Q_DECL_HIDDEN KisShortcutMatcher::Private
{
public:
Private()
: runningShortcut(0)
, readyShortcut(0)
, touchShortcut(0)
, suppressAllActions(false)
, cursorEntered(false)
, usingTouch(false)
{}
~Private()
{
qDeleteAll(singleActionShortcuts);
qDeleteAll(strokeShortcuts);
qDeleteAll(touchShortcuts);
}
QList<KisSingleActionShortcut*> singleActionShortcuts;
QList<KisStrokeShortcut*> strokeShortcuts;
QList<KisTouchShortcut*> touchShortcuts;
QSet<Qt::Key> keys; // Model of currently pressed keys
QSet<Qt::MouseButton> buttons; // Model of currently pressed buttons
KisStrokeShortcut *runningShortcut;
KisStrokeShortcut *readyShortcut;
QList<KisStrokeShortcut*> candidateShortcuts;
KisTouchShortcut *touchShortcut;
bool suppressAllActions;
bool cursorEntered;
bool usingTouch;
inline bool actionsSuppressed() const {
return suppressAllActions || !cursorEntered;
}
inline bool actionsSuppressedIgnoreFocus() const {
return suppressAllActions;
}
};
KisShortcutMatcher::KisShortcutMatcher()
: m_d(new Private)
{}
KisShortcutMatcher::~KisShortcutMatcher()
{
delete m_d;
}
bool KisShortcutMatcher::hasRunningShortcut() const
{
return m_d->runningShortcut;
}
void KisShortcutMatcher::addShortcut(KisSingleActionShortcut *shortcut)
{
m_d->singleActionShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut(KisStrokeShortcut *shortcut)
{
m_d->strokeShortcuts.append(shortcut);
}
void KisShortcutMatcher::addShortcut( KisTouchShortcut* shortcut )
{
m_d->touchShortcuts.append(shortcut);
}
bool KisShortcutMatcher::supportsHiResInputEvents()
{
return
m_d->runningShortcut &&
m_d->runningShortcut->action() &&
m_d->runningShortcut->action()->supportsHiResInputEvents();
}
bool KisShortcutMatcher::keyPressed(Qt::Key key)
{
bool retval = false;
if (m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, records show key was already pressed"); }
if (!m_d->runningShortcut) {
retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, m_d->keys);
}
m_d->keys.insert(key);
DEBUG_KEY("Pressed");
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::autoRepeatedKeyPressed(Qt::Key key)
{
bool retval = false;
if (!m_d->keys.contains(key)) { DEBUG_ACTION("Peculiar, autorepeated key but can't remember it was pressed"); }
if (!m_d->runningShortcut) {
// Autorepeated key should not be included in the shortcut
QSet<Qt::Key> filteredKeys = m_d->keys;
filteredKeys.remove(key);
retval = tryRunSingleActionShortcutImpl(key, (QEvent*)0, filteredKeys);
}
return retval;
}
bool KisShortcutMatcher::keyReleased(Qt::Key key)
{
if (!m_d->keys.contains(key)) reset("Peculiar, key released but can't remember it was pressed");
else m_d->keys.remove(key);
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return false;
}
bool KisShortcutMatcher::buttonPressed(Qt::MouseButton button, QEvent *event)
{
DEBUG_BUTTON_ACTION("entered", button);
bool retval = false;
if (m_d->usingTouch) {
return retval;
}
if (m_d->buttons.contains(button)) { DEBUG_ACTION("Peculiar, button was already pressed."); }
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
retval = tryRunReadyShortcut(button, event);
}
m_d->buttons.insert(button);
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::buttonReleased(Qt::MouseButton button, QEvent *event)
{
DEBUG_BUTTON_ACTION("entered", button);
bool retval = false;
if (m_d->usingTouch) {
return retval;
}
if (m_d->runningShortcut && !m_d->readyShortcut) {
retval = tryEndRunningShortcut(button, event);
DEBUG_BUTTON_ACTION("ended", button);
}
if (!m_d->buttons.contains(button)) reset("Peculiar, button released but we can't remember it was pressed");
else m_d->buttons.remove(button);
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
return retval;
}
bool KisShortcutMatcher::wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
{
if (m_d->runningShortcut || m_d->usingTouch) {
DEBUG_ACTION("Wheel event canceled.");
return false;
}
return tryRunWheelShortcut(wheelAction, event);
}
bool KisShortcutMatcher::pointerMoved(QEvent *event)
{
if (m_d->usingTouch || !m_d->runningShortcut) {
return false;
}
m_d->runningShortcut->action()->inputEvent(event);
return true;
}
void KisShortcutMatcher::enterEvent()
{
m_d->cursorEntered = true;
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
void KisShortcutMatcher::leaveEvent()
{
m_d->cursorEntered = false;
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
bool KisShortcutMatcher::touchBeginEvent( QTouchEvent* event )
{
Q_UNUSED(event)
return true;
}
bool KisShortcutMatcher::touchUpdateEvent( QTouchEvent* event )
{
bool retval = false;
if( m_d->touchShortcut && !m_d->touchShortcut->match( event ) ) {
retval = tryEndTouchShortcut( event );
}
if( !m_d->touchShortcut ) {
retval = tryRunTouchShortcut( event );
} else {
m_d->touchShortcut->action()->inputEvent( event );
retval = true;
}
return retval;
}
bool KisShortcutMatcher::touchEndEvent( QTouchEvent* event )
{
m_d->usingTouch = false; // we need to say we are done because qt will not send further event
// we should try and end the shortcut too (it might be that there is none? (sketch))
if( tryEndTouchShortcut( event ) ) {
return true;
}
return false;
}
Qt::MouseButtons listToFlags(const QList<Qt::MouseButton> &list) {
Qt::MouseButtons flags;
Q_FOREACH (Qt::MouseButton b, list) {
flags |= b;
}
return flags;
}
void KisShortcutMatcher::reinitialize()
{
reset("reinitialize");
if (!m_d->runningShortcut) {
prepareReadyShortcuts();
tryActivateReadyShortcut();
}
}
+void KisShortcutMatcher::lostFocusEvent(const QPointF &localPos)
+{
+ if (m_d->runningShortcut) {
+ forceEndRunningShortcut(localPos);
+ }
+}
+
void KisShortcutMatcher::reset()
{
m_d->keys.clear();
m_d->buttons.clear();
DEBUG_ACTION("reset!");
}
void KisShortcutMatcher::reset(QString msg)
{
m_d->keys.clear();
m_d->buttons.clear();
Q_UNUSED(msg);
DEBUG_ACTION(msg);
}
void KisShortcutMatcher::suppressAllActions(bool value)
{
m_d->suppressAllActions = value;
}
void KisShortcutMatcher::clearShortcuts()
{
reset("Clearing shortcuts");
qDeleteAll(m_d->singleActionShortcuts);
m_d->singleActionShortcuts.clear();
qDeleteAll(m_d->strokeShortcuts);
qDeleteAll(m_d->touchShortcuts);
m_d->strokeShortcuts.clear();
m_d->candidateShortcuts.clear();
m_d->touchShortcuts.clear();
m_d->runningShortcut = 0;
m_d->readyShortcut = 0;
}
bool KisShortcutMatcher::tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event)
{
return tryRunSingleActionShortcutImpl(wheelAction, event, m_d->keys);
}
// Note: sometimes event can be zero!!
template<typename T, typename U>
bool KisShortcutMatcher::tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState)
{
if (m_d->actionsSuppressedIgnoreFocus()) {
DEBUG_EVENT_ACTION("Event suppressed", event)
return false;
}
KisSingleActionShortcut *goodCandidate = 0;
Q_FOREACH (KisSingleActionShortcut *s, m_d->singleActionShortcuts) {
if(s->isAvailable() &&
s->match(keysState, param) &&
(!goodCandidate || s->priority() > goodCandidate->priority())) {
goodCandidate = s;
}
}
if (goodCandidate) {
DEBUG_EVENT_ACTION("Beginning action for event", event)
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
goodCandidate->action()->end(0);
} else {
DEBUG_EVENT_ACTION("Could not match a candidate for event", event)
}
return goodCandidate;
}
void KisShortcutMatcher::prepareReadyShortcuts()
{
m_d->candidateShortcuts.clear();
if (m_d->actionsSuppressed()) return;
Q_FOREACH (KisStrokeShortcut *s, m_d->strokeShortcuts) {
if (s->matchReady(m_d->keys, m_d->buttons)) {
m_d->candidateShortcuts.append(s);
}
}
}
bool KisShortcutMatcher::tryRunReadyShortcut( Qt::MouseButton button, QEvent* event )
{
KisStrokeShortcut *goodCandidate = 0;
Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
if (s->isAvailable() &&
s->matchBegin(button) &&
(!goodCandidate || s->priority() > goodCandidate->priority())) {
goodCandidate = s;
}
}
if (goodCandidate) {
if (m_d->readyShortcut) {
if (m_d->readyShortcut != goodCandidate) {
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
}
m_d->readyShortcut = 0;
} else {
DEBUG_EVENT_ACTION("Matched *new* shortcut for event", event);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
}
DEBUG_SHORTCUT("Starting new action", goodCandidate);
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
m_d->runningShortcut = goodCandidate;
}
return goodCandidate;
}
void KisShortcutMatcher::tryActivateReadyShortcut()
{
KisStrokeShortcut *goodCandidate = 0;
Q_FOREACH (KisStrokeShortcut *s, m_d->candidateShortcuts) {
if (!goodCandidate || s->priority() > goodCandidate->priority()) {
goodCandidate = s;
}
}
if (goodCandidate) {
if (m_d->readyShortcut && m_d->readyShortcut != goodCandidate) {
DEBUG_SHORTCUT("Deactivated previous shortcut action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
if (!m_d->readyShortcut) {
DEBUG_SHORTCUT("Preparing new ready action", goodCandidate);
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
m_d->readyShortcut = goodCandidate;
}
} else if (m_d->readyShortcut) {
DEBUG_SHORTCUT("Deactivating action", m_d->readyShortcut);
m_d->readyShortcut->action()->deactivate(m_d->readyShortcut->shortcutIndex());
m_d->readyShortcut = 0;
}
}
bool KisShortcutMatcher::tryEndRunningShortcut( Qt::MouseButton button, QEvent* event )
{
Q_ASSERT(m_d->runningShortcut);
Q_ASSERT(!m_d->readyShortcut);
if (m_d->runningShortcut->matchBegin(button)) {
if (m_d->runningShortcut->action()) {
DEBUG_EVENT_ACTION("Ending running shortcut at event", event);
KisAbstractInputAction* action = m_d->runningShortcut->action();
int shortcutIndex = m_d->runningShortcut->shortcutIndex();
action->end(event);
action->deactivate(shortcutIndex);
}
m_d->runningShortcut = 0;
}
return !m_d->runningShortcut;
}
+void KisShortcutMatcher::forceEndRunningShortcut(const QPointF &localPos)
+{
+ Q_ASSERT(m_d->runningShortcut);
+ Q_ASSERT(!m_d->readyShortcut);
+
+ if (m_d->runningShortcut->action()) {
+ DEBUG_ACTION("Forced ending running shortcut at event");
+ KisAbstractInputAction* action = m_d->runningShortcut->action();
+ int shortcutIndex = m_d->runningShortcut->shortcutIndex();
+
+ QMouseEvent event = m_d->runningShortcut->fakeEndEvent(localPos);
+
+ action->end(&event);
+ action->deactivate(shortcutIndex);
+ }
+ m_d->runningShortcut = 0;
+}
bool KisShortcutMatcher::tryRunTouchShortcut( QTouchEvent* event )
{
KisTouchShortcut *goodCandidate = 0;
if (m_d->actionsSuppressed())
return false;
Q_FOREACH (KisTouchShortcut* shortcut, m_d->touchShortcuts) {
if( shortcut->match( event ) && (!goodCandidate || shortcut->priority() > goodCandidate->priority()) ) {
goodCandidate = shortcut;
}
}
if( goodCandidate ) {
if( m_d->runningShortcut ) {
QMouseEvent mouseEvent(QEvent::MouseButtonRelease,
event->touchPoints().at(0).pos().toPoint(),
Qt::LeftButton,
Qt::LeftButton,
event->modifiers());
tryEndRunningShortcut(Qt::LeftButton, &mouseEvent);
}
goodCandidate->action()->activate(goodCandidate->shortcutIndex());
goodCandidate->action()->begin(goodCandidate->shortcutIndex(), event);
m_d->touchShortcut = goodCandidate;
m_d->usingTouch = true;
}
return goodCandidate;
}
bool KisShortcutMatcher::tryEndTouchShortcut( QTouchEvent* event )
{
if(m_d->touchShortcut) {
m_d->touchShortcut->action()->end(event);
m_d->touchShortcut->action()->deactivate(m_d->touchShortcut->shortcutIndex());
m_d->touchShortcut = 0;
return true;
}
return false;
}
diff --git a/libs/ui/input/kis_shortcut_matcher.h b/libs/ui/input/kis_shortcut_matcher.h
index 69498d1b8c..dcd1d93899 100644
--- a/libs/ui/input/kis_shortcut_matcher.h
+++ b/libs/ui/input/kis_shortcut_matcher.h
@@ -1,230 +1,238 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_SHORTCUT_MATCHER_H
#define __KIS_SHORTCUT_MATCHER_H
#include <QList>
#include "kis_single_action_shortcut.h"
class QEvent;
class QWheelEvent;
class QTouchEvent;
class QString;
+class QPointF;
class KisStrokeShortcut;
class KisTouchShortcut;
/**
* The class that manages connections between shortcuts and actions.
*
* It processes input events and generates state transitions for the
* actions basing on the data, represented by the shortcuts.
*
* The class works with two types of actions: long running
* (represented by KisStrokeShortcuts) and "atomic"
* (KisSingleActionShortcut). The former one invole some long
* interaction with the user by means of a mouse cursor or a tablet,
* the latter one simple action like "Zoom 100%" or "Reset Rotation".
*
* The single action shortcuts are handled quite easily. The matcher
* listens to the events coming, manages two lists of the pressed keys
* and buttons and when their content corresponds to some single
* action shortcut it just runs this shortcut once.
*
* The strategy for handling the stroke shortcuts is a bit more
* complex. Each such action may be in one of the three states:
*
* Idle <-> Ready <-> Running
*
* In "Idle" state the action is completely inactive and has no access
* to the user
*
* When the action is in "Ready" state, it means that all the
* modifiers for the action are already pressed and we are only
* waiting for a user to press the mouse button and start a stroke. In
* this state the action can show the user its Cursor to notify the user
* what is going to happen next.
*
* In the "Running" state, the action has full access to the user
* input and is considered to perform all the work it was created for.
*
* To implement such state transitions for the actions,
* KisShortcutMatcher first forms a list of the actions which can be
* moved to a ready state (m_d->readyShortcuts), then chooses the one
* with the highest priority to be the only shortcut in the "Ready"
* state and activates it (m_d->readyShortcut). Then when the user
* presses the mouse button, the matcher looks through the list of
* ready shortcuts, chooses which will be running now, deactivates (if
* needed) currently activated action and starts the chosen one.
*
* \see KisSingleActionShortcut
* \see KisStrokeShortcut
*/
class KRITAUI_EXPORT KisShortcutMatcher
{
public:
KisShortcutMatcher();
~KisShortcutMatcher();
bool hasRunningShortcut() const;
void addShortcut(KisSingleActionShortcut *shortcut);
void addShortcut(KisStrokeShortcut *shortcut);
void addShortcut(KisTouchShortcut *shortcut);
/**
* Returns true if the currently running shortcut supports
* processing hi resolution flow of events from the tablet
* device. In most of the cases (except of the painting itself)
* too many events make the execution of the action too slow, so
* the action can decide whether it needs it.
*/
bool supportsHiResInputEvents();
/**
* Handles a key press event.
* No autorepeat events should be passed to this method.
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool keyPressed(Qt::Key key);
/**
* Handles a key press event that has been generated by the
* autorepeat.
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool autoRepeatedKeyPressed(Qt::Key key);
/**
* Handles a key release event.
* No autorepeat events should be passed to this method.
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool keyReleased(Qt::Key key);
/**
* Handles button presses from a tablet or mouse.
*
* \param event the event that caused this call.
* Must be of type QTabletEvent or QMouseEvent.
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool buttonPressed(Qt::MouseButton button, QEvent *event);
/**
* Handles the mouse button release event
*
* \param event the event that caused this call.
* Must be of type QTabletEvent or QMouseEvent.
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool buttonReleased(Qt::MouseButton button, QEvent *event);
/**
* Handles the mouse wheel event
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool wheelEvent(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event);
/**
* Handles tablet and mouse move events.
*
* \param event the event that caused this call
*
* \return whether the event has been handled successfully and
* should be eaten by the events filter
*/
bool pointerMoved(QEvent *event);
/**
* Handle cursor's Enter event.
* We never eat it because it might be used by someone else
*/
void enterEvent();
/**
* Handle cursor's Leave event.
* We never eat it because it might be used by someone else
*/
void leaveEvent();
bool touchBeginEvent(QTouchEvent *event);
bool touchUpdateEvent(QTouchEvent *event);
bool touchEndEvent(QTouchEvent *event);
/**
* Resets the internal state of the matcher and activates the
* prepared action if possible.
*
* This should be done when the window has lost the focus for
* some time, so that several events could be lost
*/
void reinitialize();
+ /**
+ * Kirta lost focus, it means that all the running actions should be ended
+ * forcefully.
+ */
+ void lostFocusEvent(const QPointF &localPos);
+
/**
* Disables the start of any actions.
*
* WARNING: the actions that has been started before this call
* will *not* be ended. They will be ended in their usual way,
* when the mouse button will be released.
*/
void suppressAllActions(bool value);
/**
* Remove all shortcuts that have been registered.
*/
void clearShortcuts();
private:
friend class KisInputManagerTest;
void reset();
void reset(QString msg);
bool tryRunWheelShortcut(KisSingleActionShortcut::WheelAction wheelAction, QWheelEvent *event);
template<typename T, typename U> bool tryRunSingleActionShortcutImpl(T param, U *event, const QSet<Qt::Key> &keysState);
void prepareReadyShortcuts();
bool tryRunReadyShortcut( Qt::MouseButton button, QEvent* event );
void tryActivateReadyShortcut();
bool tryEndRunningShortcut( Qt::MouseButton button, QEvent* event );
+ void forceEndRunningShortcut(const QPointF &localPos);
bool tryRunTouchShortcut(QTouchEvent *event);
bool tryEndTouchShortcut(QTouchEvent *event);
private:
class Private;
Private * const m_d;
};
#endif /* __KIS_SHORTCUT_MATCHER_H */
diff --git a/libs/ui/input/kis_show_palette_action.cpp b/libs/ui/input/kis_show_palette_action.cpp
index e44fbc005b..c10da1fa24 100644
--- a/libs/ui/input/kis_show_palette_action.cpp
+++ b/libs/ui/input/kis_show_palette_action.cpp
@@ -1,55 +1,104 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_show_palette_action.h"
#include <QCursor>
+#include <QMenu>
#include <klocalizedstring.h>
#include <kis_favorite_resource_manager.h>
#include <kis_canvas2.h>
+#include "kis_tool_proxy.h"
#include "kis_input_manager.h"
KisShowPaletteAction::KisShowPaletteAction()
- : KisAbstractInputAction("Show Popup Palette")
+ : KisAbstractInputAction("Show Popup Palette"),
+ m_requestedWithStylus(false)
{
setName(i18n("Show Popup Palette"));
setDescription(i18n("The <i>Show Popup Palette</i> displays the popup palette."));
}
KisShowPaletteAction::~KisShowPaletteAction()
{
}
int KisShowPaletteAction::priority() const
{
return 1;
}
void KisShowPaletteAction::begin(int, QEvent *event)
{
- QPoint pos = eventPos(event);
- if (pos.isNull()) {
- pos = inputManager()->canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
+ m_menu = inputManager()->toolProxy()->popupActionsMenu();
+
+ if (m_menu) {
+ m_requestedWithStylus = event->type() == QEvent::TabletPress;
+
+ /**
+ * Opening a menu changes the focus of the windows, so we should not open it
+ * inside the filtering loop. Just raise it using the timer.
+ */
+ QTimer::singleShot(0, this, SLOT(slotShowMenu()));
+
+ } else {
+ QPoint pos = eventPos(event);
+ if (pos.isNull()) {
+ pos = inputManager()->canvas()->canvasWidget()->mapFromGlobal(QCursor::pos());
+ }
+
+ inputManager()->canvas()->slotShowPopupPalette(pos);
}
+}
+
+struct SinglePressEventEater : public QObject
+{
+ bool eventFilter(QObject *, QEvent *event) {
+ if (hungry && event->type() == QEvent::MouseButtonPress) {
+ hungry = false;
+ return true;
+ }
- inputManager()->canvas()->slotShowPopupPalette(pos);
+ return false;
+ }
+
+private:
+ bool hungry = true;
+};
+
+void KisShowPaletteAction::slotShowMenu()
+{
+ if (m_menu) {
+
+ QPoint stylusOffset;
+ QScopedPointer<SinglePressEventEater> eater;
+
+ if (m_requestedWithStylus) {
+ eater.reset(new SinglePressEventEater());
+ m_menu->installEventFilter(eater.data());
+ stylusOffset += QPoint(10,10);
+ }
+
+ m_menu->exec(QCursor::pos() + stylusOffset);
+ m_menu.clear();
+ }
}
diff --git a/libs/ui/input/kis_show_palette_action.h b/libs/ui/input/kis_show_palette_action.h
index 55bd69b7a6..6cd2606845 100644
--- a/libs/ui/input/kis_show_palette_action.h
+++ b/libs/ui/input/kis_show_palette_action.h
@@ -1,40 +1,53 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_SHOW_PALETTE_ACTION_H
#define KIS_SHOW_PALETTE_ACTION_H
#include "kis_abstract_input_action.h"
+#include <QObject>
+#include <QPointer>
+class QMenu;
+
/**
* \brief Show Palette implementation of KisAbstractInputAction.
*
* The Show Palette action shows the popup palette.
*/
-class KisShowPaletteAction : public KisAbstractInputAction
+class KisShowPaletteAction : public QObject, public KisAbstractInputAction
{
+ Q_OBJECT
+
public:
explicit KisShowPaletteAction();
virtual ~KisShowPaletteAction();
virtual int priority() const;
virtual void begin(int, QEvent *);
+
+private Q_SLOTS:
+ void slotShowMenu();
+
+private:
+ QPointer<QMenu> m_menu;
+ bool m_requestedWithStylus;
};
#endif // KIS_SHOW_PALETTE_ACTION_H
diff --git a/libs/ui/input/kis_stroke_shortcut.cpp b/libs/ui/input/kis_stroke_shortcut.cpp
index cb82717c4d..bab17a1fb6 100644
--- a/libs/ui/input/kis_stroke_shortcut.cpp
+++ b/libs/ui/input/kis_stroke_shortcut.cpp
@@ -1,82 +1,91 @@
/*
* 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_stroke_shortcut.h"
#include "kis_abstract_input_action.h"
+#include <QMouseEvent>
+
+
class Q_DECL_HIDDEN KisStrokeShortcut::Private
{
public:
QSet<Qt::Key> modifiers;
QSet<Qt::MouseButton> buttons;
};
KisStrokeShortcut::KisStrokeShortcut(KisAbstractInputAction *action, int index)
: KisAbstractShortcut(action, index),
m_d(new Private)
{
}
KisStrokeShortcut::~KisStrokeShortcut()
{
delete m_d;
}
int KisStrokeShortcut::priority() const
{
int buttonScore = 0;
Q_FOREACH (Qt::MouseButton button, m_d->buttons) {
buttonScore += Qt::XButton2 - button;
}
return m_d->modifiers.size() * 0xFFFF + buttonScore * 0xFF + action()->priority();
}
void KisStrokeShortcut::setButtons(const QSet<Qt::Key> &modifiers,
const QSet<Qt::MouseButton> &buttons)
{
if (buttons.size() == 0) return;
m_d->modifiers = modifiers;
m_d->buttons = buttons;
}
bool KisStrokeShortcut::matchReady(const QSet<Qt::Key> &modifiers,
const QSet<Qt::MouseButton> &buttons)
{
bool modifiersOk =
(m_d->modifiers.isEmpty() && action()->canIgnoreModifiers()) ||
compareKeys(m_d->modifiers, modifiers);
if (!modifiersOk || buttons.size() < m_d->buttons.size() - 1) {
return false;
}
Q_FOREACH (Qt::MouseButton button, buttons) {
if (!m_d->buttons.contains(button)) return false;
}
return true;
}
bool KisStrokeShortcut::matchBegin(Qt::MouseButton button)
{
return m_d->buttons.contains(button);
}
+
+QMouseEvent KisStrokeShortcut::fakeEndEvent(const QPointF &localPos) const
+{
+ Qt::MouseButton button = !m_d->buttons.isEmpty() ? *m_d->buttons.begin() : Qt::NoButton;
+ return QMouseEvent(QEvent::MouseButtonRelease, localPos, button, Qt::NoButton, Qt::NoModifier);
+}
diff --git a/libs/ui/input/kis_stroke_shortcut.h b/libs/ui/input/kis_stroke_shortcut.h
index 033e6a4dd2..57bf476b28 100644
--- a/libs/ui/input/kis_stroke_shortcut.h
+++ b/libs/ui/input/kis_stroke_shortcut.h
@@ -1,80 +1,85 @@
/*
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_STROKE_SHORTCUT_H
#define __KIS_STROKE_SHORTCUT_H
#include "kis_abstract_shortcut.h"
+class QMouseEvent;
+class QPointF;
+
/**
* This class represents a shortcut that starts an action that can
* involve pressing the mouse button and, probably, moving the cursor.
*
* The stroke shortcut may be represented as a simple state machine:
* It transits between 3 states:
*
* Idle <-> Ready <-> Running
*
* The possibility of trasition between Idle <-> Ready is defined
* with a matchReady() method. The transition Ready <-> Running is
* defined by matchBegin(). The Ready state is used for showing the
* user the cursor of the upcoming action and the Running state shows
* that the action linked to the shortcut should be activated.
*/
class KRITAUI_EXPORT KisStrokeShortcut : public KisAbstractShortcut
{
public:
KisStrokeShortcut(KisAbstractInputAction *action, int index);
~KisStrokeShortcut();
int priority() const;
/**
* Sets the configuration for this shortcut
*
* \param modifiers keyboard keys that should be holded
* for the shortcut to trigger
* \param buttons mouse buttons that should be pressed (simultaneously)
* for the shortcut to trigger
*/
void setButtons(const QSet<Qt::Key> &modifiers,
const QSet<Qt::MouseButton> &buttons);
/**
* Reports whether all but one buttons and modifiers are pressed
* for the shortcut. Such configuration means that the input manager
* can show the user that pressing the mouse button will start some
* action. This can be done with, e.g. changing the cursor.
*/
bool matchReady(const QSet<Qt::Key> &modifiers,
const QSet<Qt::MouseButton> &buttons);
/**
* Reports whether the shortcut can transit form the "Ready"
* to "Running" state. It means that the last button of the shortcut
* is pressed.
*/
bool matchBegin(Qt::MouseButton button);
+ QMouseEvent fakeEndEvent(const QPointF &localPos) const;
+
private:
class Private;
Private * const m_d;
};
#endif /* __KIS_STROKE_SHORTCUT_H */
diff --git a/libs/ui/input/kis_tool_invocation_action.cpp b/libs/ui/input/kis_tool_invocation_action.cpp
index 575559208b..60e1a28672 100644
--- a/libs/ui/input/kis_tool_invocation_action.cpp
+++ b/libs/ui/input/kis_tool_invocation_action.cpp
@@ -1,166 +1,185 @@
/* This file is part of the KDE project
* Copyright (C) 2012 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_invocation_action.h"
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoToolManager.h>
#include <kis_tool_proxy.h>
#include <kis_canvas2.h>
#include <kis_coordinates_converter.h>
#include "kis_tool.h"
#include "kis_input_manager.h"
#include "kis_image.h"
class KisToolInvocationAction::Private
{
public:
- Private() : active(false) { }
+ Private()
+ : active(false),
+ lineToolActivated(false)
+ {
+ }
bool active;
+ bool lineToolActivated;
};
KisToolInvocationAction::KisToolInvocationAction()
: KisAbstractInputAction("Tool Invocation")
, d(new Private)
{
setName(i18n("Tool Invocation"));
setDescription(i18n("The <i>Tool Invocation</i> action invokes the current tool, for example, using the brush tool, it will start painting."));
QHash<QString, int> indexes;
indexes.insert(i18n("Activate"), ActivateShortcut);
indexes.insert(i18n("Confirm"), ConfirmShortcut);
indexes.insert(i18n("Cancel"), CancelShortcut);
indexes.insert(i18n("Activate Line Tool"), LineToolShortcut);
setShortcutIndexes(indexes);
}
KisToolInvocationAction::~KisToolInvocationAction()
{
delete d;
}
void KisToolInvocationAction::activate(int shortcut)
{
Q_UNUSED(shortcut);
if (!inputManager()) return;
if (shortcut == LineToolShortcut) {
KoToolManager::instance()->switchToolTemporaryRequested("KritaShape/KisToolLine");
+ d->lineToolActivated = true;
}
inputManager()->toolProxy()->activateToolAction(KisTool::Primary);
}
void KisToolInvocationAction::deactivate(int shortcut)
{
Q_UNUSED(shortcut);
if (!inputManager()) return;
inputManager()->toolProxy()->deactivateToolAction(KisTool::Primary);
- if (shortcut == LineToolShortcut) {
- KoToolManager::instance()->switchBackRequested();
+ if (shortcut == LineToolShortcut && d->lineToolActivated) {
+ d->lineToolActivated = false;
+ KoToolManager::instance()->switchBackRequested();
}
}
int KisToolInvocationAction::priority() const
{
return 0;
}
bool KisToolInvocationAction::canIgnoreModifiers() const
{
return true;
}
void KisToolInvocationAction::begin(int shortcut, QEvent *event)
{
if (shortcut == ActivateShortcut || shortcut == LineToolShortcut) {
d->active =
inputManager()->toolProxy()->forwardEvent(
KisToolProxy::BEGIN, KisTool::Primary, event, event);
} else if (shortcut == ConfirmShortcut) {
QKeyEvent pressEvent(QEvent::KeyPress, Qt::Key_Return, 0);
inputManager()->toolProxy()->keyPressEvent(&pressEvent);
QKeyEvent releaseEvent(QEvent::KeyRelease, Qt::Key_Return, 0);
inputManager()->toolProxy()->keyReleaseEvent(&releaseEvent);
/**
* All the tools now have a KisTool::requestStrokeEnd() method,
* so they should use this instead of direct filtering Enter key
* press. Until all the tools support it, we just duplicate the
* key event and the method call
*/
inputManager()->canvas()->image()->requestStrokeEnd();
+
+ /**
+ * Some tools would like to distinguish automated requestStrokeEnd()
+ * calls from explicit user actions. Just let them do it!
+ *
+ * Please note that this call should happen **after**
+ * requestStrokeEnd(). Some of the tools will switch to another
+ * tool on this request, and this (next) tool does not expect to
+ * get requestStrokeEnd() right after switching in.
+ */
+ inputManager()->toolProxy()->explicitUserStrokeEndRequest();
+
} else if (shortcut == CancelShortcut) {
/**
* The tools now have a KisTool::requestStrokeCancellation() method,
* so just request it.
*/
inputManager()->canvas()->image()->requestStrokeCancellation();
}
}
void KisToolInvocationAction::end(QEvent *event)
{
if (d->active) {
inputManager()->toolProxy()->
forwardEvent(KisToolProxy::END, KisTool::Primary, event, event);
d->active = false;
}
KisAbstractInputAction::end(event);
}
void KisToolInvocationAction::inputEvent(QEvent* event)
{
if (!d->active) return;
if (!inputManager()) return;
if (!inputManager()->toolProxy()) return;
inputManager()->toolProxy()->
forwardEvent(KisToolProxy::CONTINUE, KisTool::Primary, event, event);
}
void KisToolInvocationAction::processUnhandledEvent(QEvent* event)
{
bool savedState = d->active;
d->active = true;
inputEvent(event);
d->active = savedState;
}
bool KisToolInvocationAction::supportsHiResInputEvents() const
{
return inputManager()->toolProxy()->primaryActionSupportsHiResEvents();
}
bool KisToolInvocationAction::isShortcutRequired(int shortcut) const
{
//These really all are pretty important for basic user interaction.
Q_UNUSED(shortcut)
return true;
}
diff --git a/libs/ui/kis_animation_cache_populator.cpp b/libs/ui/kis_animation_cache_populator.cpp
index 5c5d384590..5e83991a0b 100644
--- a/libs/ui/kis_animation_cache_populator.cpp
+++ b/libs/ui/kis_animation_cache_populator.cpp
@@ -1,401 +1,312 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_animation_cache_populator.h"
#include <functional>
#include <QTimer>
#include <QMutex>
#include <QtConcurrent>
+#include "kis_config.h"
+#include "kis_config_notifier.h"
#include "KisPart.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_image_animation_interface.h"
#include "kis_canvas2.h"
#include "kis_time_range.h"
#include "kis_animation_frame_cache.h"
#include "kis_update_info.h"
#include "kis_signal_auto_connection.h"
#include "kis_idle_watcher.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_keyframe_channel.h"
+#include "KisAnimationCacheRegenerator.h"
+
struct KisAnimationCachePopulator::Private
{
KisAnimationCachePopulator *q;
KisPart *part;
QTimer timer;
/**
* Counts up the number of subsequent times Krita has been detected idle.
*/
int idleCounter;
static const int IDLE_COUNT_THRESHOLD = 4;
static const int IDLE_CHECK_INTERVAL = 500;
- static const int WAITING_FOR_FRAME_TIMEOUT = 10000;
static const int BETWEEN_FRAMES_INTERVAL = 10;
int requestedFrame;
KisAnimationFrameCacheSP requestCache;
KisOpenGLUpdateInfoSP requestInfo;
KisSignalAutoConnectionsStore imageRequestConnections;
QFutureWatcher<void> infoConversionWatcher;
+ KisAnimationCacheRegenerator regenerator;
+ bool calculateAnimationCacheInBackground = true;
+
enum State {
NotWaitingForAnything,
WaitingForIdle,
WaitingForFrame,
- WaitingForConvertedFrame,
BetweenFrames
};
State state;
- QMutex mutex;
-
Private(KisAnimationCachePopulator *_q, KisPart *_part)
: q(_q),
part(_part),
idleCounter(0),
requestedFrame(-1),
state(WaitingForIdle)
{
timer.setSingleShot(true);
- connect(&infoConversionWatcher, SIGNAL(finished()), q, SLOT(slotInfoConverted()));
- }
-
- static void processFrameInfo(KisOpenGLUpdateInfoSP info) {
- if (info->needsConversion()) {
- info->convertColorSpace();
- }
- }
-
- void frameReceived(int frame)
- {
- if (frame != requestedFrame) return;
-
- imageRequestConnections.clear();
- requestInfo = requestCache->fetchFrameData(frame);
-
- /**
- * This method is called from the context of the image worker
- * threads, so we cannot modify timers here. Therefore the
- * timers reset and the conversion request will be issued in
- * the main GUI thread.
- */
- emit q->sigPrivateStartWaitingForConvertedFrame();
- }
-
- void infoConverted() {
- KIS_ASSERT_RECOVER(requestInfo && requestCache) {
- enterState(WaitingForIdle);
- return;
- }
-
- requestCache->addConvertedFrameData(requestInfo, requestedFrame);
-
- requestedFrame = 0;
- requestCache = 0;
- requestInfo = 0;
- enterState(BetweenFrames);
}
void timerTimeout() {
switch (state) {
case WaitingForIdle:
case BetweenFrames:
generateIfIdle();
break;
case WaitingForFrame:
- // Request timed out :(
- imageRequestConnections.clear();
- enterState(WaitingForIdle);
- break;
- case WaitingForConvertedFrame:
- KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForConvertedFrame cannot have a timeout. Just skip this message and report a bug");
+ KIS_ASSERT_RECOVER_NOOP(0 && "WaitingForFrame cannot have a timeout. Just skip this message and report a bug");
break;
case NotWaitingForAnything:
KIS_ASSERT_RECOVER_NOOP(0 && "NotWaitingForAnything cannot have a timeout. Just skip this message and report a bug");
break;
}
}
void generateIfIdle()
{
if (part->idleWatcher()->isIdle()) {
idleCounter++;
if (idleCounter >= IDLE_COUNT_THRESHOLD) {
if (!tryRequestGeneration()) {
enterState(NotWaitingForAnything);
}
return;
}
} else {
idleCounter = 0;
}
enterState(WaitingForIdle);
}
bool tryRequestGeneration()
{
// Prioritize the active document
KisAnimationFrameCacheSP activeDocumentCache = KisAnimationFrameCacheSP(0);
KisMainWindow *activeWindow = part->currentMainwindow();
if (activeWindow && activeWindow->activeView()) {
KisCanvas2 *activeCanvas = activeWindow->activeView()->canvasBase();
if (activeCanvas && activeCanvas->frameCache()) {
activeDocumentCache = activeCanvas->frameCache();
// Let's skip frames affected by changes to the active node (on the active document)
// This avoids constant invalidation and regeneration while drawing
KisNodeSP activeNode = activeCanvas->viewManager()->nodeManager()->activeNode();
KisTimeRange skipRange;
if (activeNode) {
int currentTime = activeCanvas->currentImage()->animationInterface()->currentUITime();
const QList<KisKeyframeChannel*> channels =
activeNode->keyframeChannels();
if (!channels.isEmpty()) {
Q_FOREACH (const KisKeyframeChannel *channel, channels) {
skipRange |= channel->affectedFrames(currentTime);
}
} else {
skipRange = KisTimeRange::infinite(0);
}
}
bool requested = tryRequestGeneration(activeDocumentCache, skipRange);
if (requested) return true;
}
}
QList<KisAnimationFrameCache*> caches = KisAnimationFrameCache::caches();
KisAnimationFrameCache *cache;
Q_FOREACH (cache, caches) {
if (cache == activeDocumentCache.data()) {
// We already handled this one...
continue;
}
bool requested = tryRequestGeneration(cache, KisTimeRange());
if (requested) return true;
}
return false;
}
bool tryRequestGeneration(KisAnimationFrameCacheSP cache, KisTimeRange skipRange)
{
KisImageSP image = cache->image();
if (!image) return false;
KisImageAnimationInterface *animation = image->animationInterface();
KisTimeRange currentRange = animation->fullClipRange();
- if (!animation->hasAnimation()) return false;
-
- if (currentRange.isValid()) {
- Q_ASSERT(!currentRange.isInfinite());
-
- // TODO: optimize check for fully-cached case
+ const int frame = KisAnimationCacheRegenerator::calcFirstDirtyFrame(cache, currentRange, skipRange);
- for (int frame = currentRange.start(); frame <= currentRange.end(); frame++) {
- if (skipRange.contains(frame)) {
- if (skipRange.isInfinite()) {
- break;
- } else {
- frame = skipRange.end();
- continue;
- }
- }
-
- if (cache->frameStatus(frame) != KisAnimationFrameCache::Cached) {
- return regenerate(cache, frame);
- }
- }
+ if (frame >= 0) {
+ return regenerate(cache, frame);
}
return false;
}
bool regenerate(KisAnimationFrameCacheSP cache, int frame)
{
- if (state == WaitingForFrame || state == WaitingForConvertedFrame) {
+ if (state == WaitingForFrame) {
// Already busy, deny request
return false;
}
- KIS_ASSERT_RECOVER_NOOP(QThread::currentThread() == q->thread());
-
- KisImageSP image = cache->image();
-
- requestCache = cache;
- requestedFrame = frame;
-
- imageRequestConnections.clear();
- imageRequestConnections.addConnection(
- image->animationInterface(), SIGNAL(sigFrameReady(int)),
- q, SLOT(slotFrameReady(int)),
- Qt::DirectConnection);
-
- imageRequestConnections.addConnection(
- image->animationInterface(), SIGNAL(sigFrameCancelled()),
- q, SLOT(slotFrameCancelled()),
- Qt::AutoConnection);
-
/**
* We should enter the state before the frame is
* requested. Otherwise the signal may come earlier than we
* enter it.
*/
enterState(WaitingForFrame);
- image->animationInterface()->requestFrameRegeneration(frame, image->bounds());
+
+ regenerator.startFrameRegeneration(frame, cache);
return true;
}
QString debugStateToString(State newState) {
QString str = "<unknown>";
switch (newState) {
case WaitingForIdle:
str = "WaitingForIdle";
break;
case WaitingForFrame:
str = "WaitingForFrame";
break;
case NotWaitingForAnything:
str = "NotWaitingForAnything";
break;
- case WaitingForConvertedFrame:
- str = "WaitingForConvertedFrame";
- break;
case BetweenFrames:
str = "BetweenFrames";
break;
}
return str;
}
void enterState(State newState)
{
state = newState;
int timerTimeout = -1;
switch (state) {
case WaitingForIdle:
timerTimeout = IDLE_CHECK_INTERVAL;
break;
case WaitingForFrame:
- timerTimeout = WAITING_FOR_FRAME_TIMEOUT;
+ // the timeout is handled by the regenerator now
+ timerTimeout = -1;
break;
case NotWaitingForAnything:
- case WaitingForConvertedFrame:
// frame conversion cannot be cancelled,
// so there is no timeout
timerTimeout = -1;
break;
case BetweenFrames:
timerTimeout = BETWEEN_FRAMES_INTERVAL;
break;
}
if (timerTimeout >= 0) {
timer.start(timerTimeout);
} else {
timer.stop();
}
}
};
KisAnimationCachePopulator::KisAnimationCachePopulator(KisPart *part)
: m_d(new Private(this, part))
{
connect(&m_d->timer, SIGNAL(timeout()), this, SLOT(slotTimer()));
- connect(this, SIGNAL(sigPrivateStartWaitingForConvertedFrame()), SLOT(slotPrivateStartWaitingForConvertedFrame()));
+
+ connect(&m_d->regenerator, SIGNAL(sigFrameCancelled()), SLOT(slotRegeneratorFrameCancelled()));
+ connect(&m_d->regenerator, SIGNAL(sigFrameFinished()), SLOT(slotRegeneratorFrameReady()));
+
+ connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
+ slotConfigChanged();
}
KisAnimationCachePopulator::~KisAnimationCachePopulator()
{}
bool KisAnimationCachePopulator::regenerate(KisAnimationFrameCacheSP cache, int frame)
{
return m_d->regenerate(cache, frame);
}
-void KisAnimationCachePopulator::slotStart()
-{
- m_d->timer.start();
-}
-
void KisAnimationCachePopulator::slotTimer()
{
m_d->timerTimeout();
}
-void KisAnimationCachePopulator::slotFrameReady(int frame)
+void KisAnimationCachePopulator::slotRequestRegeneration()
{
- m_d->frameReceived(frame);
+ // skip if the user forbade background regeneration
+ if (!m_d->calculateAnimationCacheInBackground) return;
+
+ m_d->enterState(Private::WaitingForIdle);
}
-void KisAnimationCachePopulator::slotFrameCancelled()
+void KisAnimationCachePopulator::slotRegeneratorFrameCancelled()
{
KIS_ASSERT_RECOVER_RETURN(m_d->state == Private::WaitingForFrame);
-
- m_d->timer.stop();
- m_d->imageRequestConnections.clear();
m_d->enterState(Private::NotWaitingForAnything);
}
-void KisAnimationCachePopulator::slotInfoConverted()
+void KisAnimationCachePopulator::slotRegeneratorFrameReady()
{
- m_d->infoConverted();
+ m_d->enterState(Private::BetweenFrames);
}
-void KisAnimationCachePopulator::slotRequestRegeneration()
+void KisAnimationCachePopulator::slotConfigChanged()
{
- m_d->enterState(Private::WaitingForIdle);
-}
-
-void KisAnimationCachePopulator::slotPrivateStartWaitingForConvertedFrame()
-{
- KIS_ASSERT_RECOVER_RETURN(m_d->requestInfo);
-
- m_d->enterState(Private::WaitingForConvertedFrame);
-
- QFuture<void> requestFuture =
- QtConcurrent::run(
- std::bind(&KisAnimationCachePopulator::Private::processFrameInfo,
- m_d->requestInfo));
-
- m_d->infoConversionWatcher.setFuture(requestFuture);
+ KisConfig cfg;
+ m_d->calculateAnimationCacheInBackground = cfg.calculateAnimationCacheInBackground();
}
diff --git a/libs/ui/kis_animation_cache_populator.h b/libs/ui/kis_animation_cache_populator.h
index fc9af320c4..3a117a233d 100644
--- a/libs/ui/kis_animation_cache_populator.h
+++ b/libs/ui/kis_animation_cache_populator.h
@@ -1,63 +1,58 @@
/*
* 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.
*/
#ifndef KIS_ANIMATION_CACHE_POPULATOR_H
#define KIS_ANIMATION_CACHE_POPULATOR_H
#include <QObject>
#include "kis_types.h"
class KisPart;
class KisAnimationCachePopulator : public QObject
{
Q_OBJECT
public:
KisAnimationCachePopulator(KisPart *part);
~KisAnimationCachePopulator();
/**
* Request generation of given frame. The request will
* be ignored if the populator is already requesting a frame.
* @return true if generation reqeusted, false if busy
*/
bool regenerate(KisAnimationFrameCacheSP cache, int frame);
-Q_SIGNALS:
- void sigPrivateStartWaitingForConvertedFrame();
-
public Q_SLOTS:
- void slotStart();
-
void slotRequestRegeneration();
private Q_SLOTS:
void slotTimer();
- void slotFrameReady(int frame);
- void slotFrameCancelled();
- void slotInfoConverted();
- void slotPrivateStartWaitingForConvertedFrame();
+ void slotRegeneratorFrameCancelled();
+ void slotRegeneratorFrameReady();
+
+ void slotConfigChanged();
private:
struct Private;
QScopedPointer<Private> m_d;
};
#endif
diff --git a/libs/ui/kis_aspect_ratio_locker.cpp b/libs/ui/kis_aspect_ratio_locker.cpp
index 211cbf45d4..7c3a35bc7f 100644
--- a/libs/ui/kis_aspect_ratio_locker.cpp
+++ b/libs/ui/kis_aspect_ratio_locker.cpp
@@ -1,200 +1,208 @@
/*
* 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_aspect_ratio_locker.h"
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <KoAspectButton.h>
#include "kis_signals_blocker.h"
#include "kis_assert.h"
#include "kis_debug.h"
#include "kis_slider_spin_box.h"
#include "kis_int_parse_spin_box.h"
#include "kis_double_parse_spin_box.h"
+#include "kis_double_parse_unit_spin_box.h"
struct SliderWrapper
{
template <class Slider>
SliderWrapper(Slider *slider)
: m_slider(QVariant::fromValue(slider)),
m_object(slider) {}
void setValue(qreal value) {
if (m_slider.canConvert<QSpinBox*>()) {
m_slider.value<QSpinBox*>()->setValue(qRound(value));
} else if (m_slider.canConvert<QDoubleSpinBox*>()) {
m_slider.value<QDoubleSpinBox*>()->setValue(value);
} else if (m_slider.canConvert<KisSliderSpinBox*>()) {
m_slider.value<KisSliderSpinBox*>()->setValue(qRound(value));
} else if (m_slider.canConvert<KisDoubleSliderSpinBox*>()) {
m_slider.value<KisDoubleSliderSpinBox*>()->setValue(value);
} else if (m_slider.canConvert<KisIntParseSpinBox*>()) {
m_slider.value<KisIntParseSpinBox*>()->setValue(qRound(value));
} else if (m_slider.canConvert<KisDoubleParseSpinBox*>()) {
m_slider.value<KisDoubleParseSpinBox*>()->setValue(value);
}
}
qreal value() const {
qreal result = 0.0;
if (m_slider.canConvert<QSpinBox*>()) {
result = m_slider.value<QSpinBox*>()->value();
} else if (m_slider.canConvert<QDoubleSpinBox*>()) {
result = m_slider.value<QDoubleSpinBox*>()->value();
} else if (m_slider.canConvert<KisSliderSpinBox*>()) {
result = m_slider.value<KisSliderSpinBox*>()->value();
} else if (m_slider.canConvert<KisDoubleSliderSpinBox*>()) {
result = m_slider.value<KisDoubleSliderSpinBox*>()->value();
} else if (m_slider.canConvert<KisIntParseSpinBox*>()) {
result = m_slider.value<KisIntParseSpinBox*>()->value();
} else if (m_slider.canConvert<KisDoubleParseSpinBox*>()) {
result = m_slider.value<KisDoubleParseSpinBox*>()->value();
}
return result;
}
bool isDragging() const {
bool result = false;
if (m_slider.canConvert<KisSliderSpinBox*>()) {
result = m_slider.value<KisSliderSpinBox*>()->isDragging();
} else if (m_slider.canConvert<KisDoubleSliderSpinBox*>()) {
result = m_slider.value<KisDoubleSliderSpinBox*>()->isDragging();
}
return result;
}
QObject* object() const {
return m_object;
}
private:
QVariant m_slider;
QObject *m_object;
};
struct KisAspectRatioLocker::Private
{
QScopedPointer<SliderWrapper> spinOne;
QScopedPointer<SliderWrapper> spinTwo;
KoAspectButton *aspectButton = 0;
qreal aspectRatio = 1.0;
bool blockUpdatesOnDrag = false;
};
KisAspectRatioLocker::KisAspectRatioLocker(QObject *parent)
: QObject(parent),
m_d(new Private)
{
}
KisAspectRatioLocker::~KisAspectRatioLocker()
{
}
template <class SpinBoxType>
void KisAspectRatioLocker::connectSpinBoxes(SpinBoxType *spinOne, SpinBoxType *spinTwo, KoAspectButton *aspectButton)
{
m_d->spinOne.reset(new SliderWrapper(spinOne));
m_d->spinTwo.reset(new SliderWrapper(spinTwo));
m_d->aspectButton = aspectButton;
if (QVariant::fromValue(spinOne->value()).type() == QVariant::Double) {
connect(spinOne, SIGNAL(valueChanged(qreal)), SLOT(slotSpinOneChanged()));
connect(spinTwo, SIGNAL(valueChanged(qreal)), SLOT(slotSpinTwoChanged()));
} else {
connect(spinOne, SIGNAL(valueChanged(int)), SLOT(slotSpinOneChanged()));
connect(spinTwo, SIGNAL(valueChanged(int)), SLOT(slotSpinTwoChanged()));
}
connect(m_d->aspectButton, SIGNAL(keepAspectRatioChanged(bool)), SLOT(slotAspectButtonChanged()));
slotAspectButtonChanged();
}
template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(QSpinBox *spinOne, QSpinBox *spinTwo, KoAspectButton *aspectButton);
template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(QDoubleSpinBox *spinOne, QDoubleSpinBox *spinTwo, KoAspectButton *aspectButton);
template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(KisSliderSpinBox *spinOne, KisSliderSpinBox *spinTwo, KoAspectButton *aspectButton);
template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(KisDoubleSliderSpinBox *spinOne, KisDoubleSliderSpinBox *spinTwo, KoAspectButton *aspectButton);
template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(KisIntParseSpinBox *spinOne, KisIntParseSpinBox *spinTwo, KoAspectButton *aspectButton);
template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(KisDoubleParseSpinBox *spinOne, KisDoubleParseSpinBox *spinTwo, KoAspectButton *aspectButton);
+template KRITAUI_EXPORT void KisAspectRatioLocker::connectSpinBoxes(KisDoubleParseUnitSpinBox *spinOne, KisDoubleParseUnitSpinBox *spinTwo, KoAspectButton *aspectButton);
void KisAspectRatioLocker::slotSpinOneChanged()
{
if (m_d->aspectButton->keepAspectRatio()) {
KisSignalsBlocker b(m_d->spinTwo->object());
m_d->spinTwo->setValue(m_d->aspectRatio * m_d->spinOne->value());
}
if (!m_d->blockUpdatesOnDrag || !m_d->spinOne->isDragging()) {
emit sliderValueChanged();
}
}
void KisAspectRatioLocker::slotSpinTwoChanged()
{
if (m_d->aspectButton->keepAspectRatio()) {
KisSignalsBlocker b(m_d->spinOne->object());
m_d->spinOne->setValue(m_d->spinTwo->value() / m_d->aspectRatio);
}
if (!m_d->blockUpdatesOnDrag || !m_d->spinTwo->isDragging()) {
emit sliderValueChanged();
}
}
void KisAspectRatioLocker::slotAspectButtonChanged()
{
if (m_d->aspectButton->keepAspectRatio() &&
m_d->spinTwo->value() > 0 &&
m_d->spinOne->value() > 0) {
m_d->aspectRatio = qreal(m_d->spinTwo->value()) / m_d->spinOne->value();
} else {
m_d->aspectRatio = 1.0;
}
if (!m_d->spinTwo->isDragging()) {
emit aspectButtonChanged();
}
}
void KisAspectRatioLocker::setBlockUpdateSignalOnDrag(bool value)
{
m_d->blockUpdatesOnDrag = value;
}
+
+void KisAspectRatioLocker::updateAspect()
+{
+ KisSignalsBlocker b(this);
+ slotAspectButtonChanged();
+}
diff --git a/libs/ui/kis_aspect_ratio_locker.h b/libs/ui/kis_aspect_ratio_locker.h
index 321b0c6904..d8793cfff9 100644
--- a/libs/ui/kis_aspect_ratio_locker.h
+++ b/libs/ui/kis_aspect_ratio_locker.h
@@ -1,58 +1,59 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_ASPECT_RATIO_LOCKER_H
#define __KIS_ASPECT_RATIO_LOCKER_H
#include <QScopedPointer>
#include <QObject>
#include "kritaui_export.h"
class QSpinBox;
class QDoubleSpinBox;
class KisSliderSpinBox;
class KisDoubleSliderSpinBox;
class KoAspectButton;
class KRITAUI_EXPORT KisAspectRatioLocker : public QObject
{
Q_OBJECT
public:
KisAspectRatioLocker(QObject *parent = 0);
~KisAspectRatioLocker();
template <class SpinBoxType>
void connectSpinBoxes(SpinBoxType *spinOne, SpinBoxType *spinTwo, KoAspectButton *aspectButton);
void setBlockUpdateSignalOnDrag(bool block);
+ void updateAspect();
private Q_SLOTS:
void slotSpinOneChanged();
void slotSpinTwoChanged();
void slotAspectButtonChanged();
Q_SIGNALS:
void sliderValueChanged();
void aspectButtonChanged();
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_ASPECT_RATIO_LOCKER_H */
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc
index 5d053bd040..b396d29e95 100644
--- a/libs/ui/kis_config.cc
+++ b/libs/ui/kis_config.cc
@@ -1,1822 +1,1832 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_config.h"
#include <limits.h>
#include <QtGlobal>
#include <QApplication>
#include <QDesktopWidget>
#include <QMutex>
#include <QFont>
#include <QThread>
#include <QStringList>
#include <QSettings>
#include <QStandardPaths>
#include <kconfig.h>
#include <KisDocument.h>
#include <KoColor.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorProfile.h>
#include <kis_debug.h>
#include <kis_types.h>
#include "kis_canvas_resource_provider.h"
#include "kis_config_notifier.h"
#include "kis_snap_config.h"
#include <config-ocio.h>
#include <kis_color_manager.h>
KisConfig::KisConfig()
: m_cfg( KSharedConfig::openConfig()->group(""))
{
}
KisConfig::~KisConfig()
{
if (qApp->thread() != QThread::currentThread()) {
//dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Skipping...";
return;
}
m_cfg.sync();
}
bool KisConfig::disableTouchOnCanvas(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false));
}
void KisConfig::setDisableTouchOnCanvas(bool value) const
{
m_cfg.writeEntry("disableTouchOnCanvas", value);
}
bool KisConfig::useProjections(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useProjections", true));
}
void KisConfig::setUseProjections(bool useProj) const
{
m_cfg.writeEntry("useProjections", useProj);
}
bool KisConfig::undoEnabled(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true));
}
void KisConfig::setUndoEnabled(bool undo) const
{
m_cfg.writeEntry("undoEnabled", undo);
}
int KisConfig::undoStackLimit(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30));
}
void KisConfig::setUndoStackLimit(int limit) const
{
m_cfg.writeEntry("undoStackLimit", limit);
}
bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false));
}
void KisConfig::setCumulativeUndoRedo(bool value)
{
m_cfg.writeEntry("useCumulativeUndoRedo", value);
}
qreal KisConfig::stackT1(bool defaultValue) const
{
return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5));
}
void KisConfig::setStackT1(int T1)
{
m_cfg.writeEntry("stackT1", T1);
}
qreal KisConfig::stackT2(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1));
}
void KisConfig::setStackT2(int T2)
{
m_cfg.writeEntry("stackT2", T2);
}
int KisConfig::stackN(bool defaultValue) const
{
return (defaultValue ? 5 : m_cfg.readEntry("stackN",5));
}
void KisConfig::setStackN(int N)
{
m_cfg.writeEntry("stackN", N);
}
qint32 KisConfig::defImageWidth(bool defaultValue) const
{
return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600));
}
qint32 KisConfig::defImageHeight(bool defaultValue) const
{
return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200));
}
qreal KisConfig::defImageResolution(bool defaultValue) const
{
return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0;
}
QString KisConfig::defColorModel(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id()
: m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id()));
}
void KisConfig::defColorModel(const QString & model) const
{
m_cfg.writeEntry("colorModelDef", model);
}
QString KisConfig::defaultColorDepth(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id()
: m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id()));
}
void KisConfig::setDefaultColorDepth(const QString & depth) const
{
m_cfg.writeEntry("colorDepthDef", depth);
}
QString KisConfig::defColorProfile(bool defaultValue) const
{
return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() :
m_cfg.readEntry("colorProfileDef",
KoColorSpaceRegistry::instance()->rgb8()->profile()->name()));
}
void KisConfig::defColorProfile(const QString & profile) const
{
m_cfg.writeEntry("colorProfileDef", profile);
}
void KisConfig::defImageWidth(qint32 width) const
{
m_cfg.writeEntry("imageWidthDef", width);
}
void KisConfig::defImageHeight(qint32 height) const
{
m_cfg.writeEntry("imageHeightDef", height);
}
void KisConfig::defImageResolution(qreal res) const
{
m_cfg.writeEntry("imageResolutionDef", res*72.0);
}
void cleanOldCursorStyleKeys(KConfigGroup &cfg)
{
if (cfg.hasKey("newCursorStyle") &&
cfg.hasKey("newOutlineStyle")) {
cfg.deleteEntry("cursorStyleDef");
}
}
CursorStyle KisConfig::newCursorStyle(bool defaultValue) const
{
if (defaultValue) {
return CURSOR_STYLE_NO_CURSOR;
}
int style = m_cfg.readEntry("newCursorStyle", int(-1));
if (style < 0) {
// old style format
style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE));
switch (style) {
case OLD_CURSOR_STYLE_TOOLICON:
style = CURSOR_STYLE_TOOLICON;
break;
case OLD_CURSOR_STYLE_CROSSHAIR:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS:
style = CURSOR_STYLE_CROSSHAIR;
break;
case OLD_CURSOR_STYLE_POINTER:
style = CURSOR_STYLE_POINTER;
break;
case OLD_CURSOR_STYLE_OUTLINE:
case OLD_CURSOR_STYLE_NO_CURSOR:
style = CURSOR_STYLE_NO_CURSOR;
break;
case OLD_CURSOR_STYLE_SMALL_ROUND:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT:
style = CURSOR_STYLE_SMALL_ROUND;
break;
case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED:
style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED;
break;
case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED:
style = CURSOR_STYLE_TRIANGLE_LEFTHANDED;
break;
default:
style = -1;
}
}
cleanOldCursorStyleKeys(m_cfg);
// compatibility with future versions
if (style < 0 || style >= N_CURSOR_STYLE_SIZE) {
style = CURSOR_STYLE_NO_CURSOR;
}
return (CursorStyle) style;
}
void KisConfig::setNewCursorStyle(CursorStyle style)
{
m_cfg.writeEntry("newCursorStyle", (int)style);
}
OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const
{
if (defaultValue) {
return OUTLINE_FULL;
}
int style = m_cfg.readEntry("newOutlineStyle", int(-1));
if (style < 0) {
// old style format
style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE));
switch (style) {
case OLD_CURSOR_STYLE_TOOLICON:
case OLD_CURSOR_STYLE_CROSSHAIR:
case OLD_CURSOR_STYLE_POINTER:
case OLD_CURSOR_STYLE_NO_CURSOR:
case OLD_CURSOR_STYLE_SMALL_ROUND:
case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED:
style = OUTLINE_NONE;
break;
case OLD_CURSOR_STYLE_OUTLINE:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT:
case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED:
case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED:
style = OUTLINE_FULL;
break;
default:
style = -1;
}
}
cleanOldCursorStyleKeys(m_cfg);
// compatibility with future versions
if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) {
style = OUTLINE_FULL;
}
return (OutlineStyle) style;
}
void KisConfig::setNewOutlineStyle(OutlineStyle style)
{
m_cfg.writeEntry("newOutlineStyle", (int)style);
}
QRect KisConfig::colorPreviewRect() const
{
return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect();
}
void KisConfig::setColorPreviewRect(const QRect &rect)
{
m_cfg.writeEntry("colorPreviewRect", QVariant(rect));
}
bool KisConfig::useDirtyPresets(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets",false));
}
void KisConfig::setUseDirtyPresets(bool value)
{
m_cfg.writeEntry("useDirtyPresets",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::useEraserBrushSize(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize",false));
}
void KisConfig::setUseEraserBrushSize(bool value)
{
m_cfg.writeEntry("useEraserBrushSize",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::useEraserBrushOpacity(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false));
}
void KisConfig::setUseEraserBrushOpacity(bool value)
{
m_cfg.writeEntry("useEraserBrushOpacity",value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
QColor KisConfig::getMDIBackgroundColor(bool defaultValue) const
{
QColor col(77, 77, 77);
return (defaultValue ? col : m_cfg.readEntry("mdiBackgroundColor", col));
}
void KisConfig::setMDIBackgroundColor(const QColor &v) const
{
m_cfg.writeEntry("mdiBackgroundColor", v);
}
QString KisConfig::getMDIBackgroundImage(bool defaultValue) const
{
return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", ""));
}
void KisConfig::setMDIBackgroundImage(const QString &filename) const
{
m_cfg.writeEntry("mdiBackgroundImage", filename);
}
QString KisConfig::monitorProfile(int screen) const
{
// Note: keep this in sync with the default profile for the RGB colorspaces!
QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc");
//dbgKrita << "KisConfig::monitorProfile()" << profile;
return profile;
}
QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const
{
return (defaultValue ? defaultMonitor
: m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor));
}
void KisConfig::setMonitorForScreen(int screen, const QString& monitor)
{
m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor);
}
void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const
{
m_cfg.writeEntry("monitorProfile/OverrideX11", override);
m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile);
}
const KoColorProfile *KisConfig::getScreenProfile(int screen)
{
if (screen < 0) return 0;
KisConfig cfg;
QString monitorId;
if (KisColorManager::instance()->devices().size() > screen) {
monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]);
}
//dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId;
if (monitorId.isEmpty()) {
return 0;
}
QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId);
//dbgKrita << "\tgetScreenProfile()" << bytes.size();
if (bytes.length() > 0) {
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes);
//dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name();
return profile;
}
else {
//dbgKrita << "\tCould not get a system monitor profile";
return 0;
}
}
const KoColorProfile *KisConfig::displayProfile(int screen) const
{
if (screen < 0) return 0;
// if the user plays with the settings, they can override the display profile, in which case
// we don't want the system setting.
bool override = useSystemMonitorProfile();
//dbgKrita << "KisConfig::displayProfile(). Override X11:" << override;
const KoColorProfile *profile = 0;
if (override) {
//dbgKrita << "\tGoing to get the screen profile";
profile = KisConfig::getScreenProfile(screen);
}
// if it fails. check the configuration
if (!profile || !profile->isSuitableForDisplay()) {
//dbgKrita << "\tGoing to get the monitor profile";
QString monitorProfileName = monitorProfile(screen);
//dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName;
if (!monitorProfileName.isEmpty()) {
profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName);
}
if (profile) {
//dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay();
}
else {
//dbgKrita << "\t\tstill no profile";
}
}
// if we still don't have a profile, or the profile isn't suitable for display,
// we need to get a last-resort profile. the built-in sRGB is a good choice then.
if (!profile || !profile->isSuitableForDisplay()) {
//dbgKrita << "\tnothing worked, going to get sRGB built-in";
profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in");
}
if (profile) {
//dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name();
}
else {
//dbgKrita << "\tCouldn't get a display profile at all";
}
return profile;
}
QString KisConfig::workingColorSpace(bool defaultValue) const
{
return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA"));
}
void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const
{
m_cfg.writeEntry("workingColorSpace", workingColorSpace);
}
QString KisConfig::printerColorSpace(bool /*defaultValue*/) const
{
//TODO currently only rgb8 is supported
//return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA"));
return QString("RGBA");
}
void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const
{
m_cfg.writeEntry("printerColorSpace", printerColorSpace);
}
QString KisConfig::printerProfile(bool defaultValue) const
{
return (defaultValue ? "" : m_cfg.readEntry("printerProfile", ""));
}
void KisConfig::setPrinterProfile(const QString & printerProfile) const
{
m_cfg.writeEntry("printerProfile", printerProfile);
}
bool KisConfig::useBlackPointCompensation(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true));
}
void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const
{
m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation);
}
bool KisConfig::allowLCMSOptimization(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true));
}
void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization)
{
m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization);
}
bool KisConfig::showRulers(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showrulers", false));
}
void KisConfig::setShowRulers(bool rulers) const
{
m_cfg.writeEntry("showrulers", rulers);
}
bool KisConfig::rulersTrackMouse(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true));
}
void KisConfig::setRulersTrackMouse(bool value) const
{
m_cfg.writeEntry("rulersTrackMouse", value);
}
qint32 KisConfig::pasteBehaviour(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2));
}
void KisConfig::setPasteBehaviour(qint32 renderIntent) const
{
m_cfg.writeEntry("pasteBehaviour", renderIntent);
}
qint32 KisConfig::monitorRenderIntent(bool defaultValue) const
{
qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL);
if (intent > 3) intent = 3;
if (intent < 0) intent = 0;
return (defaultValue ? INTENT_PERCEPTUAL : intent);
}
void KisConfig::setRenderIntent(qint32 renderIntent) const
{
if (renderIntent > 3) renderIntent = 3;
if (renderIntent < 0) renderIntent = 0;
m_cfg.writeEntry("renderIntent", renderIntent);
}
bool KisConfig::useOpenGL(bool defaultValue) const
{
if (defaultValue) {
return true;
}
//dbgKrita << "use opengl" << m_cfg.readEntry("useOpenGL", true) << "success" << m_cfg.readEntry("canvasState", "OPENGL_SUCCESS");
QString cs = canvasState();
return (m_cfg.readEntry("useOpenGL", true) && (cs == "OPENGL_SUCCESS" || cs == "TRY_OPENGL"));
}
void KisConfig::setUseOpenGL(bool useOpenGL) const
{
m_cfg.writeEntry("useOpenGL", useOpenGL);
}
int KisConfig::openGLFilteringMode(bool defaultValue) const
{
return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3));
}
void KisConfig::setOpenGLFilteringMode(int filteringMode)
{
m_cfg.writeEntry("OpenGLFilterMode", filteringMode);
}
bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true));
}
void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer)
{
m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer);
}
int KisConfig::openGLTextureSize(bool defaultValue) const
{
return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256));
}
bool KisConfig::disableVSync(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("disableVSync", true));
}
void KisConfig::setDisableVSync(bool disableVSync)
{
m_cfg.writeEntry("disableVSync", disableVSync);
}
bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false));
}
bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false));
}
int KisConfig::numMipmapLevels(bool defaultValue) const
{
return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4));
}
int KisConfig::textureOverlapBorder() const
{
return 1 << qMax(0, numMipmapLevels());
}
qint32 KisConfig::maxNumberOfThreads(bool defaultValue) const
{
return (defaultValue ? QThread::idealThreadCount() : m_cfg.readEntry("maxthreads", QThread::idealThreadCount()));
}
void KisConfig::setMaxNumberOfThreads(qint32 maxThreads)
{
m_cfg.writeEntry("maxthreads", maxThreads);
}
quint32 KisConfig::getGridMainStyle(bool defaultValue) const
{
int v = m_cfg.readEntry("gridmainstyle", 0);
v = qBound(0, v, 2);
return (defaultValue ? 0 : v);
}
void KisConfig::setGridMainStyle(quint32 v) const
{
m_cfg.writeEntry("gridmainstyle", v);
}
quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const
{
quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1);
if (v > 2) v = 2;
return (defaultValue ? 1 : v);
}
void KisConfig::setGridSubdivisionStyle(quint32 v) const
{
m_cfg.writeEntry("gridsubdivisionstyle", v);
}
QColor KisConfig::getGridMainColor(bool defaultValue) const
{
QColor col(99, 99, 99);
return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col));
}
void KisConfig::setGridMainColor(const QColor & v) const
{
m_cfg.writeEntry("gridmaincolor", v);
}
QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const
{
QColor col(150, 150, 150);
return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col));
}
void KisConfig::setGridSubdivisionColor(const QColor & v) const
{
m_cfg.writeEntry("gridsubdivisioncolor", v);
}
quint32 KisConfig::guidesLineStyle(bool defaultValue) const
{
int v = m_cfg.readEntry("guidesLineStyle", 0);
v = qBound(0, v, 2);
return (defaultValue ? 0 : v);
}
void KisConfig::setGuidesLineStyle(quint32 v) const
{
m_cfg.writeEntry("guidesLineStyle", v);
}
QColor KisConfig::guidesColor(bool defaultValue) const
{
QColor col(99, 99, 99);
return (defaultValue ? col : m_cfg.readEntry("guidesColor", col));
}
void KisConfig::setGuidesColor(const QColor & v) const
{
m_cfg.writeEntry("guidesColor", v);
}
void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const
{
KisSnapConfig defaultConfig(false);
if (defaultValue) {
*config = defaultConfig;
return;
}
config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal()));
config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node()));
config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension()));
config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection()));
config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox()));
config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds()));
config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter()));
}
void KisConfig::saveSnapConfig(const KisSnapConfig &config)
{
m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal());
m_cfg.writeEntry("globalSnapNode", config.node());
m_cfg.writeEntry("globalSnapExtension", config.extension());
m_cfg.writeEntry("globalSnapIntersection", config.intersection());
m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox());
m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds());
m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter());
}
qint32 KisConfig::checkSize(bool defaultValue) const
{
return (defaultValue ? 32 : m_cfg.readEntry("checksize", 32));
}
void KisConfig::setCheckSize(qint32 checksize) const
{
m_cfg.writeEntry("checksize", checksize);
}
bool KisConfig::scrollCheckers(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false));
}
void KisConfig::setScrollingCheckers(bool sc) const
{
m_cfg.writeEntry("scrollingcheckers", sc);
}
QColor KisConfig::canvasBorderColor(bool defaultValue) const
{
QColor color(QColor(128,128,128));
return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color));
}
void KisConfig::setCanvasBorderColor(const QColor& color) const
{
m_cfg.writeEntry("canvasBorderColor", color);
}
bool KisConfig::hideScrollbars(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false));
}
void KisConfig::setHideScrollbars(bool value) const
{
m_cfg.writeEntry("hideScrollbars", value);
}
QColor KisConfig::checkersColor1(bool defaultValue) const
{
QColor col(220, 220, 220);
return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col));
}
void KisConfig::setCheckersColor1(const QColor & v) const
{
m_cfg.writeEntry("checkerscolor", v);
}
QColor KisConfig::checkersColor2(bool defaultValue) const
{
return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white)));
}
void KisConfig::setCheckersColor2(const QColor & v) const
{
m_cfg.writeEntry("checkerscolor2", v);
}
bool KisConfig::antialiasCurves(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true));
}
void KisConfig::setAntialiasCurves(bool v) const
{
m_cfg.writeEntry("antialiascurves", v);
}
QColor KisConfig::selectionOverlayMaskColor(bool defaultValue) const
{
QColor def(255, 0, 0, 220);
return (defaultValue ? def : m_cfg.readEntry("selectionOverlayMaskColor", def));
}
void KisConfig::setSelectionOverlayMaskColor(const QColor &color)
{
m_cfg.writeEntry("selectionOverlayMaskColor", color);
}
bool KisConfig::antialiasSelectionOutline(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false));
}
void KisConfig::setAntialiasSelectionOutline(bool v) const
{
m_cfg.writeEntry("AntialiasSelectionOutline", v);
}
bool KisConfig::showRootLayer(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false));
}
void KisConfig::setShowRootLayer(bool showRootLayer) const
{
m_cfg.writeEntry("ShowRootLayer", showRootLayer);
}
bool KisConfig::showGlobalSelection(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false));
}
void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const
{
m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection);
}
bool KisConfig::showOutlineWhilePainting(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true));
}
void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const
{
m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting);
}
bool KisConfig::hideSplashScreen(bool defaultValue) const
{
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
return (defaultValue ? true : cfg.readEntry("HideSplashAfterStartup", true));
}
void KisConfig::setHideSplashScreen(bool hideSplashScreen) const
{
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
cfg.writeEntry("HideSplashAfterStartup", hideSplashScreen);
}
qreal KisConfig::outlineSizeMinimum(bool defaultValue) const
{
return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0));
}
void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const
{
m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum);
}
qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const
{
return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0));
}
void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const
{
m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum);
}
int KisConfig::autoSaveInterval(bool defaultValue) const
{
return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60));
}
void KisConfig::setAutoSaveInterval(int seconds) const
{
return m_cfg.writeEntry("AutoSaveInterval", seconds);
}
bool KisConfig::backupFile(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true));
}
void KisConfig::setBackupFile(bool backupFile) const
{
m_cfg.writeEntry("CreateBackupFile", backupFile);
}
bool KisConfig::showFilterGallery(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false));
}
void KisConfig::setShowFilterGallery(bool showFilterGallery) const
{
m_cfg.writeEntry("showFilterGallery", showFilterGallery);
}
bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true));
}
void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const
{
m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery);
}
QString KisConfig::canvasState(bool defaultValue) const
{
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString());
}
void KisConfig::setCanvasState(const QString& state) const
{
static QStringList acceptableStates;
if (acceptableStates.isEmpty()) {
acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED";
}
if (acceptableStates.contains(state)) {
const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
kritarc.setValue("canvasState", state);
}
}
bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false));
}
void KisConfig::setToolOptionsPopupDetached(bool detached) const
{
m_cfg.writeEntry("ToolOptionsPopupDetached", detached);
}
bool KisConfig::paintopPopupDetached(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false));
}
void KisConfig::setPaintopPopupDetached(bool detached) const
{
m_cfg.writeEntry("PaintopPopupDetached", detached);
}
QString KisConfig::pressureTabletCurve(bool defaultValue) const
{
return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;"));
}
void KisConfig::setPressureTabletCurve(const QString& curveString) const
{
m_cfg.writeEntry("tabletPressureCurve", curveString);
}
qreal KisConfig::vastScrolling(bool defaultValue) const
{
return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9));
}
void KisConfig::setVastScrolling(const qreal factor) const
{
m_cfg.writeEntry("vastScrolling", factor);
}
int KisConfig::presetChooserViewMode(bool defaultValue) const
{
return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0));
}
void KisConfig::setPresetChooserViewMode(const int mode) const
{
m_cfg.writeEntry("presetChooserViewMode", mode);
}
int KisConfig::presetIconSize(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("presetIconSize", 30));
}
void KisConfig::setPresetIconSize(const int value) const
{
m_cfg.writeEntry("presetIconSize", value);
}
bool KisConfig::firstRun(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("firstRun", true));
}
void KisConfig::setFirstRun(const bool first) const
{
m_cfg.writeEntry("firstRun", first);
}
int KisConfig::horizontalSplitLines(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1));
}
void KisConfig::setHorizontalSplitLines(const int numberLines) const
{
m_cfg.writeEntry("horizontalSplitLines", numberLines);
}
int KisConfig::verticalSplitLines(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1));
}
void KisConfig::setVerticalSplitLines(const int numberLines) const
{
m_cfg.writeEntry("verticalSplitLines", numberLines);
}
bool KisConfig::clicklessSpacePan(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true));
}
void KisConfig::setClicklessSpacePan(const bool toggle) const
{
m_cfg.writeEntry("clicklessSpacePan", toggle);
}
bool KisConfig::hideDockersFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true));
}
void KisConfig::setHideDockersFullscreen(const bool value) const
{
m_cfg.writeEntry("hideDockersFullScreen", value);
}
bool KisConfig::showDockerTitleBars(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showDockerTitleBars", true));
}
void KisConfig::setShowDockerTitleBars(const bool value) const
{
m_cfg.writeEntry("showDockerTitleBars", value);
}
bool KisConfig::showDockers(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showDockers", true));
}
void KisConfig::setShowDockers(const bool value) const
{
m_cfg.writeEntry("showDockers", value);
}
bool KisConfig::showStatusBar(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true));
}
void KisConfig::setShowStatusBar(const bool value) const
{
m_cfg.writeEntry("showStatusBar", value);
}
bool KisConfig::hideMenuFullscreen(bool defaultValue) const
{
return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true));
}
void KisConfig::setHideMenuFullscreen(const bool value) const
{
m_cfg.writeEntry("hideMenuFullScreen", value);
}
bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true));
}
void KisConfig::setHideScrollbarsFullscreen(const bool value) const
{
m_cfg.writeEntry("hideScrollbarsFullScreen", value);
}
bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const
{
return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true));
}
void KisConfig::setHideStatusbarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideStatusbarFullScreen", value);
}
bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true));
}
void KisConfig::setHideTitlebarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideTitleBarFullscreen", value);
}
bool KisConfig::hideToolbarFullscreen(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true));
}
void KisConfig::setHideToolbarFullscreen(const bool value) const
{
m_cfg.writeEntry("hideToolbarFullscreen", value);
}
bool KisConfig::fullscreenMode(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true));
}
void KisConfig::setFullscreenMode(const bool value) const
{
m_cfg.writeEntry("fullscreenMode", value);
}
QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const
{
return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QStringList()));
}
void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const
{
m_cfg.writeEntry("favoriteCompositeOps", compositeOps);
}
QString KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString()));
}
void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const
{
QString exportConfig = properties->toXML();
m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig);
}
bool KisConfig::useOcio(bool defaultValue) const
{
#ifdef HAVE_OCIO
return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false));
#else
Q_UNUSED(defaultValue);
return false;
#endif
}
void KisConfig::setUseOcio(bool useOCIO) const
{
m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO);
}
int KisConfig::favoritePresets(bool defaultValue) const
{
return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10));
}
void KisConfig::setFavoritePresets(const int value)
{
m_cfg.writeEntry("numFavoritePresets", value);
}
bool KisConfig::levelOfDetailEnabled(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false));
}
void KisConfig::setLevelOfDetailEnabled(bool value)
{
m_cfg.writeEntry("levelOfDetailEnabled", value);
}
KisConfig::OcioColorManagementMode
KisConfig::ocioColorManagementMode(bool defaultValue) const
{
return (OcioColorManagementMode)(defaultValue ? INTERNAL
: m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL));
}
void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const
{
m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode);
}
QString KisConfig::ocioConfigurationPath(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()));
}
void KisConfig::setOcioConfigurationPath(const QString &path) const
{
m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", path);
}
QString KisConfig::ocioLutPath(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()));
}
void KisConfig::setOcioLutPath(const QString &path) const
{
m_cfg.writeEntry("Krita/Ocio/OcioLutPath", path);
}
int KisConfig::ocioLutEdgeSize(bool defaultValue) const
{
return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64));
}
void KisConfig::setOcioLutEdgeSize(int value)
{
m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value);
}
bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false));
}
void KisConfig::setOcioLockColorVisualRepresentation(bool value)
{
m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value);
}
QString KisConfig::defaultPalette(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", QString()));
}
void KisConfig::setDefaultPalette(const QString& name) const
{
m_cfg.writeEntry("defaultPalette", name);
}
QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const
{
QString def = "flow";
if (sliderNumber == 1) {
def = "opacity";
}
if (sliderNumber == 2) {
def = "size";
}
return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def));
}
void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider)
{
m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider);
}
bool KisConfig::sliderLabels(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true));
}
void KisConfig::setSliderLabels(bool enabled)
{
m_cfg.writeEntry("sliderLabels", enabled);
}
QString KisConfig::currentInputProfile(bool defaultValue) const
{
return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString()));
}
void KisConfig::setCurrentInputProfile(const QString& name)
{
m_cfg.writeEntry("currentInputProfile", name);
}
bool KisConfig::useSystemMonitorProfile(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false));
}
void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const
{
m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile);
}
bool KisConfig::presetStripVisible(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true));
}
void KisConfig::setPresetStripVisible(bool visible)
{
m_cfg.writeEntry("presetStripVisible", visible);
}
bool KisConfig::scratchpadVisible(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true));
}
void KisConfig::setScratchpadVisible(bool visible)
{
m_cfg.writeEntry("scratchpadVisible", visible);
}
bool KisConfig::showSingleChannelAsColor(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false));
}
void KisConfig::setShowSingleChannelAsColor(bool asColor)
{
m_cfg.writeEntry("showSingleChannelAsColor", asColor);
}
bool KisConfig::hidePopups(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("hidePopups", false));
}
void KisConfig::setHidePopups(bool hidepopups)
{
m_cfg.writeEntry("hidePopups", hidepopups);
}
int KisConfig::numDefaultLayers(bool defaultValue) const
{
return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2));
}
void KisConfig::setNumDefaultLayers(int num)
{
m_cfg.writeEntry("NumberOfLayersForNewImage", num);
}
quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const
{
return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8));
}
void KisConfig::setDefaultBackgroundOpacity(quint8 value)
{
m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value);
}
QColor KisConfig::defaultBackgroundColor(bool defaultValue) const
{
return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white)));
}
void KisConfig::setDefaultBackgroundColor(QColor value)
{
m_cfg.writeEntry("BackgroundColorForNewImage", value);
}
KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const
{
return (KisConfig::BackgroundStyle)(defaultValue ? LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)LAYER));
}
void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value)
{
m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value);
}
int KisConfig::lineSmoothingType(bool defaultValue) const
{
return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1));
}
void KisConfig::setLineSmoothingType(int value)
{
m_cfg.writeEntry("LineSmoothingType", value);
}
qreal KisConfig::lineSmoothingDistance(bool defaultValue) const
{
return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0));
}
void KisConfig::setLineSmoothingDistance(qreal value)
{
m_cfg.writeEntry("LineSmoothingDistance", value);
}
qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const
{
return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15));
}
void KisConfig::setLineSmoothingTailAggressiveness(qreal value)
{
m_cfg.writeEntry("LineSmoothingTailAggressiveness", value);
}
bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false));
}
void KisConfig::setLineSmoothingSmoothPressure(bool value)
{
m_cfg.writeEntry("LineSmoothingSmoothPressure", value);
}
bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true));
}
void KisConfig::setLineSmoothingScalableDistance(bool value)
{
m_cfg.writeEntry("LineSmoothingScalableDistance", value);
}
qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const
{
return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0));
}
void KisConfig::setLineSmoothingDelayDistance(qreal value)
{
m_cfg.writeEntry("LineSmoothingDelayDistance", value);
}
bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true));
}
void KisConfig::setLineSmoothingUseDelayDistance(bool value)
{
m_cfg.writeEntry("LineSmoothingUseDelayDistance", value);
}
bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true));
}
void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value)
{
m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value);
}
bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true));
}
void KisConfig::setLineSmoothingStabilizeSensors(bool value)
{
m_cfg.writeEntry("LineSmoothingStabilizeSensors", value);
}
int KisConfig::paletteDockerPaletteViewSectionSize(bool defaultValue) const
{
return (defaultValue ? 12 : m_cfg.readEntry("paletteDockerPaletteViewSectionSize", 12));
}
void KisConfig::setPaletteDockerPaletteViewSectionSize(int value) const
{
m_cfg.writeEntry("paletteDockerPaletteViewSectionSize", value);
}
int KisConfig::tabletEventsDelay(bool defaultValue) const
{
return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10));
}
void KisConfig::setTabletEventsDelay(int value)
{
m_cfg.writeEntry("tabletEventsDelay", value);
}
bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false));
}
void KisConfig::setTestingAcceptCompressedTabletEvents(bool value)
{
m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value);
}
bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false));
}
bool KisConfig::testingCompressBrushEvents(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false));
}
void KisConfig::setTestingCompressBrushEvents(bool value)
{
m_cfg.writeEntry("testingCompressBrushEvents", value);
}
bool KisConfig::useVerboseOpenGLDebugOutput(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("useVerboseOpenGLDebugOutput", false));
}
int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const
{
return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0));
}
bool KisConfig::showCanvasMessages(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true));
}
void KisConfig::setShowCanvasMessages(bool show)
{
m_cfg.writeEntry("showOnCanvasMessages", show);
}
bool KisConfig::compressKra(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false));
}
void KisConfig::setCompressKra(bool compress)
{
m_cfg.writeEntry("compressLayersInKra", compress);
}
bool KisConfig::toolOptionsInDocker(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true));
}
void KisConfig::setToolOptionsInDocker(bool inDocker)
{
m_cfg.writeEntry("ToolOptionsInDocker", inDocker);
}
const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const
{
const KoColorSpace *cs = 0;
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) {
KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance();
QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA");
QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8");
QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)");
if (profile == "default") {
// qDebug() << "Falling back to default color profile.";
profile = "sRGB built-in - (lcms internal)";
}
cs = csr->colorSpace(modelID, depthID, profile);
}
return cs;
}
void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs)
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector");
cfg.writeEntry("useCustomColorSpace", bool(cs));
if(cs) {
cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id());
cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id());
cfg.writeEntry("customColorSpaceProfile", cs->profile()->name());
}
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::enableOpenGLDebugging(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("enableOpenGLDebugging", false));
}
void KisConfig::setEnableOpenGLDebugging(bool value) const
{
m_cfg.writeEntry("enableOpenGLDebugging", value);
}
void KisConfig::setEnableAmdVectorizationWorkaround(bool value)
{
m_cfg.writeEntry("amdDisableVectorWorkaround", value);
}
bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const
{
return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false));
}
void KisConfig::setAnimationDropFrames(bool value)
{
bool oldValue = animationDropFrames();
if (value == oldValue) return;
m_cfg.writeEntry("animationDropFrames", value);
KisConfigNotifier::instance()->notifyDropFramesModeChanged();
}
bool KisConfig::animationDropFrames(bool defaultValue) const
{
return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true));
}
int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const
{
return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30));
}
void KisConfig::setScrubbingUpdatesDelay(int value)
{
m_cfg.writeEntry("scrubbingUpdatesDelay", value);
}
int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const
{
return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1));
}
void KisConfig::setScrubbingAudioUpdatesDelay(int value)
{
m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value);
}
int KisConfig::audioOffsetTolerance(bool defaultValue) const
{
return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1));
}
void KisConfig::setAudioOffsetTolerance(int value)
{
m_cfg.writeEntry("audioOffsetTolerance", value);
}
bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false);
}
void KisConfig::setSwitchSelectionCtrlAlt(bool value)
{
m_cfg.writeEntry("switchSelectionCtrlAlt", value);
KisConfigNotifier::instance()->notifyConfigChanged();
}
bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false);
}
void KisConfig::setConvertToImageColorspaceOnImport(bool value)
{
m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value);
}
int KisConfig::stabilizerSampleSize(bool defaultValue) const
{
#ifdef Q_OS_WIN
const int defaultSampleSize = 50;
#else
const int defaultSampleSize = 15;
#endif
return defaultValue ?
defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize);
}
void KisConfig::setStabilizerSampleSize(int value)
{
m_cfg.writeEntry("stabilizerSampleSize", value);
}
int KisConfig::stabilizerDelayedPaintInterval(bool defaultValue) const
{
const int defaultInterval = 20;
return defaultValue ?
defaultInterval : m_cfg.readEntry("stabilizerDelayedPaintInterval", defaultInterval);
}
void KisConfig::setStabilizerDelayedPaintInterval(int value)
{
m_cfg.writeEntry("stabilizerDelayedPaintInterval", value);
}
QString KisConfig::customFFMpegPath(bool defaultValue) const
{
return defaultValue ? QString() : m_cfg.readEntry("ffmpegExecutablePath", QString());
}
void KisConfig::setCustomFFMpegPath(const QString &value) const
{
m_cfg.writeEntry("ffmpegExecutablePath", value);
}
bool KisConfig::showBrushHud(bool defaultValue) const
{
return defaultValue ? false : m_cfg.readEntry("showBrushHud", false);
}
void KisConfig::setShowBrushHud(bool value)
{
m_cfg.writeEntry("showBrushHud", value);
}
QString KisConfig::brushHudSetting(bool defaultValue) const
{
QString defaultDoc = "<!DOCTYPE hud_properties>\n<hud_properties>\n <version value=\"1\" type=\"value\"/>\n <paintbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"angle\" type=\"value\"/>\n </properties_list>\n </paintbrush>\n <colorsmudge>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"smudge_mode\" type=\"value\"/>\n <item_3 value=\"smudge_length\" type=\"value\"/>\n <item_4 value=\"smudge_color_rate\" type=\"value\"/>\n </properties_list>\n </colorsmudge>\n <sketchbrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"size\" type=\"value\"/>\n </properties_list>\n </sketchbrush>\n <hairybrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </hairybrush>\n <experimentbrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"shape_windingfill\" type=\"value\"/>\n </properties_list>\n </experimentbrush>\n <spraybrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"spray_particlecount\" type=\"value\"/>\n <item_3 value=\"spray_density\" type=\"value\"/>\n </properties_list>\n </spraybrush>\n <hatchingbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"hatching_angle\" type=\"value\"/>\n <item_3 value=\"hatching_thickness\" type=\"value\"/>\n <item_4 value=\"hatching_separation\" type=\"value\"/>\n </properties_list>\n </hatchingbrush>\n <gridbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"grid_divisionlevel\" type=\"value\"/>\n </properties_list>\n </gridbrush>\n <curvebrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"curve_historysize\" type=\"value\"/>\n <item_2 value=\"curve_linewidth\" type=\"value\"/>\n <item_3 value=\"curve_lineopacity\" type=\"value\"/>\n <item_4 value=\"curve_connectionline\" type=\"value\"/>\n </properties_list>\n </curvebrush>\n <dynabrush>\n <properties_list type=\"array\">\n <item_0 value=\"dyna_diameter\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"dyna_mass\" type=\"value\"/>\n <item_3 value=\"dyna_drag\" type=\"value\"/>\n </properties_list>\n </dynabrush>\n <particlebrush>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"particle_particles\" type=\"value\"/>\n <item_2 value=\"particle_opecityweight\" type=\"value\"/>\n <item_3 value=\"particle_iterations\" type=\"value\"/>\n </properties_list>\n </particlebrush>\n <duplicate>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"clone_healing\" type=\"value\"/>\n <item_3 value=\"clone_movesource\" type=\"value\"/>\n </properties_list>\n </duplicate>\n <deformbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n <item_2 value=\"deform_amount\" type=\"value\"/>\n <item_3 value=\"deform_mode\" type=\"value\"/>\n </properties_list>\n </deformbrush>\n <tangentnormal>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </tangentnormal>\n <filter>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </filter>\n <chalkbrush>\n <properties_list type=\"array\">\n <item_0 value=\"size\" type=\"value\"/>\n <item_1 value=\"opacity\" type=\"value\"/>\n </properties_list>\n </chalkbrush>\n <roundmarker>\n <properties_list type=\"array\">\n <item_0 value=\"opacity\" type=\"value\"/>\n <item_1 value=\"size\" type=\"value\"/>\n </properties_list>\n </roundmarker>\n</hud_properties>\n";
return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc);
}
void KisConfig::setBrushHudSetting(const QString &value) const
{
m_cfg.writeEntry("brushHudSettings", value);
}
+bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const
+{
+ return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true);
+}
+
+void KisConfig::setCalculateAnimationCacheInBackground(bool value)
+{
+ m_cfg.writeEntry("calculateAnimationCacheInBackground", value);
+}
+
#include <QDomDocument>
#include <QDomElement>
void KisConfig::writeKoColor(const QString& name, const KoColor& color) const
{
QDomDocument doc = QDomDocument(name);
QDomElement el = doc.createElement(name);
doc.appendChild(el);
color.toXML(doc, el);
m_cfg.writeEntry(name, doc.toString());
}
//ported from kispropertiesconfig.
KoColor KisConfig::readKoColor(const QString& name, const KoColor& color) const
{
QDomDocument doc;
if (!m_cfg.readEntry(name).isNull()) {
doc.setContent(m_cfg.readEntry(name));
QDomElement e = doc.documentElement().firstChild().toElement();
return KoColor::fromXML(e, Integer16BitsColorDepthID.id());
} else {
QString blackColor = "<!DOCTYPE Color>\n<Color>\n <RGB r=\"0\" space=\"sRGB-elle-V2-srgbtrc.icc\" b=\"0\" g=\"0\"/>\n</Color>\n";
doc.setContent(blackColor);
QDomElement e = doc.documentElement().firstChild().toElement();
return KoColor::fromXML(e, Integer16BitsColorDepthID.id());
}
return color;
}
diff --git a/libs/ui/kis_config.h b/libs/ui/kis_config.h
index f8dffd6221..b0a561d7ae 100644
--- a/libs/ui/kis_config.h
+++ b/libs/ui/kis_config.h
@@ -1,546 +1,549 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_CONFIG_H_
#define KIS_CONFIG_H_
#include <QString>
#include <QStringList>
#include <QList>
#include <QColor>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include "kis_global.h"
#include "kis_properties_configuration.h"
#include "kritaui_export.h"
class KoColorProfile;
class KoColorSpace;
class KisSnapConfig;
class KRITAUI_EXPORT KisConfig
{
public:
KisConfig();
~KisConfig();
bool disableTouchOnCanvas(bool defaultValue = false) const;
void setDisableTouchOnCanvas(bool value) const;
bool useProjections(bool defaultValue = false) const;
void setUseProjections(bool useProj) const;
bool undoEnabled(bool defaultValue = false) const;
void setUndoEnabled(bool undo) const;
int undoStackLimit(bool defaultValue = false) const;
void setUndoStackLimit(int limit) const;
bool useCumulativeUndoRedo(bool defaultValue = false) const;
void setCumulativeUndoRedo(bool value);
double stackT1(bool defaultValue = false) const;
void setStackT1(int T1);
double stackT2(bool defaultValue = false) const;
void setStackT2(int T2);
int stackN(bool defaultValue = false) const;
void setStackN(int N);
qint32 defImageWidth(bool defaultValue = false) const;
void defImageWidth(qint32 width) const;
qint32 defImageHeight(bool defaultValue = false) const;
void defImageHeight(qint32 height) const;
qreal defImageResolution(bool defaultValue = false) const;
void defImageResolution(qreal res) const;
/**
* @return the id of the default color model used for creating new images.
*/
QString defColorModel(bool defaultValue = false) const;
/**
* set the id of the default color model used for creating new images.
*/
void defColorModel(const QString & model) const;
/**
* @return the id of the default color depth used for creating new images.
*/
QString defaultColorDepth(bool defaultValue = false) const;
/**
* set the id of the default color depth used for creating new images.
*/
void setDefaultColorDepth(const QString & depth) const;
/**
* @return the id of the default color profile used for creating new images.
*/
QString defColorProfile(bool defaultValue = false) const;
/**
* set the id of the default color profile used for creating new images.
*/
void defColorProfile(const QString & depth) const;
CursorStyle newCursorStyle(bool defaultValue = false) const;
void setNewCursorStyle(CursorStyle style);
OutlineStyle newOutlineStyle(bool defaultValue = false) const;
void setNewOutlineStyle(OutlineStyle style);
QRect colorPreviewRect() const;
void setColorPreviewRect(const QRect &rect);
/// get the profile the user has selected for the given screen
QString monitorProfile(int screen) const;
void setMonitorProfile(int screen, const QString & monitorProfile, bool override) const;
QString monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue = true) const;
void setMonitorForScreen(int screen, const QString& monitor);
/// Get the actual profile to be used for the given screen, which is
/// either the screen profile set by the color management system or
/// the custom monitor profile set by the user, depending on the configuration
const KoColorProfile *displayProfile(int screen) const;
QString workingColorSpace(bool defaultValue = false) const;
void setWorkingColorSpace(const QString & workingColorSpace) const;
QString importProfile(bool defaultValue = false) const;
void setImportProfile(const QString & importProfile) const;
QString printerColorSpace(bool defaultValue = false) const;
void setPrinterColorSpace(const QString & printerColorSpace) const;
QString printerProfile(bool defaultValue = false) const;
void setPrinterProfile(const QString & printerProfile) const;
bool useBlackPointCompensation(bool defaultValue = false) const;
void setUseBlackPointCompensation(bool useBlackPointCompensation) const;
bool allowLCMSOptimization(bool defaultValue = false) const;
void setAllowLCMSOptimization(bool allowLCMSOptimization);
void writeKoColor(const QString& name, const KoColor& color) const;
KoColor readKoColor(const QString& name, const KoColor& color = KoColor()) const;
bool showRulers(bool defaultValue = false) const;
void setShowRulers(bool rulers) const;
bool rulersTrackMouse(bool defaultValue = false) const;
void setRulersTrackMouse(bool value) const;
qint32 pasteBehaviour(bool defaultValue = false) const;
void setPasteBehaviour(qint32 behaviour) const;
qint32 monitorRenderIntent(bool defaultValue = false) const;
void setRenderIntent(qint32 monitorRenderIntent) const;
bool useOpenGL(bool defaultValue = false) const;
void setUseOpenGL(bool useOpenGL) const;
int openGLFilteringMode(bool defaultValue = false) const;
void setOpenGLFilteringMode(int filteringMode);
bool useOpenGLTextureBuffer(bool defaultValue = false) const;
void setUseOpenGLTextureBuffer(bool useBuffer);
bool disableVSync(bool defaultValue = false) const;
void setDisableVSync(bool disableVSync);
bool showAdvancedOpenGLSettings(bool defaultValue = false) const;
bool forceOpenGLFenceWorkaround(bool defaultValue = false) const;
int numMipmapLevels(bool defaultValue = false) const;
int openGLTextureSize(bool defaultValue = false) const;
int textureOverlapBorder() const;
qint32 maxNumberOfThreads(bool defaultValue = false) const;
void setMaxNumberOfThreads(qint32 numberOfThreads);
quint32 getGridMainStyle(bool defaultValue = false) const;
void setGridMainStyle(quint32 v) const;
quint32 getGridSubdivisionStyle(bool defaultValue = false) const;
void setGridSubdivisionStyle(quint32 v) const;
QColor getGridMainColor(bool defaultValue = false) const;
void setGridMainColor(const QColor & v) const;
QColor getGridSubdivisionColor(bool defaultValue = false) const;
void setGridSubdivisionColor(const QColor & v) const;
quint32 guidesLineStyle(bool defaultValue = false) const;
void setGuidesLineStyle(quint32 v) const;
QColor guidesColor(bool defaultValue = false) const;
void setGuidesColor(const QColor & v) const;
void loadSnapConfig(KisSnapConfig *config, bool defaultValue = false) const;
void saveSnapConfig(const KisSnapConfig &config);
qint32 checkSize(bool defaultValue = false) const;
void setCheckSize(qint32 checkSize) const;
bool scrollCheckers(bool defaultValue = false) const;
void setScrollingCheckers(bool scollCheckers) const;
QColor checkersColor1(bool defaultValue = false) const;
void setCheckersColor1(const QColor & v) const;
QColor checkersColor2(bool defaultValue = false) const;
void setCheckersColor2(const QColor & v) const;
QColor canvasBorderColor(bool defaultValue = false) const;
void setCanvasBorderColor(const QColor &color) const;
bool hideScrollbars(bool defaultValue = false) const;
void setHideScrollbars(bool value) const;
bool antialiasCurves(bool defaultValue = false) const;
void setAntialiasCurves(bool v) const;
QColor selectionOverlayMaskColor(bool defaultValue = false) const;
void setSelectionOverlayMaskColor(const QColor &color);
bool antialiasSelectionOutline(bool defaultValue = false) const;
void setAntialiasSelectionOutline(bool v) const;
bool showRootLayer(bool defaultValue = false) const;
void setShowRootLayer(bool showRootLayer) const;
bool showGlobalSelection(bool defaultValue = false) const;
void setShowGlobalSelection(bool showGlobalSelection) const;
bool showOutlineWhilePainting(bool defaultValue = false) const;
void setShowOutlineWhilePainting(bool showOutlineWhilePainting) const;
bool hideSplashScreen(bool defaultValue = false) const;
void setHideSplashScreen(bool hideSplashScreen) const;
qreal outlineSizeMinimum(bool defaultValue = false) const;
void setOutlineSizeMinimum(qreal outlineSizeMinimum) const;
qreal selectionViewSizeMinimum(bool defaultValue = false) const;
void setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const;
int autoSaveInterval(bool defaultValue = false) const;
void setAutoSaveInterval(int seconds) const;
bool backupFile(bool defaultValue = false) const;
void setBackupFile(bool backupFile) const;
bool showFilterGallery(bool defaultValue = false) const;
void setShowFilterGallery(bool showFilterGallery) const;
bool showFilterGalleryLayerMaskDialog(bool defaultValue = false) const;
void setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const;
// OPENGL_SUCCESS, TRY_OPENGL, OPENGL_NOT_TRIED, OPENGL_FAILED
QString canvasState(bool defaultValue = false) const;
void setCanvasState(const QString& state) const;
bool toolOptionsPopupDetached(bool defaultValue = false) const;
void setToolOptionsPopupDetached(bool detached) const;
bool paintopPopupDetached(bool defaultValue = false) const;
void setPaintopPopupDetached(bool detached) const;
QString pressureTabletCurve(bool defaultValue = false) const;
void setPressureTabletCurve(const QString& curveString) const;
qreal vastScrolling(bool defaultValue = false) const;
void setVastScrolling(const qreal factor) const;
int presetChooserViewMode(bool defaultValue = false) const;
void setPresetChooserViewMode(const int mode) const;
int presetIconSize(bool defaultValue = false) const;
void setPresetIconSize(const int value) const;
bool firstRun(bool defaultValue = false) const;
void setFirstRun(const bool firstRun) const;
bool clicklessSpacePan(bool defaultValue = false) const;
void setClicklessSpacePan(const bool toggle) const;
int horizontalSplitLines(bool defaultValue = false) const;
void setHorizontalSplitLines(const int numberLines) const;
int verticalSplitLines(bool defaultValue = false) const;
void setVerticalSplitLines(const int numberLines) const;
bool hideDockersFullscreen(bool defaultValue = false) const;
void setHideDockersFullscreen(const bool value) const;
bool showDockerTitleBars(bool defaultValue = false) const;
void setShowDockerTitleBars(const bool value) const;
bool showDockers(bool defaultValue = false) const;
void setShowDockers(const bool value) const;
bool showStatusBar(bool defaultValue = false) const;
void setShowStatusBar(const bool value) const;
bool hideMenuFullscreen(bool defaultValue = false) const;
void setHideMenuFullscreen(const bool value) const;
bool hideScrollbarsFullscreen(bool defaultValue = false) const;
void setHideScrollbarsFullscreen(const bool value) const;
bool hideStatusbarFullscreen(bool defaultValue = false) const;
void setHideStatusbarFullscreen(const bool value) const;
bool hideTitlebarFullscreen(bool defaultValue = false) const;
void setHideTitlebarFullscreen(const bool value) const;
bool hideToolbarFullscreen(bool defaultValue = false) const;
void setHideToolbarFullscreen(const bool value) const;
bool fullscreenMode(bool defaultValue = false) const;
void setFullscreenMode(const bool value) const;
QStringList favoriteCompositeOps(bool defaultValue = false) const;
void setFavoriteCompositeOps(const QStringList& compositeOps) const;
QString exportConfiguration(const QString &filterId, bool defaultValue = false) const;
void setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const;
bool useOcio(bool defaultValue = false) const;
void setUseOcio(bool useOCIO) const;
int favoritePresets(bool defaultValue = false) const;
void setFavoritePresets(const int value);
bool levelOfDetailEnabled(bool defaultValue = false) const;
void setLevelOfDetailEnabled(bool value);
enum OcioColorManagementMode {
INTERNAL = 0,
OCIO_CONFIG,
OCIO_ENVIRONMENT
};
OcioColorManagementMode ocioColorManagementMode(bool defaultValue = false) const;
void setOcioColorManagementMode(OcioColorManagementMode mode) const;
QString ocioConfigurationPath(bool defaultValue = false) const;
void setOcioConfigurationPath(const QString &path) const;
QString ocioLutPath(bool defaultValue = false) const;
void setOcioLutPath(const QString &path) const;
int ocioLutEdgeSize(bool defaultValue = false) const;
void setOcioLutEdgeSize(int value);
bool ocioLockColorVisualRepresentation(bool defaultValue = false) const;
void setOcioLockColorVisualRepresentation(bool value);
bool useSystemMonitorProfile(bool defaultValue = false) const;
void setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const;
QString defaultPalette(bool defaultValue = false) const;
void setDefaultPalette(const QString& name) const;
QString toolbarSlider(int sliderNumber, bool defaultValue = false) const;
void setToolbarSlider(int sliderNumber, const QString &slider);
bool sliderLabels(bool defaultValue = false) const;
void setSliderLabels(bool enabled);
QString currentInputProfile(bool defaultValue = false) const;
void setCurrentInputProfile(const QString& name);
bool presetStripVisible(bool defaultValue = false) const;
void setPresetStripVisible(bool visible);
bool scratchpadVisible(bool defaultValue = false) const;
void setScratchpadVisible(bool visible);
bool showSingleChannelAsColor(bool defaultValue = false) const;
void setShowSingleChannelAsColor(bool asColor);
bool hidePopups(bool defaultValue = false) const;
void setHidePopups(bool hidepopups);
int numDefaultLayers(bool defaultValue = false) const;
void setNumDefaultLayers(int num);
quint8 defaultBackgroundOpacity(bool defaultValue = false) const;
void setDefaultBackgroundOpacity(quint8 value);
QColor defaultBackgroundColor(bool defaultValue = false) const;
void setDefaultBackgroundColor(QColor value);
enum BackgroundStyle {
LAYER = 0,
PROJECTION = 1
};
BackgroundStyle defaultBackgroundStyle(bool defaultValue = false) const;
void setDefaultBackgroundStyle(BackgroundStyle value);
int lineSmoothingType(bool defaultValue = false) const;
void setLineSmoothingType(int value);
qreal lineSmoothingDistance(bool defaultValue = false) const;
void setLineSmoothingDistance(qreal value);
qreal lineSmoothingTailAggressiveness(bool defaultValue = false) const;
void setLineSmoothingTailAggressiveness(qreal value);
bool lineSmoothingSmoothPressure(bool defaultValue = false) const;
void setLineSmoothingSmoothPressure(bool value);
bool lineSmoothingScalableDistance(bool defaultValue = false) const;
void setLineSmoothingScalableDistance(bool value);
qreal lineSmoothingDelayDistance(bool defaultValue = false) const;
void setLineSmoothingDelayDistance(qreal value);
bool lineSmoothingUseDelayDistance(bool defaultValue = false) const;
void setLineSmoothingUseDelayDistance(bool value);
bool lineSmoothingFinishStabilizedCurve(bool defaultValue = false) const;
void setLineSmoothingFinishStabilizedCurve(bool value);
bool lineSmoothingStabilizeSensors(bool defaultValue = false) const;
void setLineSmoothingStabilizeSensors(bool value);
int paletteDockerPaletteViewSectionSize(bool defaultValue = false) const;
void setPaletteDockerPaletteViewSectionSize(int value) const;
int tabletEventsDelay(bool defaultValue = false) const;
void setTabletEventsDelay(int value);
bool testingAcceptCompressedTabletEvents(bool defaultValue = false) const;
void setTestingAcceptCompressedTabletEvents(bool value);
bool shouldEatDriverShortcuts(bool defaultValue = false) const;
bool testingCompressBrushEvents(bool defaultValue = false) const;
void setTestingCompressBrushEvents(bool value);
const KoColorSpace* customColorSelectorColorSpace(bool defaultValue = false) const;
void setCustomColorSelectorColorSpace(const KoColorSpace *cs);
bool useDirtyPresets(bool defaultValue = false) const;
void setUseDirtyPresets(bool value);
bool useEraserBrushSize(bool defaultValue = false) const;
void setUseEraserBrushSize(bool value);
bool useEraserBrushOpacity(bool defaultValue = false) const;
void setUseEraserBrushOpacity(bool value);
QColor getMDIBackgroundColor(bool defaultValue = false) const;
void setMDIBackgroundColor(const QColor & v) const;
QString getMDIBackgroundImage(bool defaultValue = false) const;
void setMDIBackgroundImage(const QString & fileName) const;
bool useVerboseOpenGLDebugOutput(bool defaultValue = false) const;
int workaroundX11SmoothPressureSteps(bool defaultValue = false) const;
bool showCanvasMessages(bool defaultValue = false) const;
void setShowCanvasMessages(bool show);
bool compressKra(bool defaultValue = false) const;
void setCompressKra(bool compress);
bool toolOptionsInDocker(bool defaultValue = false) const;
void setToolOptionsInDocker(bool inDocker);
void setEnableOpenGLDebugging(bool value) const;
bool enableOpenGLDebugging(bool defaultValue = false) const;
void setEnableAmdVectorizationWorkaround(bool value);
bool enableAmdVectorizationWorkaround(bool defaultValue = false) const;
bool animationDropFrames(bool defaultValue = false) const;
void setAnimationDropFrames(bool value);
int scrubbingUpdatesDelay(bool defaultValue = false) const;
void setScrubbingUpdatesDelay(int value);
int scrubbingAudioUpdatesDelay(bool defaultValue = false) const;
void setScrubbingAudioUpdatesDelay(int value);
int audioOffsetTolerance(bool defaultValue = false) const;
void setAudioOffsetTolerance(int value);
bool switchSelectionCtrlAlt(bool defaultValue = false) const;
void setSwitchSelectionCtrlAlt(bool value);
bool convertToImageColorspaceOnImport(bool defaultValue = false) const;
void setConvertToImageColorspaceOnImport(bool value);
int stabilizerSampleSize(bool defaultValue = false) const;
void setStabilizerSampleSize(int value);
int stabilizerDelayedPaintInterval(bool defaultValue = false) const;
void setStabilizerDelayedPaintInterval(int value);
QString customFFMpegPath(bool defaultValue = false) const;
void setCustomFFMpegPath(const QString &value) const;
bool showBrushHud(bool defaultValue = false) const;
void setShowBrushHud(bool value);
QString brushHudSetting(bool defaultValue = false) const;
void setBrushHudSetting(const QString &value) const;
+ bool calculateAnimationCacheInBackground(bool defaultValue = false) const;
+ void setCalculateAnimationCacheInBackground(bool value);
+
template<class T>
void writeEntry(const QString& name, const T& value) {
m_cfg.writeEntry(name, value);
}
template<class T>
void writeList(const QString& name, const QList<T>& value) {
m_cfg.writeEntry(name, value);
}
template<class T>
T readEntry(const QString& name, const T& defaultValue=T()) {
return m_cfg.readEntry(name, defaultValue);
}
template<class T>
QList<T> readList(const QString& name, const QList<T>& defaultValue=QList<T>()) {
return m_cfg.readEntry(name, defaultValue);
}
/// get the profile the color managment system has stored for the given screen
static const KoColorProfile* getScreenProfile(int screen);
private:
KisConfig(const KisConfig&);
KisConfig& operator=(const KisConfig&) const;
private:
mutable KConfigGroup m_cfg;
};
#endif // KIS_CONFIG_H_
diff --git a/libs/ui/kis_file_layer.cpp b/libs/ui/kis_file_layer.cpp
index 12742532cc..751e3a667c 100644
--- a/libs/ui/kis_file_layer.cpp
+++ b/libs/ui/kis_file_layer.cpp
@@ -1,204 +1,210 @@
/*
* Copyright (c) 2013 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_file_layer.h"
#include <QFile>
#include <QFileInfo>
#include "kis_transform_worker.h"
#include "kis_filter_strategy.h"
#include "kis_node_progress_proxy.h"
#include "kis_node_visitor.h"
#include "kis_image.h"
#include "kis_types.h"
#include "commands_new/kis_node_move_command2.h"
#include "kis_default_bounds.h"
+KisFileLayer::KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity)
+ : KisExternalLayer(image, name, opacity)
+{
+ connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int)));
+}
+
KisFileLayer::KisFileLayer(KisImageWSP image, const QString &basePath, const QString &filename, ScalingMethod scaleToImageResolution, const QString &name, quint8 opacity)
: KisExternalLayer(image, name, opacity)
, m_basePath(basePath)
, m_filename(filename)
, m_scalingMethod(scaleToImageResolution)
{
/**
* Set default paint device for a layer. It will be used is case
* the file does not exist anymore. Or course, this can happen only
* in the failing execution path.
*/
m_paintDevice = new KisPaintDevice(image->colorSpace());
connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int)));
QFileInfo fi(path());
if (fi.exists()) {
m_loader.setPath(path());
m_loader.reloadImage();
}
}
KisFileLayer::~KisFileLayer()
{
}
KisFileLayer::KisFileLayer(const KisFileLayer &rhs)
: KisExternalLayer(rhs)
{
m_basePath = rhs.m_basePath;
m_filename = rhs.m_filename;
Q_ASSERT(QFile::exists(rhs.path()));
m_scalingMethod = rhs.m_scalingMethod;
m_paintDevice = new KisPaintDevice(rhs.image()->colorSpace());
connect(&m_loader, SIGNAL(loadingFinished(KisPaintDeviceSP,int,int)), SLOT(slotLoadingFinished(KisPaintDeviceSP,int,int)));
m_loader.setPath(path());
m_loader.reloadImage();
}
QIcon KisFileLayer::icon() const
{
return KisIconUtils::loadIcon("fileLayer");
}
void KisFileLayer::resetCache()
{
m_loader.reloadImage();
}
const KoColorSpace *KisFileLayer::colorSpace() const
{
return m_paintDevice->colorSpace();
}
KisPaintDeviceSP KisFileLayer::original() const
{
return m_paintDevice;
}
KisPaintDeviceSP KisFileLayer::paintDevice() const
{
return 0;
}
KisBaseNode::PropertyList KisFileLayer::sectionModelProperties() const
{
KisBaseNode::PropertyList l = KisLayer::sectionModelProperties();
l << KisBaseNode::Property(KoID("sourcefile", i18n("File")), m_filename);
return l;
}
void KisFileLayer::setFileName(const QString &basePath, const QString &filename)
{
m_basePath = basePath;
m_filename = filename;
m_loader.setPath(path());
m_loader.reloadImage();
}
QString KisFileLayer::fileName() const
{
return m_filename;
}
QString KisFileLayer::path() const
{
if (m_basePath.isEmpty()) {
return m_filename;
}
else {
return m_basePath + '/' + m_filename;
}
}
KisFileLayer::ScalingMethod KisFileLayer::scalingMethod() const
{
return m_scalingMethod;
}
void KisFileLayer::slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes)
{
qint32 oldX = x();
qint32 oldY = y();
m_paintDevice->makeCloneFrom(projection, projection->extent());
m_paintDevice->setDefaultBounds(new KisDefaultBounds(image()));
QSize size = projection->exactBounds().size();
if (m_scalingMethod == ToImagePPI && (image()->xRes() != xRes
|| image()->yRes() != yRes)) {
qreal xscale = image()->xRes() / xRes;
qreal yscale = image()->yRes() / yRes;
KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic"));
worker.run();
}
else if (m_scalingMethod == ToImageSize) {
QSize sz = size;
sz.scale(image()->size(), Qt::KeepAspectRatio);
qreal xscale = (qreal)sz.width() / (qreal)size.width();
qreal yscale = (qreal)sz.height() / (qreal)size.height();
KisTransformWorker worker(m_paintDevice, xscale, yscale, 0.0, 0.0, 0.0, 0, 0, 0, 0, 0, KisFilterStrategyRegistry::instance()->get("Bicubic"));
worker.run();
}
m_paintDevice->setX(oldX);
m_paintDevice->setY(oldY);
setDirty();
}
KisNodeSP KisFileLayer::clone() const
{
qDebug() << "Cloning KisFileLayer" << m_filename;
return KisNodeSP(new KisFileLayer(*this));
}
bool KisFileLayer::allowAsChild(KisNodeSP node) const
{
return node->inherits("KisMask");
}
bool KisFileLayer::accept(KisNodeVisitor& visitor)
{
return visitor.visit(this);
}
void KisFileLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
{
return visitor.visit(this, undoAdapter);
}
KUndo2Command* KisFileLayer::crop(const QRect & rect)
{
QPoint oldPos(x(), y());
QPoint newPos = oldPos - rect.topLeft();
return new KisNodeMoveCommand2(this, oldPos, newPos);
}
KUndo2Command* KisFileLayer::transform(const QTransform &/*transform*/)
{
warnKrita << "WARNING: File Layer does not support transformations!" << name();
return 0;
}
diff --git a/libs/ui/kis_file_layer.h b/libs/ui/kis_file_layer.h
index 3c703c1b25..be3a5ee4de 100644
--- a/libs/ui/kis_file_layer.h
+++ b/libs/ui/kis_file_layer.h
@@ -1,83 +1,84 @@
/*
* Copyright (c) 2013 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_FILE_LAYER_H
#define KIS_FILE_LAYER_H
#include "kritaui_export.h"
#include "kis_external_layer_iface.h"
#include "kis_safe_document_loader.h"
/**
* @brief The KisFileLayer class loads a particular file as a layer into the layer stack.
*/
class KRITAUI_EXPORT KisFileLayer : public KisExternalLayer
{
Q_OBJECT
public:
enum ScalingMethod {
None,
ToImageSize,
ToImagePPI
};
- explicit KisFileLayer(KisImageWSP image, const QString& basePath, const QString &filename, ScalingMethod scalingMethod, const QString &name, quint8 opacity);
+ KisFileLayer(KisImageWSP image, const QString &name, quint8 opacity);
+ KisFileLayer(KisImageWSP image, const QString& basePath, const QString &filename, ScalingMethod scalingMethod, const QString &name, quint8 opacity);
~KisFileLayer();
KisFileLayer(const KisFileLayer& rhs);
QIcon icon() const;
void resetCache();
virtual const KoColorSpace *colorSpace() const;
KisPaintDeviceSP original() const;
KisPaintDeviceSP paintDevice() const;
KisBaseNode::PropertyList sectionModelProperties() const;
void setFileName(const QString &basePath, const QString &filename);
QString fileName() const;
QString path() const;
ScalingMethod scalingMethod() const;
KisNodeSP clone() const;
bool allowAsChild(KisNodeSP) const;
bool accept(KisNodeVisitor&);
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
KUndo2Command* crop(const QRect & rect);
KUndo2Command* transform(const QTransform &transform);
public Q_SLOTS:
void slotLoadingFinished(KisPaintDeviceSP projection, int xRes, int yRes);
private:
QString m_basePath;
QString m_filename;
ScalingMethod m_scalingMethod;
KisPaintDeviceSP m_paintDevice;
KisSafeDocumentLoader m_loader;
};
#endif // KIS_FILE_LAYER_H
diff --git a/libs/ui/kis_md5_generator.h b/libs/ui/kis_md5_generator.h
index 305a38d4ca..e4603b6f5d 100644
--- a/libs/ui/kis_md5_generator.h
+++ b/libs/ui/kis_md5_generator.h
@@ -1,29 +1,29 @@
/*
* Copyright (c) 2015 Stefano Bonicatti <smjert@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <resources/KoMD5Generator.h>
-
-class KisMD5Generator : public KoMD5Generator
+#include "kritaui_export.h"
+class KRITAUI_EXPORT KisMD5Generator : public KoMD5Generator
{
public:
KisMD5Generator();
~KisMD5Generator();
QByteArray generateHash(const QString &filename);
using KoMD5Generator::generateHash;
};
diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp
index 5a1fce8a00..271756f048 100644
--- a/libs/ui/kis_node_manager.cpp
+++ b/libs/ui/kis_node_manager.cpp
@@ -1,1343 +1,1343 @@
/*
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_node_manager.h"
#include <QDesktopServices>
#include <QMessageBox>
#include <QSignalMapper>
#include <kactioncollection.h>
#include <QKeySequence>
#include <kis_icon.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoShape.h>
#include <KoShapeLayer.h>
#include <KisImportExportManager.h>
#include <KoFileDialog.h>
#include <KoToolManager.h>
#include <KoProperties.h>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <kis_types.h>
#include <kis_node.h>
#include <kis_selection.h>
#include <kis_selection_mask.h>
#include <kis_layer.h>
#include <kis_mask.h>
#include <kis_image.h>
#include <kis_painter.h>
#include <kis_paint_layer.h>
#include <KisMimeDatabase.h>
#include "KisPart.h"
#include "canvas/kis_canvas2.h"
#include "kis_shape_controller.h"
#include "kis_canvas_resource_provider.h"
#include "KisViewManager.h"
#include "KisDocument.h"
#include "kis_mask_manager.h"
#include "kis_group_layer.h"
#include "kis_layer_manager.h"
#include "kis_selection_manager.h"
#include "kis_node_commands_adapter.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "kis_processing_applicator.h"
#include "kis_sequential_iterator.h"
#include "kis_transaction.h"
#include "kis_node_selection_adapter.h"
#include "kis_node_insertion_adapter.h"
#include "kis_node_juggler_compressed.h"
#include "kis_clipboard.h"
#include "kis_node_dummies_graph.h"
#include "kis_mimedata.h"
#include "kis_layer_utils.h"
#include "krita_utils.h"
#include "processing/kis_mirror_processing_visitor.h"
#include "KisView.h"
struct KisNodeManager::Private {
Private(KisNodeManager *_q, KisViewManager *v)
: q(_q)
, view(v)
, imageView(0)
, layerManager(v)
, maskManager(v)
, commandsAdapter(v)
, nodeSelectionAdapter(new KisNodeSelectionAdapter(q))
, nodeInsertionAdapter(new KisNodeInsertionAdapter(q))
{
}
KisNodeManager * q;
KisViewManager * view;
QPointer<KisView>imageView;
KisLayerManager layerManager;
KisMaskManager maskManager;
KisNodeCommandsAdapter commandsAdapter;
QScopedPointer<KisNodeSelectionAdapter> nodeSelectionAdapter;
QScopedPointer<KisNodeInsertionAdapter> nodeInsertionAdapter;
KisNodeList selectedNodes;
QPointer<KisNodeJugglerCompressed> nodeJuggler;
KisNodeWSP previouslyActiveNode;
bool activateNodeImpl(KisNodeSP node);
QSignalMapper nodeCreationSignalMapper;
QSignalMapper nodeConversionSignalMapper;
void saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity);
void mergeTransparencyMaskAsAlpha(bool writeToLayers);
KisNodeJugglerCompressed* lazyGetJuggler(const KUndo2MagicString &actionName);
};
bool KisNodeManager::Private::activateNodeImpl(KisNodeSP node)
{
Q_ASSERT(view);
Q_ASSERT(view->canvasBase());
Q_ASSERT(view->canvasBase()->globalShapeManager());
Q_ASSERT(imageView);
if (node && node == q->activeNode()) {
return false;
}
// Set the selection on the shape manager to the active layer
// and set call KoSelection::setActiveLayer( KoShapeLayer* layer )
// with the parent of the active layer.
KoSelection *selection = view->canvasBase()->globalShapeManager()->selection();
Q_ASSERT(selection);
selection->deselectAll();
if (!node) {
selection->setActiveLayer(0);
imageView->setCurrentNode(0);
maskManager.activateMask(0);
layerManager.activateLayer(0);
previouslyActiveNode = q->activeNode();
} else {
previouslyActiveNode = q->activeNode();
KoShape * shape = view->document()->shapeForNode(node);
KIS_ASSERT_RECOVER_RETURN_VALUE(shape, false);
selection->select(shape);
KoShapeLayer * shapeLayer = dynamic_cast<KoShapeLayer*>(shape);
KIS_ASSERT_RECOVER_RETURN_VALUE(shapeLayer, false);
// shapeLayer->setGeometryProtected(node->userLocked());
// shapeLayer->setVisible(node->visible());
selection->setActiveLayer(shapeLayer);
imageView->setCurrentNode(node);
if (KisLayerSP layer = qobject_cast<KisLayer*>(node.data())) {
maskManager.activateMask(0);
layerManager.activateLayer(layer);
} else if (KisMaskSP mask = dynamic_cast<KisMask*>(node.data())) {
maskManager.activateMask(mask);
// XXX_NODE: for now, masks cannot be nested.
layerManager.activateLayer(static_cast<KisLayer*>(node->parent().data()));
}
}
return true;
}
KisNodeManager::KisNodeManager(KisViewManager *view)
: m_d(new Private(this, view))
{
connect(&m_d->layerManager, SIGNAL(sigLayerActivated(KisLayerSP)), SIGNAL(sigLayerActivated(KisLayerSP)));
}
KisNodeManager::~KisNodeManager()
{
delete m_d;
}
void KisNodeManager::setView(QPointer<KisView>imageView)
{
m_d->maskManager.setView(imageView);
m_d->layerManager.setView(imageView);
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
shapeController->disconnect(SIGNAL(sigActivateNode(KisNodeSP)), this);
m_d->imageView->image()->disconnect(this);
}
m_d->imageView = imageView;
if (m_d->imageView) {
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
connect(shapeController, SIGNAL(sigActivateNode(KisNodeSP)), SLOT(slotNonUiActivatedNode(KisNodeSP)));
connect(m_d->imageView->image(), SIGNAL(sigIsolatedModeChanged()),this, SLOT(slotUpdateIsolateModeAction()));
connect(m_d->imageView->image(), SIGNAL(sigRequestNodeReselection(KisNodeSP, const KisNodeList&)),this, SLOT(slotImageRequestNodeReselection(KisNodeSP, const KisNodeList&)));
m_d->imageView->resourceProvider()->slotNodeActivated(m_d->imageView->currentNode());
}
}
#define NEW_LAYER_ACTION(id, layerType) \
{ \
action = actionManager->createAction(id); \
m_d->nodeCreationSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeCreationSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION_2(id, layerType, exclude) \
{ \
action = actionManager->createAction(id); \
action->setExcludedNodeTypes(QStringList(exclude)); \
actionManager->addAction(id, action); \
m_d->nodeConversionSignalMapper.setMapping(action, layerType); \
connect(action, SIGNAL(triggered()), \
&m_d->nodeConversionSignalMapper, SLOT(map())); \
}
#define CONVERT_NODE_ACTION(id, layerType) \
CONVERT_NODE_ACTION_2(id, layerType, layerType)
void KisNodeManager::setup(KActionCollection * actionCollection, KisActionManager* actionManager)
{
m_d->layerManager.setup(actionManager);
m_d->maskManager.setup(actionCollection, actionManager);
KisAction * action = actionManager->createAction("mirrorNodeX");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeX()));
action = actionManager->createAction("mirrorNodeY");
connect(action, SIGNAL(triggered()), this, SLOT(mirrorNodeY()));
action = actionManager->createAction("activateNextLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activateNextNode()));
action = actionManager->createAction("activatePreviousLayer");
connect(action, SIGNAL(triggered()), this, SLOT(activatePreviousNode()));
action = actionManager->createAction("switchToPreviouslyActiveNode");
connect(action, SIGNAL(triggered()), this, SLOT(switchToPreviouslyActiveNode()));
action = actionManager->createAction("save_node_as_image");
connect(action, SIGNAL(triggered()), this, SLOT(saveNodeAsImage()));
action = actionManager->createAction("duplicatelayer");
connect(action, SIGNAL(triggered()), this, SLOT(duplicateActiveNode()));
action = actionManager->createAction("copy_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(copyLayersToClipboard()));
action = actionManager->createAction("cut_layer_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(cutLayersToClipboard()));
action = actionManager->createAction("paste_layer_from_clipboard");
connect(action, SIGNAL(triggered()), this, SLOT(pasteLayersFromClipboard()));
action = actionManager->createAction("create_quick_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickGroup()));
action = actionManager->createAction("create_quick_clipping_group");
connect(action, SIGNAL(triggered()), this, SLOT(createQuickClippingGroup()));
action = actionManager->createAction("quick_ungroup");
connect(action, SIGNAL(triggered()), this, SLOT(quickUngroup()));
action = actionManager->createAction("select_all_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectAllNodes()));
action = actionManager->createAction("select_visible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectVisibleNodes()));
action = actionManager->createAction("select_locked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectLockedNodes()));
action = actionManager->createAction("select_invisible_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectInvisibleNodes()));
action = actionManager->createAction("select_unlocked_layers");
connect(action, SIGNAL(triggered()), this, SLOT(selectUnlockedNodes()));
action = actionManager->createAction("new_from_visible");
connect(action, SIGNAL(triggered()), this, SLOT(createFromVisible()));
NEW_LAYER_ACTION("add_new_paint_layer", "KisPaintLayer");
NEW_LAYER_ACTION("add_new_group_layer", "KisGroupLayer");
NEW_LAYER_ACTION("add_new_clone_layer", "KisCloneLayer");
NEW_LAYER_ACTION("add_new_shape_layer", "KisShapeLayer");
NEW_LAYER_ACTION("add_new_adjustment_layer", "KisAdjustmentLayer");
NEW_LAYER_ACTION("add_new_fill_layer", "KisGeneratorLayer");
NEW_LAYER_ACTION("add_new_file_layer", "KisFileLayer");
NEW_LAYER_ACTION("add_new_transparency_mask", "KisTransparencyMask");
NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask");
NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask");
NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask");
NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask");
connect(&m_d->nodeCreationSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(createNode(const QString &)));
CONVERT_NODE_ACTION("convert_to_paint_layer", "KisPaintLayer");
CONVERT_NODE_ACTION_2("convert_to_selection_mask", "KisSelectionMask", QStringList() << "KisSelectionMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_filter_mask", "KisFilterMask", QStringList() << "KisFilterMask" << "KisColorizeMask");
CONVERT_NODE_ACTION_2("convert_to_transparency_mask", "KisTransparencyMask", QStringList() << "KisTransparencyMask" << "KisColorizeMask");
CONVERT_NODE_ACTION("convert_to_animated", "animated");
connect(&m_d->nodeConversionSignalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(convertNode(const QString &)));
action = actionManager->createAction("isolate_layer");
connect(action, SIGNAL(triggered(bool)), this, SLOT(toggleIsolateMode(bool)));
action = actionManager->createAction("split_alpha_into_mask");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaIntoMask()));
action = actionManager->createAction("split_alpha_write");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaWrite()));
// HINT: we can save even when the nodes are not editable
action = actionManager->createAction("split_alpha_save_merged");
connect(action, SIGNAL(triggered()), this, SLOT(slotSplitAlphaSaveMerged()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotUpdateIsolateModeAction()));
connect(this, SIGNAL(sigNodeActivated(KisNodeSP)), SLOT(slotTryFinishIsolatedMode()));
}
void KisNodeManager::updateGUI()
{
// enable/disable all relevant actions
m_d->layerManager.updateGUI();
m_d->maskManager.updateGUI();
}
KisNodeSP KisNodeManager::activeNode()
{
if (m_d->imageView) {
return m_d->imageView->currentNode();
}
return 0;
}
KisLayerSP KisNodeManager::activeLayer()
{
return m_d->layerManager.activeLayer();
}
const KoColorSpace* KisNodeManager::activeColorSpace()
{
if (m_d->maskManager.activeDevice()) {
return m_d->maskManager.activeDevice()->colorSpace();
} else {
Q_ASSERT(m_d->layerManager.activeLayer());
if (m_d->layerManager.activeLayer()->parentLayer())
return m_d->layerManager.activeLayer()->parentLayer()->colorSpace();
else
return m_d->view->image()->colorSpace();
}
}
void KisNodeManager::moveNodeAt(KisNodeSP node, KisNodeSP parent, int index)
{
if (parent->allowAsChild(node)) {
if (node->inherits("KisSelectionMask") && parent->inherits("KisLayer")) {
KisSelectionMask *m = dynamic_cast<KisSelectionMask*>(node.data());
KisLayer *l = qobject_cast<KisLayer*>(parent.data());
KisSelectionMaskSP selMask = l->selectionMask();
if (m && m->active() && l && l->selectionMask())
selMask->setActive(false);
}
m_d->commandsAdapter.moveNode(node, parent, index);
}
}
void KisNodeManager::moveNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Move Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, aboveThis);
}
void KisNodeManager::copyNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Copy Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->copyNode(nodes, parent, aboveThis);
}
void KisNodeManager::addNodesDirect(KisNodeList nodes, KisNodeSP parent, KisNodeSP aboveThis)
{
KUndo2MagicString actionName = kundo2_i18n("Add Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->addNode(nodes, parent, aboveThis);
}
void KisNodeManager::toggleIsolateActiveNode()
{
KisImageWSP image = m_d->view->image();
KisNodeSP activeNode = this->activeNode();
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode == image->isolatedModeRoot()) {
toggleIsolateMode(false);
} else {
toggleIsolateMode(true);
}
}
void KisNodeManager::toggleIsolateMode(bool checked)
{
KisImageWSP image = m_d->view->image();
if (checked) {
KisNodeSP activeNode = this->activeNode();
// Transform and colorize masks don't have pixel data...
if (activeNode->inherits("KisTransformMask") ||
activeNode->inherits("KisColorizeMask")) return;
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (!image->startIsolatedMode(activeNode)) {
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
action->setChecked(false);
}
} else {
image->stopIsolatedMode();
}
}
void KisNodeManager::slotUpdateIsolateModeAction()
{
KisAction *action = m_d->view->actionManager()->actionByName("isolate_layer");
Q_ASSERT(action);
KisNodeSP activeNode = this->activeNode();
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
action->setChecked(isolatedRootNode && isolatedRootNode == activeNode);
}
void KisNodeManager::slotTryFinishIsolatedMode()
{
KisNodeSP isolatedRootNode = m_d->view->image()->isolatedModeRoot();
if (!isolatedRootNode) return;
this->toggleIsolateMode(true);
}
void KisNodeManager::createNode(const QString & nodeType, bool quiet, KisPaintDeviceSP copyFrom)
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
KIS_ASSERT_RECOVER_RETURN(activeNode);
if (activeNode->systemLocked()) {
return;
}
// XXX: make factories for this kind of stuff,
// with a registry
if (nodeType == "KisPaintLayer") {
m_d->layerManager.addLayer(activeNode);
} else if (nodeType == "KisGroupLayer") {
m_d->layerManager.addGroupLayer(activeNode);
} else if (nodeType == "KisAdjustmentLayer") {
m_d->layerManager.addAdjustmentLayer(activeNode);
} else if (nodeType == "KisGeneratorLayer") {
m_d->layerManager.addGeneratorLayer(activeNode);
} else if (nodeType == "KisShapeLayer") {
m_d->layerManager.addShapeLayer(activeNode);
} else if (nodeType == "KisCloneLayer") {
m_d->layerManager.addCloneLayer(activeNode);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false);
} else if (nodeType == "KisColorizeMask") {
m_d->maskManager.createColorizeMask(activeNode);
} else if (nodeType == "KisTransformMask") {
m_d->maskManager.createTransformMask(activeNode);
} else if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, false);
} else if (nodeType == "KisFileLayer") {
m_d->layerManager.addFileLayer(activeNode);
}
}
void KisNodeManager::createFromVisible()
{
KisLayerUtils::newLayerFromVisible(m_d->view->image(), m_d->view->image()->root()->lastChild());
}
KisLayerSP KisNodeManager::createPaintLayer()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) {
activeNode = m_d->view->image()->root();
}
return m_d->layerManager.addLayer(activeNode);
}
void KisNodeManager::convertNode(const QString &nodeType)
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
if (nodeType == "KisPaintLayer") {
m_d->layerManager.convertNodeToPaintLayer(activeNode);
} else if (nodeType == "KisSelectionMask" ||
nodeType == "KisFilterMask" ||
nodeType == "KisTransparencyMask") {
KisPaintDeviceSP copyFrom = activeNode->paintDevice() ?
activeNode->paintDevice() : activeNode->projection();
m_d->commandsAdapter.beginMacro(kundo2_i18n("Convert to a Selection Mask"));
if (nodeType == "KisSelectionMask") {
m_d->maskManager.createSelectionMask(activeNode, copyFrom, true);
} else if (nodeType == "KisFilterMask") {
m_d->maskManager.createFilterMask(activeNode, copyFrom, false, true);
} else if (nodeType == "KisTransparencyMask") {
m_d->maskManager.createTransparencyMask(activeNode, copyFrom, true);
}
m_d->commandsAdapter.removeNode(activeNode);
m_d->commandsAdapter.endMacro();
} else {
warnKrita << "Unsupported node conversion type:" << nodeType;
}
}
void KisNodeManager::slotSomethingActivatedNodeImpl(KisNodeSP node)
{
KIS_ASSERT_RECOVER_RETURN(node != activeNode());
if (m_d->activateNodeImpl(node)) {
emit sigUiNeedChangeActiveNode(node);
emit sigNodeActivated(node);
nodesUpdated();
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
}
void KisNodeManager::slotNonUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
bool toggled = m_d->view->actionCollection()->action("view_show_canvas_only")->isChecked();
if (toggled) {
m_d->view->showFloatingMessage( activeLayer()->name(), QIcon(), 1600, KisFloatingMessage::Medium, Qt::TextSingleLine);
}
}
}
void KisNodeManager::slotUiActivatedNode(KisNodeSP node)
{
if (node == activeNode()) return;
slotSomethingActivatedNodeImpl(node);
if (node) {
QStringList vectorTools = QStringList()
<< "InteractionTool"
<< "KarbonPatternTool"
<< "KarbonGradientTool"
<< "KarbonCalligraphyTool"
<< "CreateShapesTool"
<< "PathTool";
QStringList pixelTools = QStringList()
<< "KritaShape/KisToolBrush"
<< "KritaShape/KisToolDyna"
<< "KritaShape/KisToolMultiBrush"
<< "KritaFill/KisToolFill"
<< "KritaFill/KisToolGradient";
if (node->inherits("KisShapeLayer")) {
if (pixelTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("InteractionTool");
}
}
else {
if (vectorTools.contains(KoToolManager::instance()->activeToolId())) {
KoToolManager::instance()->switchToolRequested("KritaShape/KisToolBrush");
}
}
}
}
void KisNodeManager::nodesUpdated()
{
KisNodeSP node = activeNode();
if (!node) return;
m_d->layerManager.layersUpdated();
m_d->maskManager.masksUpdated();
m_d->view->updateGUI();
m_d->view->selectionManager()->selectionChanged();
}
KisPaintDeviceSP KisNodeManager::activePaintDevice()
{
return m_d->maskManager.activeMask() ?
m_d->maskManager.activeDevice() :
m_d->layerManager.activeDevice();
}
void KisNodeManager::nodeProperties(KisNodeSP node)
{
if (selectedNodes().size() > 1 || node->inherits("KisLayer")) {
m_d->layerManager.layerProperties();
} else if (node->inherits("KisMask")) {
m_d->maskManager.maskProperties();
}
}
qint32 KisNodeManager::convertOpacityToInt(qreal opacity)
{
/**
* Scales opacity from the range 0...100
* to the integer range 0...255
*/
return qMin(255, int(opacity * 2.55 + 0.5));
}
void KisNodeManager::setNodeOpacity(KisNodeSP node, qint32 opacity,
bool finalChange)
{
if (!node) return;
if (node->opacity() == opacity) return;
if (!finalChange) {
node->setOpacity(opacity);
node->setDirty();
} else {
m_d->commandsAdapter.setOpacity(node, opacity);
}
}
void KisNodeManager::setNodeCompositeOp(KisNodeSP node,
const KoCompositeOp* compositeOp)
{
if (!node) return;
if (node->compositeOp() == compositeOp) return;
m_d->commandsAdapter.setCompositeOp(node, compositeOp);
}
void KisNodeManager::slotImageRequestNodeReselection(KisNodeSP activeNode, const KisNodeList &selectedNodes)
{
if (activeNode) {
slotNonUiActivatedNode(activeNode);
}
if (!selectedNodes.isEmpty()) {
slotSetSelectedNodes(selectedNodes);
}
}
void KisNodeManager::slotSetSelectedNodes(const KisNodeList &nodes)
{
m_d->selectedNodes = nodes;
emit sigUiNeedChangeSelectedNodes(nodes);
}
KisNodeList KisNodeManager::selectedNodes()
{
return m_d->selectedNodes;
}
KisNodeSelectionAdapter* KisNodeManager::nodeSelectionAdapter() const
{
return m_d->nodeSelectionAdapter.data();
}
KisNodeInsertionAdapter* KisNodeManager::nodeInsertionAdapter() const
{
return m_d->nodeInsertionAdapter.data();
}
void KisNodeManager::nodeOpacityChanged(qreal opacity, bool finalChange)
{
KisNodeSP node = activeNode();
setNodeOpacity(node, convertOpacityToInt(opacity), finalChange);
}
void KisNodeManager::nodeCompositeOpChanged(const KoCompositeOp* op)
{
KisNodeSP node = activeNode();
setNodeCompositeOp(node, op);
}
void KisNodeManager::duplicateActiveNode()
{
KUndo2MagicString actionName = kundo2_i18n("Duplicate Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->duplicateNode(selectedNodes());
}
KisNodeJugglerCompressed* KisNodeManager::Private::lazyGetJuggler(const KUndo2MagicString &actionName)
{
KisImageWSP image = view->image();
if (!nodeJuggler ||
(nodeJuggler &&
!nodeJuggler->canMergeAction(actionName))) {
nodeJuggler = new KisNodeJugglerCompressed(actionName, image, q, 750);
nodeJuggler->setAutoDelete(true);
}
return nodeJuggler;
}
void KisNodeManager::raiseNode()
{
KUndo2MagicString actionName = kundo2_i18n("Raise Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->raiseNode(selectedNodes());
}
void KisNodeManager::lowerNode()
{
KUndo2MagicString actionName = kundo2_i18n("Lower Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->lowerNode(selectedNodes());
}
void KisNodeManager::removeSingleNode(KisNodeSP node)
{
if (!node || !node->parent()) {
return;
}
KisNodeList nodes;
nodes << node;
removeSelectedNodes(nodes);
}
void KisNodeManager::removeSelectedNodes(KisNodeList nodes)
{
KUndo2MagicString actionName = kundo2_i18n("Remove Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::removeNode()
{
removeSelectedNodes(selectedNodes());
}
void KisNodeManager::mirrorNodeX()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer X");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask X");
}
mirrorNode(node, commandName, Qt::Horizontal);
}
void KisNodeManager::mirrorNodeY()
{
KisNodeSP node = activeNode();
KUndo2MagicString commandName;
if (node->inherits("KisLayer")) {
commandName = kundo2_i18n("Mirror Layer Y");
} else if (node->inherits("KisMask")) {
commandName = kundo2_i18n("Mirror Mask Y");
}
mirrorNode(node, commandName, Qt::Vertical);
}
inline bool checkForGlobalSelection(KisNodeSP node) {
return dynamic_cast<KisSelectionMask*>(node.data()) && node->parent() && !node->parent()->parent();
}
void KisNodeManager::activateNextNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node = activeNode->nextSibling();
while (node && node->childCount() > 0) {
node = node->firstChild();
}
if (!node && activeNode->parent() && activeNode->parent()->parent()) {
node = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->nextSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::activatePreviousNode()
{
KisNodeSP activeNode = this->activeNode();
if (!activeNode) return;
KisNodeSP node;
if (activeNode->childCount() > 0) {
node = activeNode->lastChild();
}
else {
node = activeNode->prevSibling();
}
while (!node && activeNode->parent()) {
node = activeNode->parent()->prevSibling();
activeNode = activeNode->parent();
}
while(node && checkForGlobalSelection(node)) {
node = node->prevSibling();
}
if (node) {
slotNonUiActivatedNode(node);
}
}
void KisNodeManager::switchToPreviouslyActiveNode()
{
if (m_d->previouslyActiveNode && m_d->previouslyActiveNode->parent()) {
slotNonUiActivatedNode(m_d->previouslyActiveNode);
}
}
void KisNodeManager::mergeLayer()
{
m_d->layerManager.mergeLayer();
}
void KisNodeManager::rotate(double radians)
{
if(!m_d->view->image()) return;
KisNodeSP node = activeNode();
if (!node) return;
if (!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return;
m_d->view->image()->rotateNode(node, radians);
}
void KisNodeManager::rotate180()
{
rotate(M_PI);
}
void KisNodeManager::rotateLeft90()
{
rotate(-M_PI / 2);
}
void KisNodeManager::rotateRight90()
{
rotate(M_PI / 2);
}
void KisNodeManager::shear(double angleX, double angleY)
{
if (!m_d->view->image()) return;
KisNodeSP node = activeNode();
if (!node) return;
if(!m_d->view->blockUntilOperationsFinished(m_d->view->image())) return;
m_d->view->image()->shearNode(node, angleX, angleY);
}
void KisNodeManager::scale(double sx, double sy, KisFilterStrategy *filterStrategy)
{
KisNodeSP node = activeNode();
KIS_ASSERT_RECOVER_RETURN(node);
m_d->view->image()->scaleNode(node, sx, sy, filterStrategy);
nodesUpdated();
}
void KisNodeManager::mirrorNode(KisNodeSP node, const KUndo2MagicString& actionName, Qt::Orientation orientation)
{
KisImageSignalVector emitSignals;
emitSignals << ModifiedSignal;
KisProcessingApplicator applicator(m_d->view->image(), node,
KisProcessingApplicator::RECURSIVE,
emitSignals, actionName);
KisProcessingVisitorSP visitor =
new KisMirrorProcessingVisitor(m_d->view->image()->bounds(), orientation);
applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT);
applicator.end();
nodesUpdated();
}
void KisNodeManager::Private::saveDeviceAsImage(KisPaintDeviceSP device,
const QString &defaultName,
const QRect &bounds,
qreal xRes,
qreal yRes,
quint8 opacity)
{
KoFileDialog dialog(view->mainWindow(), KoFileDialog::SaveFile, "savenodeasimage");
dialog.setCaption(i18n("Export \"%1\"", defaultName));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Export));
QString filename = dialog.filename();
if (filename.isEmpty()) return;
QUrl url = QUrl::fromLocalFile(filename);
if (url.isEmpty()) return;
QString mimefilter = KisMimeDatabase::mimeTypeForFile(filename);;
- QScopedPointer<KisDocument> d(KisPart::instance()->createDocument());
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
- KisImageSP dst = new KisImage(d->createUndoStore(),
+ KisImageSP dst = new KisImage(doc->createUndoStore(),
bounds.width(),
bounds.height(),
device->compositionSourceColorSpace(),
defaultName);
dst->setResolution(xRes, yRes);
- d->setCurrentImage(dst);
+ doc->setCurrentImage(dst);
KisPaintLayer* paintLayer = new KisPaintLayer(dst, "paint device", opacity);
paintLayer->paintDevice()->makeCloneFrom(device, bounds);
dst->addNode(paintLayer, dst->rootLayer(), KisLayerSP(0));
dst->initialRefreshGraph();
- d->setOutputMimeType(mimefilter.toLatin1());
- d->exportDocument(url);
+ doc->setOutputMimeType(mimefilter.toLatin1());
+ doc->exportDocument(url);
}
void KisNodeManager::saveNodeAsImage()
{
KisNodeSP node = activeNode();
if (!node) {
warnKrita << "BUG: Save Node As Image was called without any node selected";
return;
}
KisImageWSP image = m_d->view->image();
QRect saveRect = image->bounds() | node->exactBounds();
KisPaintDeviceSP device = node->paintDevice();
if (!device) {
device = node->projection();
}
m_d->saveDeviceAsImage(device, node->name(),
saveRect,
image->xRes(), image->yRes(),
node->opacity());
}
void KisNodeManager::slotSplitAlphaIntoMask()
{
KisNodeSP node = activeNode();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->hasEditablePaintDevice());
KisPaintDeviceSP srcDevice = node->paintDevice();
const KoColorSpace *srcCS = srcDevice->colorSpace();
const QRect processRect =
srcDevice->exactBounds() |
srcDevice->defaultBounds()->bounds();
KisPaintDeviceSP selectionDevice =
new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
m_d->commandsAdapter.beginMacro(kundo2_i18n("Split Alpha into a Mask"));
KisTransaction transaction(kundo2_noi18n("__split_alpha_channel__"), srcDevice);
KisSequentialIterator srcIt(srcDevice, processRect);
KisSequentialIterator dstIt(selectionDevice, processRect);
do {
quint8 *srcPtr = srcIt.rawData();
quint8 *alpha8Ptr = dstIt.rawData();
*alpha8Ptr = srcCS->opacityU8(srcPtr);
srcCS->setOpacity(srcPtr, OPACITY_OPAQUE_U8, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
m_d->commandsAdapter.addExtraCommand(transaction.endAndTake());
createNode("KisTransparencyMask", false, selectionDevice);
m_d->commandsAdapter.endMacro();
}
void KisNodeManager::Private::mergeTransparencyMaskAsAlpha(bool writeToLayers)
{
KisNodeSP node = q->activeNode();
KisNodeSP parentNode = node->parent();
// guaranteed by KisActionManager
KIS_ASSERT_RECOVER_RETURN(node->inherits("KisTransparencyMask"));
if (writeToLayers && !parentNode->hasEditablePaintDevice()) {
QMessageBox::information(view->mainWindow(),
i18nc("@title:window", "Layer %1 is not editable").arg(parentNode->name()),
i18n("Cannot write alpha channel of "
"the parent layer \"%1\".\n"
"The operation will be cancelled.").arg(parentNode->name()));
return;
}
KisPaintDeviceSP dstDevice;
if (writeToLayers) {
KIS_ASSERT_RECOVER_RETURN(parentNode->paintDevice());
dstDevice = parentNode->paintDevice();
} else {
KisPaintDeviceSP copyDevice = parentNode->paintDevice();
if (!copyDevice) {
copyDevice = parentNode->original();
}
dstDevice = new KisPaintDevice(*copyDevice);
}
const KoColorSpace *dstCS = dstDevice->colorSpace();
KisPaintDeviceSP selectionDevice = node->paintDevice();
KIS_ASSERT_RECOVER_RETURN(selectionDevice->colorSpace()->pixelSize() == 1);
const QRect processRect =
selectionDevice->exactBounds() |
dstDevice->exactBounds() |
selectionDevice->defaultBounds()->bounds();
QScopedPointer<KisTransaction> transaction;
if (writeToLayers) {
commandsAdapter.beginMacro(kundo2_i18n("Write Alpha into a Layer"));
transaction.reset(new KisTransaction(kundo2_noi18n("__write_alpha_channel__"), dstDevice));
}
KisSequentialIterator srcIt(selectionDevice, processRect);
KisSequentialIterator dstIt(dstDevice, processRect);
do {
quint8 *alpha8Ptr = srcIt.rawData();
quint8 *dstPtr = dstIt.rawData();
dstCS->setOpacity(dstPtr, *alpha8Ptr, 1);
} while (srcIt.nextPixel() && dstIt.nextPixel());
if (writeToLayers) {
commandsAdapter.addExtraCommand(transaction->endAndTake());
commandsAdapter.removeNode(node);
commandsAdapter.endMacro();
} else {
KisImageWSP image = view->image();
QRect saveRect = image->bounds();
saveDeviceAsImage(dstDevice, parentNode->name(),
saveRect,
image->xRes(), image->yRes(),
OPACITY_OPAQUE_U8);
}
}
void KisNodeManager::slotSplitAlphaWrite()
{
m_d->mergeTransparencyMaskAsAlpha(true);
}
void KisNodeManager::slotSplitAlphaSaveMerged()
{
m_d->mergeTransparencyMaskAsAlpha(false);
}
void KisNodeManager::cutLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP root = m_d->view->image()->root();
if (nodes.isEmpty()) return;
KisClipboard::instance()->setLayers(nodes, root, false);
KUndo2MagicString actionName = kundo2_i18n("Cut Nodes");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->removeNode(nodes);
}
void KisNodeManager::copyLayersToClipboard()
{
KisNodeList nodes = this->selectedNodes();
KisNodeSP root = m_d->view->image()->root();
KisClipboard::instance()->setLayers(nodes, root, true);
}
void KisNodeManager::pasteLayersFromClipboard()
{
const QMimeData *data = KisClipboard::instance()->layersMimeData();
if (!data) return;
KisNodeSP activeNode = this->activeNode();
KisShapeController *shapeController = dynamic_cast<KisShapeController*>(m_d->imageView->document()->shapeController());
Q_ASSERT(shapeController);
KisDummiesFacadeBase *dummiesFacade = dynamic_cast<KisDummiesFacadeBase*>(m_d->imageView->document()->shapeController());
Q_ASSERT(dummiesFacade);
const bool copyNode = false;
KisImageSP image = m_d->view->image();
KisNodeDummy *parentDummy = dummiesFacade->dummyForNode(activeNode);
KisNodeDummy *aboveThisDummy = parentDummy ? parentDummy->lastChild() : 0;
KisMimeData::insertMimeLayers(data,
image,
shapeController,
parentDummy,
aboveThisDummy,
copyNode,
nodeInsertionAdapter());
}
void KisNodeManager::createQuickGroupImpl(KisNodeJugglerCompressed *juggler,
const QString &overrideGroupName,
KisNodeSP *newGroup,
KisNodeSP *newLastChild)
{
KisNodeSP active = activeNode();
if (!active) return;
KisImageSP image = m_d->view->image();
QString groupName = !overrideGroupName.isEmpty() ? overrideGroupName : image->nextLayerName();
KisGroupLayerSP group = new KisGroupLayer(image.data(), groupName, OPACITY_OPAQUE_U8);
KisNodeList nodes1;
nodes1 << group;
KisNodeList nodes2;
nodes2 = KisLayerUtils::sortMergableNodes(image->root(), selectedNodes());
KisLayerUtils::filterMergableNodes(nodes2);
if (KisLayerUtils::checkIsChildOf(active, nodes2)) {
active = nodes2.first();
}
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
juggler->addNode(nodes1, parent, aboveThis);
juggler->moveNode(nodes2, group, 0);
*newGroup = group;
*newLastChild = nodes2.last();
}
void KisNodeManager::createQuickGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
createQuickGroupImpl(juggler, "", &parent, &above);
}
void KisNodeManager::createQuickClippingGroup()
{
KUndo2MagicString actionName = kundo2_i18n("Quick Clipping Group");
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
KisNodeSP parent;
KisNodeSP above;
KisImageSP image = m_d->view->image();
createQuickGroupImpl(juggler, image->nextLayerName(i18nc("default name for a clipping group layer", "Clipping Group")), &parent, &above);
KisPaintLayerSP maskLayer = new KisPaintLayer(image.data(), i18nc("default name for quick clip group mask layer", "Mask Layer"), OPACITY_OPAQUE_U8, image->colorSpace());
maskLayer->disableAlphaChannel(true);
juggler->addNode(KisNodeList() << maskLayer, parent, above);
}
void KisNodeManager::quickUngroup()
{
KisNodeSP active = activeNode();
if (!active) return;
KisNodeSP parent = active->parent();
KisNodeSP aboveThis = active;
KUndo2MagicString actionName = kundo2_i18n("Quick Ungroup");
if (parent && dynamic_cast<KisGroupLayer*>(active.data())) {
KisNodeList nodes = active->childNodes(QStringList(), KoProperties());
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(nodes, parent, active);
juggler->removeNode(KisNodeList() << active);
} else if (parent && parent->parent()) {
KisNodeSP grandParent = parent->parent();
KisNodeList allChildNodes = parent->childNodes(QStringList(), KoProperties());
KisNodeList allSelectedNodes = selectedNodes();
const bool removeParent = KritaUtils::compareListsUnordered(allChildNodes, allSelectedNodes);
KisNodeJugglerCompressed *juggler = m_d->lazyGetJuggler(actionName);
juggler->moveNode(allSelectedNodes, grandParent, parent);
if (removeParent) {
juggler->removeNode(KisNodeList() << parent);
}
}
}
void KisNodeManager::selectLayersImpl(const KoProperties &props, const KoProperties &invertedProps)
{
KisImageSP image = m_d->view->image();
KisNodeList nodes = KisLayerUtils::findNodesWithProps(image->root(), props, true);
KisNodeList selectedNodes = this->selectedNodes();
if (KritaUtils::compareListsUnordered(nodes, selectedNodes)) {
nodes = KisLayerUtils::findNodesWithProps(image->root(), invertedProps, true);
}
if (!nodes.isEmpty()) {
slotImageRequestNodeReselection(nodes.last(), nodes);
}
}
void KisNodeManager::selectAllNodes()
{
KoProperties props;
selectLayersImpl(props, props);
}
void KisNodeManager::selectVisibleNodes()
{
KoProperties props;
props.setProperty("visible", true);
KoProperties invertedProps;
invertedProps.setProperty("visible", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectLockedNodes()
{
KoProperties props;
props.setProperty("locked", true);
KoProperties invertedProps;
invertedProps.setProperty("locked", false);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectInvisibleNodes()
{
KoProperties props;
props.setProperty("visible", false);
KoProperties invertedProps;
invertedProps.setProperty("visible", true);
selectLayersImpl(props, invertedProps);
}
void KisNodeManager::selectUnlockedNodes()
{
KoProperties props;
props.setProperty("locked", false);
KoProperties invertedProps;
invertedProps.setProperty("locked", true);
selectLayersImpl(props, invertedProps);
}
diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp
index 726d0a6f02..6117269b91 100644
--- a/libs/ui/kis_png_converter.cpp
+++ b/libs/ui/kis_png_converter.cpp
@@ -1,1287 +1,1286 @@
/*
* Copyright (c) 2005-2007 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_png_converter.h"
// A big thank to Glenn Randers-Pehrson for his wonderful
// documentation of libpng available at
// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html
#ifndef PNG_MAX_UINT // Removed in libpng 1.4
#define PNG_MAX_UINT PNG_UINT_31_MAX
#endif
#include <KoConfig.h> // WORDS_BIGENDIAN
#include <KoStore.h>
#include <KoStoreDevice.h>
#include <limits.h>
#include <stdio.h>
#include <zlib.h>
#include <QBuffer>
#include <QFile>
#include <QApplication>
#include <klocalizedstring.h>
#include <QUrl>
#include <KoColorSpace.h>
#include <KoDocumentInfo.h>
#include <KoID.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoColorSpace.h>
#include <KoColor.h>
#include <KoUnit.h>
#include <kis_config.h>
#include <kis_painter.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_iterator_ng.h>
#include <kis_layer.h>
#include <kis_paint_device.h>
#include <kis_transaction.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <metadata/kis_meta_data_io_backend.h>
#include <metadata/kis_meta_data_store.h>
#include <KoColorModelStandardIds.h>
#include "dialogs/kis_dlg_png_import.h"
#include "kis_clipboard.h"
namespace
{
int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha)
{
QString id = cs->id();
if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") {
return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY;
}
if (id == "RGBA" || id == "RGBA16") {
return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB;
}
return -1;
}
bool colorSpaceIdSupported(const QString &id)
{
return id == "RGBA" || id == "RGBA16" ||
id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16";
}
QPair<QString, QString> getColorSpaceForColorType(int color_type, int color_nb_bits)
{
QPair<QString, QString> r;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
r.first = RGBAColorModelID.id();
r.second = Integer8BitsColorDepthID.id();
} else {
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
r.first = GrayAColorModelID.id();
} else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) {
r.first = RGBAColorModelID.id();
}
if (color_nb_bits == 16) {
r.second = Integer16BitsColorDepthID.id();
} else if (color_nb_bits <= 8) {
r.second = Integer8BitsColorDepthID.id();
}
}
return r;
}
void fillText(png_text* p_text, const char* key, QString& text)
{
p_text->compression = PNG_TEXT_COMPRESSION_zTXt;
p_text->key = const_cast<char *>(key);
char* textc = new char[text.length()+1];
strcpy(textc, text.toLatin1());
p_text->text = textc;
p_text->text_length = text.length() + 1;
}
long formatStringList(char *string, const size_t length, const char *format, va_list operands)
{
int n = vsnprintf(string, length, format, operands);
if (n < 0)
string[length-1] = '\0';
return((long) n);
}
long formatString(char *string, const size_t length, const char *format, ...)
{
long n;
va_list operands;
va_start(operands, format);
n = (long) formatStringList(string, length, format, operands);
va_end(operands);
return(n);
}
void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data)
{
png_textp text;
png_uint_32 allocated_length, description_length;
const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl;
text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text));
description_length = profile_type.length();
allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length);
text[0].text = (png_charp) png_malloc(ping, allocated_length);
QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1();
QByteArray keyData = key.toLatin1();
text[0].key = keyData.data();
uchar* sp = (uchar*)profile_data.data();
png_charp dp = text[0].text;
*dp++ = '\n';
memcpy(dp, profile_type.toLatin1().constData(), profile_type.length());
dp += description_length;
*dp++ = '\n';
formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length());
dp += 8;
for (long i = 0; i < (long) profile_data.length(); i++) {
if (i % 36 == 0)
*dp++ = '\n';
*(dp++) = (char) hex[((*sp >> 4) & 0x0f)];
*(dp++) = (char) hex[((*sp++) & 0x0f)];
}
*dp++ = '\n';
*dp = '\0';
text[0].text_length = (png_size_t)(dp - text[0].text);
text[0].compression = -1;
if (text[0].text_length <= allocated_length)
png_set_text(ping, ping_info, text, 1);
png_free(ping, text[0].text);
png_free(ping, text);
}
QByteArray png_read_raw_profile(png_textp text)
{
QByteArray profile;
static const unsigned char unhex[103] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12,
13, 14, 15
};
png_charp sp = text[0].text + 1;
/* look for newline */
while (*sp != '\n')
sp++;
/* look for length */
while (*sp == '\0' || *sp == ' ' || *sp == '\n')
sp++;
png_uint_32 length = (png_uint_32) atol(sp);
while (*sp != ' ' && *sp != '\n')
sp++;
if (length == 0) {
return profile;
}
profile.resize(length);
/* copy profile, skipping white space and column 1 "=" signs */
unsigned char *dp = (unsigned char*)profile.data();
png_uint_32 nibbles = length * 2;
for (png_uint_32 i = 0; i < nibbles; i++) {
while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') {
if (*sp == '\0') {
return QByteArray();
}
sp++;
}
if (i % 2 == 0)
*dp = (unsigned char)(16 * unhex[(int) *sp++]);
else
(*dp++) += unhex[(int) *sp++];
}
return profile;
}
void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize)
{
dbgFile << "Decoding " << type << " " << text[0].key;
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type);
Q_ASSERT(exifIO);
QByteArray rawProfile = png_read_raw_profile(text);
if (headerSize > 0) {
rawProfile.remove(0, headerSize);
}
if (rawProfile.size() > 0) {
QBuffer buffer;
buffer.setData(rawProfile);
exifIO->loadFrom(store, &buffer);
} else {
dbgFile << "Decoding failed";
}
}
}
KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode)
{
// Q_ASSERT(doc);
// Q_ASSERT(adapter);
m_doc = doc;
m_stop = false;
m_max_row = 0;
m_image = 0;
m_batchMode = batchMode;
}
KisPNGConverter::~KisPNGConverter()
{
}
class KisPNGReadStream
{
public:
KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
}
int nextValue() {
if (m_posinc == 0) {
m_posinc = 8;
m_buf++;
}
m_posinc -= m_depth;
return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1));
}
private:
quint32 m_posinc, m_depth;
quint8* m_buf;
};
class KisPNGWriteStream
{
public:
KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) {
*m_buf = 0;
}
void setNextValue(int v) {
if (m_posinc == 0) {
m_posinc = 8;
m_buf++;
*m_buf = 0;
}
m_posinc -= m_depth;
*m_buf = (v << m_posinc) | *m_buf;
}
private:
quint32 m_posinc, m_depth;
quint8* m_buf;
};
class KisPNGReaderAbstract
{
public:
KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {}
virtual ~KisPNGReaderAbstract() {}
virtual png_bytep readLine() = 0;
protected:
png_structp png_ptr;
int width, height;
};
class KisPNGReaderLineByLine : public KisPNGReaderAbstract
{
public:
KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) {
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
row_pointer = new png_byte[rowbytes];
}
~KisPNGReaderLineByLine() override {
delete[] row_pointer;
}
png_bytep readLine() override {
png_read_row(png_ptr, row_pointer, 0);
return row_pointer;
}
private:
png_bytep row_pointer;
};
class KisPNGReaderFullImage : public KisPNGReaderAbstract
{
public:
KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) {
row_pointers = new png_bytep[height];
png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
for (int i = 0; i < height; i++) {
row_pointers[i] = new png_byte[rowbytes];
}
png_read_image(png_ptr, row_pointers);
}
~KisPNGReaderFullImage() override {
for (int i = 0; i < height; i++) {
delete[] row_pointers[i];
}
delete[] row_pointers;
}
png_bytep readLine() override {
return row_pointers[y++];
}
private:
png_bytepp row_pointers;
int y;
};
static
void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr);
while (length) {
int nr = in->read((char*)data, length);
if (nr <= 0) {
png_error(png_ptr, "Read Error");
return;
}
length -= nr;
}
}
static
void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
{
QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr);
uint nr = out->write((char*)data, length);
if (nr != length) {
png_error(png_ptr, "Write Error");
return;
}
}
static
void _flush_fn(png_structp png_ptr)
{
Q_UNUSED(png_ptr);
}
KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod)
{
dbgFile << "Start decoding PNG File";
png_byte signature[8];
iod->peek((char*)signature, 8);
#if PNG_LIBPNG_VER < 10400
if (!png_check_sig(signature, 8)) {
#else
if (png_sig_cmp(signature, 0, 8) != 0) {
#endif
iod->close();
return (KisImageBuilder_RESULT_BAD_FETCH);
}
// Initialize the internal structures
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png_ptr) {
iod->close();
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0);
iod->close();
return (KisImageBuilder_RESULT_FAILURE);
}
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info) {
png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0);
iod->close();
return (KisImageBuilder_RESULT_FAILURE);
}
// Catch errors
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
iod->close();
return (KisImageBuilder_RESULT_FAILURE);
}
// Initialize the special
png_set_read_fn(png_ptr, iod, _read_fn);
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
// read all PNG info up to image data
png_read_info(png_ptr, info_ptr);
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) {
png_set_expand(png_ptr);
}
if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) {
png_set_packing(png_ptr);
}
if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE &&
(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) {
png_set_expand(png_ptr);
}
png_read_update_info(png_ptr, info_ptr);
// Read information about the png
png_uint_32 width, height;
int color_nb_bits, color_type, interlace_type;
png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0);
dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl;
// swap byteorder on little endian machines.
#ifndef WORDS_BIGENDIAN
if (color_nb_bits > 8)
png_set_swap(png_ptr);
#endif
// Determine the colorspace
QPair<QString, QString> csName = getColorSpaceForColorType(color_type, color_nb_bits);
if (csName.first.isEmpty()) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
iod->close();
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
}
bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA);
// Read image profile
png_charp profile_name;
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
png_bytep profile_data;
#else
png_charp profile_data;
#endif
int compression_type;
png_uint_32 proflen;
// Get the various optional chunks
// https://www.w3.org/TR/PNG/#11cHRM
#if defined(PNG_cHRM_SUPPORTED)
double whitePointX, whitePointY;
double redX, redY;
double greenX, greenY;
double blueX, blueY;
png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY);
qDebug() << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY;
#endif
// https://www.w3.org/TR/PNG/#11gAMA
#if defined(PNG_GAMMA_SUPPORTED)
double gamma;
png_get_gAMA(png_ptr, info_ptr, &gamma);
qDebug() << "gAMA" << gamma;
#endif
// https://www.w3.org/TR/PNG/#11sRGB
#if defined(PNG_sRGB_SUPPORTED)
int sRGBIntent;
png_get_sRGB(png_ptr, info_ptr, &sRGBIntent);
qDebug() << "sRGB" << sRGBIntent;
#endif
bool fromBlender = false;
png_text* text_ptr;
int num_comments;
png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
for (int i = 0; i < num_comments; i++) {
QString key = QString(text_ptr[i].key).toLower();
if (key == "file") {
QString relatedFile = text_ptr[i].text;
if (relatedFile.contains(".blend", Qt::CaseInsensitive)){
fromBlender=true;
}
}
}
const KoColorProfile* profile = 0;
if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) {
QByteArray profile_rawdata;
// XXX: Hardcoded for icc type -- is that correct for us?
profile_rawdata.resize(proflen);
memcpy(profile_rawdata.data(), profile_data, proflen);
profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata);
Q_CHECK_PTR(profile);
if (profile) {
// dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo();
if (!profile->isSuitableForOutput()) {
dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user
}
}
}
else {
dbgFile << "no embedded profile, will use the default profile";
if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) {
KisConfig cfg;
quint32 behaviour = cfg.pasteBehaviour();
if (behaviour == PASTE_ASK) {
KisDlgPngImport dlg(m_path, csName.first, csName.second);
QApplication::restoreOverrideCursor();
dlg.exec();
if (!dlg.profile().isEmpty()) {
QString s = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second);
const KoColorSpaceFactory * csf = KoColorSpaceRegistry::instance()->colorSpaceFactory(s);
if (csf) {
QList<const KoColorProfile *> profileList = KoColorSpaceRegistry::instance()->profilesFor(csf);
Q_FOREACH (const KoColorProfile *p, profileList) {
if (p->name() == dlg.profile()) {
profile = p;
break;
}
}
}
}
QApplication::setOverrideCursor(Qt::WaitCursor);
}
}
dbgFile << "no embedded profile, will use the default profile";
}
// Check that the profile is used by the color space
if (profile && !KoColorSpaceRegistry::instance()->colorSpaceFactory(
KoColorSpaceRegistry::instance()->colorSpaceId(
csName.first, csName.second))->profileIsCompatible(profile)) {
warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << csName.first << " " << csName.second;
profile = 0;
}
// Retrieve a pointer to the colorspace
const KoColorSpace* cs;
if (profile && profile->isSuitableForOutput()) {
dbgFile << "image has embedded profile: " << profile->name() << "\n";
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile);
}
else {
if (csName.first == RGBAColorModelID.id()) {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc");
}
else {
cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0);
}
}
if (cs == 0) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE;
}
//TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation
// Create the cmsTransform if needed
KoColorTransformation* transform = 0;
if (profile && !profile->isSuitableForOutput()) {
transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
}
// Creating the KisImageWSP
if (m_image == 0) {
m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image");
Q_CHECK_PTR(m_image);
}
// Read resolution
int unit_type;
png_uint_32 x_resolution, y_resolution;
png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type);
if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) {
m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points
}
double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1);
KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX);
// Read comments/texts...
png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments);
if (m_doc) {
KoDocumentInfo * info = m_doc->documentInfo();
dbgFile << "There are " << num_comments << " comments in the text";
for (int i = 0; i < num_comments; i++) {
QString key = QString(text_ptr[i].key).toLower();
dbgFile << "key is |" << text_ptr[i].key << "| containing " << text_ptr[i].text << " " << (key == "Raw profile type exif ");
if (key == "title") {
info->setAboutInfo("title", text_ptr[i].text);
} else if (key == "description") {
info->setAboutInfo("comment", text_ptr[i].text);
} else if (key == "author") {
qDebug()<<"Author:"<<text_ptr[i].text;
info->setAuthorInfo("creator", text_ptr[i].text);
} else if (key.contains("Raw profile type exif")) {
decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6);
} else if (key.contains("Raw profile type iptc")) {
decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14);
} else if (key.contains("Raw profile type xmp")) {
decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0);
} else if (key == "version") {
m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text)));
} else if (key == "preset") {
m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text)));
}
}
}
// Read image data
KisPNGReaderAbstract* reader = 0;
try {
if (interlace_type == PNG_INTERLACE_ADAM7) {
reader = new KisPNGReaderFullImage(png_ptr, info_ptr, width, height);
} else {
reader = new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height);
}
} catch (std::bad_alloc& e) {
// new png_byte[] may raise such an exception if the image
// is invalid / to large.
dbgFile << "bad alloc: " << e.what();
// Free only the already allocated png_byte instances.
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
return (KisImageBuilder_RESULT_FAILURE);
}
// Read the palette if the file is indexed
png_colorp palette ;
int num_palette;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
}
// Read the transparency palette
quint8 palette_alpha[256];
memset(palette_alpha, 255, 256);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_bytep alpha_ptr;
int num_alpha;
png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0);
for (int i = 0; i < num_alpha; ++i) {
palette_alpha[i] = alpha_ptr[i];
}
}
}
for (png_uint_32 y = 0; y < height; y++) {
KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width);
png_bytep row_pointer = reader->readLine();
switch (color_type) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (color_nb_bits == 16) {
quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
do {
quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
d[0] = *(src++);
if (transform) transform->transform(reinterpret_cast<quint8*>(d), reinterpret_cast<quint8*>(d), 1);
if (hasalpha) {
d[1] = *(src++);
} else {
d[1] = quint16_MAX;
}
} while (it->nextPixel());
} else {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
d[0] = (quint8)(stream.nextValue() * coeff);
if (transform) transform->transform(d, d, 1);
if (hasalpha) {
d[1] = (quint8)(stream.nextValue() * coeff);
} else {
d[1] = UCHAR_MAX;
}
} while (it->nextPixel());
}
// FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits"
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
if (color_nb_bits == 16) {
quint16 *src = reinterpret_cast<quint16 *>(row_pointer);
do {
quint16 *d = reinterpret_cast<quint16 *>(it->rawData());
d[2] = *(src++);
d[1] = *(src++);
d[0] = *(src++);
if (transform) transform->transform(reinterpret_cast<quint8 *>(d), reinterpret_cast<quint8*>(d), 1);
if (hasalpha) d[3] = *(src++);
else d[3] = quint16_MAX;
} while (it->nextPixel());
} else {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
d[2] = (quint8)(stream.nextValue() * coeff);
d[1] = (quint8)(stream.nextValue() * coeff);
d[0] = (quint8)(stream.nextValue() * coeff);
if (transform) transform->transform(d, d, 1);
if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff);
else d[3] = UCHAR_MAX;
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_PALETTE: {
KisPNGReadStream stream(row_pointer, color_nb_bits);
do {
quint8 *d = it->rawData();
quint8 index = stream.nextValue();
quint8 alpha = palette_alpha[ index ];
if (alpha == 0) {
memset(d, 0, 4);
} else {
png_color c = palette[ index ];
d[2] = c.red;
d[1] = c.green;
d[0] = c.blue;
d[3] = alpha;
}
} while (it->nextPixel());
}
break;
default:
return KisImageBuilder_RESULT_UNSUPPORTED;
}
}
m_image->addNode(layer.data(), m_image->rootLayer().data());
png_read_end(png_ptr, end_info);
iod->close();
// Freeing memory
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
delete reader;
return KisImageBuilder_RESULT_OK;
}
KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename)
{
m_path = filename;
QFile fp(filename);
if (fp.exists()) {
if (!fp.open(QIODevice::ReadOnly)) {
dbgFile << "Failed to open PNG File";
return (KisImageBuilder_RESULT_FAILURE);
}
return buildImage(&fp);
}
return (KisImageBuilder_RESULT_NOT_EXIST);
}
KisImageSP KisPNGConverter::image()
{
return m_image;
}
bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData)
{
if (store->open(filename)) {
KoStoreDevice io(store);
if (!io.open(QIODevice::WriteOnly)) {
dbgFile << "Could not open for writing:" << filename;
return false;
}
KisPNGConverter pngconv(0);
vKisAnnotationSP_it annotIt = 0;
KisMetaData::Store* metaDataStore = 0;
if (metaData) {
metaDataStore = new KisMetaData::Store(*metaData);
}
KisPNGOptions options;
options.compression = 0;
options.interlace = false;
options.tryToSaveAsIndexed = false;
options.alpha = true;
options.saveSRGBProfile = false;
if (dev->colorSpace()->id() != "RGBA") {
dev = new KisPaintDevice(*dev.data());
KUndo2Command *cmd = dev->convertTo(KoColorSpaceRegistry::instance()->rgb8());
delete cmd;
}
bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore);
if (success != KisImageBuilder_RESULT_OK) {
dbgFile << "Saving PNG failed:" << filename;
delete metaDataStore;
return false;
}
delete metaDataStore;
io.close();
if (!store->close()) {
return false;
}
} else {
dbgFile << "Opening of data file failed :" << filename;
return false;
}
return true;
}
KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
{
dbgFile << "Start writing PNG File " << filename;
// Open a QIODevice for writing
QFile fp (filename);
if (!fp.open(QIODevice::WriteOnly)) {
dbgFile << "Failed to open PNG File for writing";
return (KisImageBuilder_RESULT_FAILURE);
}
KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData);
return result;
}
KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData)
{
if (!device)
return KisImageBuilder_RESULT_INVALID_ARG;
if (!options.alpha) {
KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace());
KoColor c(options.transparencyFillColor, device->colorSpace());
tmp->fill(imageRect, c);
KisPainter gc(tmp);
gc.bitBlt(imageRect.topLeft(), device, imageRect);
gc.end();
device = tmp;
}
if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID
|| device->colorSpace()->colorDepthId() == Float32BitsColorDepthID
|| device->colorSpace()->colorDepthId() == Float64BitsColorDepthID) {
const KoColorSpace *dstcs = KoColorSpaceRegistry::instance()->colorSpace(device->colorSpace()->colorModelId().id(), Integer8BitsColorDepthID.id(), "");
KisPaintDeviceSP tmp = new KisPaintDevice(dstcs);
KisPainter gc(tmp);
gc.bitBlt(imageRect.topLeft(), device, imageRect);
gc.end();
device = tmp;
}
if (options.forceSRGB) {
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)");
device = new KisPaintDevice(*device);
device->convertTo(cs);
}
// Initialize structures
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png_ptr) {
return (KisImageBuilder_RESULT_FAILURE);
}
#if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED)
png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON);
#endif
#ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED
png_set_check_for_invalid_index(png_ptr, 0);
#endif
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, (png_infopp)0);
return (KisImageBuilder_RESULT_FAILURE);
}
// If an error occurs during writing, libpng will jump here
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
return (KisImageBuilder_RESULT_FAILURE);
}
// Initialize the writing
// png_init_io(png_ptr, fp);
// Setup the progress function
// XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);"
// setProgressTotalSteps(100/*height*/);
/* set the zlib compression level */
png_set_compression_level(png_ptr, options.compression);
png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn);
/* set other zlib parameters */
png_set_compression_mem_level(png_ptr, 8);
png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY);
png_set_compression_window_bits(png_ptr, 15);
png_set_compression_method(png_ptr, 8);
png_set_compression_buffer_size(png_ptr, 8192);
int color_nb_bits = 8 * device->pixelSize() / device->channelCount();
int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha);
Q_ASSERT(color_type > -1);
// Try to compute a table of color if the colorspace is RGB8f
png_colorp palette = 0;
int num_palette = 0;
if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8
palette = new png_color[255];
KisSequentialIterator it(device, imageRect);
bool toomuchcolor = false;
do {
const quint8* c = it.oldRawData();
bool findit = false;
for (int i = 0; i < num_palette; i++) {
if (palette[i].red == c[2] &&
palette[i].green == c[1] &&
palette[i].blue == c[0]) {
findit = true;
break;
}
}
if (!findit) {
if (num_palette == 255) {
toomuchcolor = true;
break;
}
palette[num_palette].red = c[2];
palette[num_palette].green = c[1];
palette[num_palette].blue = c[0];
num_palette++;
}
} while (it.nextPixel());
if (!toomuchcolor) {
dbgFile << "Found a palette of " << num_palette << " colors";
color_type = PNG_COLOR_TYPE_PALETTE;
if (num_palette <= 2) {
color_nb_bits = 1;
} else if (num_palette <= 4) {
color_nb_bits = 2;
} else if (num_palette <= 16) {
color_nb_bits = 4;
} else {
color_nb_bits = 8;
}
} else {
delete [] palette;
}
}
int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE;
png_set_IHDR(png_ptr, info_ptr,
imageRect.width(),
imageRect.height(),
color_nb_bits,
color_type, interlacetype,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present
bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive);
/*
* This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's
* color management is bugged, so once you give it any incentive to start color managing an sRGB image it
* will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now.
*/
/*if (!options.saveSRGBProfile && sRGB) {
png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL);
}*/
// set the palette
if (color_type == PNG_COLOR_TYPE_PALETTE) {
png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
}
// Save annotation
vKisAnnotationSP_it it = annotationsStart;
while (it != annotationsEnd) {
if (!(*it) || (*it)->type().isEmpty()) {
dbgFile << "Warning: empty annotation";
it++;
continue;
}
dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size();
if ((*it) -> type().startsWith(QString("krita_attribute:"))) { //
// Attribute
// XXX: it should be possible to save krita_attributes in the \"CHUNKs\""
dbgFile << "cannot save this annotation : " << (*it) -> type();
} else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) {
dbgFile << "Saving preset information " << (*it)->description();
png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text));
QByteArray keyData = (*it)->description().toLatin1();
text[0].key = keyData.data();
text[0].text = (char*)(*it)->annotation().data();
text[0].text_length = (*it)->annotation().size();
text[0].compression = -1;
png_set_text(png_ptr, info_ptr, text, 1);
png_free(png_ptr, text);
}
it++;
}
// Save the color profile
const KoColorProfile* colorProfile = device->colorSpace()->profile();
QByteArray colorProfileData = colorProfile->rawData();
if (!sRGB || options.saveSRGBProfile) {
#if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5
png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (const png_bytep)colorProfileData.constData(), colorProfileData . size());
#else
png_set_iCCP(png_ptr, info_ptr, (char*)"icc", PNG_COMPRESSION_TYPE_BASE, (char*)colorProfileData.constData(), colorProfileData . size());
#endif
}
// read comments from the document information
// warning: according to the official png spec, the keys need to be capitalised!
if (m_doc) {
png_text texts[3];
int nbtexts = 0;
KoDocumentInfo * info = m_doc->documentInfo();
QString title = info->aboutInfo("title");
if (!title.isEmpty()) {
fillText(texts + nbtexts, "Title", title);
nbtexts++;
}
QString abstract = info->aboutInfo("comment");
if (!abstract.isEmpty()) {
fillText(texts + nbtexts, "Description", abstract);
nbtexts++;
}
QString author = info->authorInfo("creator");
if (!author.isEmpty()) {
fillText(texts + nbtexts, "Author", author);
nbtexts++;
}
png_set_text(png_ptr, info_ptr, texts, nbtexts);
}
// Save metadata following imagemagick way
// Save exif
if (metaData && !metaData->empty()) {
if (options.exif) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif");
Q_ASSERT(exifIO);
QBuffer buffer;
exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
writeRawProfile(png_ptr, info_ptr, "exif", buffer.data());
}
// Save IPTC
if (options.iptc) {
dbgFile << "Trying to save exif information";
KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc");
Q_ASSERT(iptcIO);
QBuffer buffer;
iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader);
dbgFile << "IPTC information size is" << buffer.data().size();
writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data());
}
// Save XMP
if (options.xmp) {
dbgFile << "Trying to save XMP information";
KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp");
Q_ASSERT(xmpIO);
QBuffer buffer;
xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::NoHeader);
dbgFile << "XMP information size is" << buffer.data().size();
writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data());
}
}
#if 0 // Unimplemented?
// Save resolution
int unit_type;
png_uint_32 x_resolution, y_resolution;
#endif
png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points
// Save the information to the file
png_write_info(png_ptr, info_ptr);
png_write_flush(png_ptr);
// swap byteorder on little endian machines.
#ifndef WORDS_BIGENDIAN
if (color_nb_bits > 8)
png_set_swap(png_ptr);
#endif
// Write the PNG
// png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
// Fill the data structure
png_byte** row_pointers = new png_byte*[imageRect.height()];
int row = 0;
for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) {
KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width());
row_pointers[row] = new png_byte[imageRect.width() * device->pixelSize()];
switch (color_type) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
if (color_nb_bits == 16) {
quint16 *dst = reinterpret_cast<quint16 *>(row_pointers[row]);
do {
const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[1];
} while (it->nextPixel());
} else {
quint8 *dst = row_pointers[row];
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[1];
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
if (color_nb_bits == 16) {
quint16 *dst = reinterpret_cast<quint16 *>(row_pointers[row]);
do {
const quint16 *d = reinterpret_cast<const quint16 *>(it->oldRawData());
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[3];
} while (it->nextPixel());
} else {
quint8 *dst = row_pointers[row];
do {
const quint8 *d = it->oldRawData();
*(dst++) = d[2];
*(dst++) = d[1];
*(dst++) = d[0];
if (options.alpha) *(dst++) = d[3];
} while (it->nextPixel());
}
break;
case PNG_COLOR_TYPE_PALETTE: {
quint8 *dst = row_pointers[row];
KisPNGWriteStream writestream(dst, color_nb_bits);
do {
const quint8 *d = it->oldRawData();
int i;
for (i = 0; i < num_palette; i++) {
if (palette[i].red == d[2] &&
palette[i].green == d[1] &&
palette[i].blue == d[0]) {
break;
}
}
writestream.setNextValue(i);
} while (it->nextPixel());
}
break;
default:
delete[] row_pointers;
return KisImageBuilder_RESULT_UNSUPPORTED;
}
}
png_write_image(png_ptr, row_pointers);
// Writing is over
png_write_end(png_ptr, info_ptr);
// Free memory
png_destroy_write_struct(&png_ptr, &info_ptr);
for (int y = 0; y < imageRect.height(); y++) {
delete[] row_pointers[y];
}
delete[] row_pointers;
if (color_type == PNG_COLOR_TYPE_PALETTE) {
delete [] palette;
}
- iodevice->close();
return KisImageBuilder_RESULT_OK;
}
void KisPNGConverter::cancel()
{
m_stop = true;
}
void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass)
{
if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return;
// setProgress(row_number);
}
bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs)
{
return colorSpaceIdSupported(cs->id());
}
diff --git a/libs/ui/kis_script_manager.cpp b/libs/ui/kis_script_manager.cpp
new file mode 100644
index 0000000000..205ae77d75
--- /dev/null
+++ b/libs/ui/kis_script_manager.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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 "kis_script_manager.h"
+
+#include <QMenu>
+
+#include <kactionmenu.h>
+#include <klocalizedstring.h>
+#include <kactionmenu.h>
+#include <kactioncollection.h>
+
+#include <kis_action_manager.h>
+#include "KisViewManager.h"
+
+struct KisScriptManager::Private {
+ Private()
+ : actionCollection(0)
+ , actionManager(0)
+ , view(0)
+ , scriptMenu(0)
+ {
+ }
+
+ KActionCollection *actionCollection;
+ KisActionManager *actionManager;
+ KisViewManager *view;
+ KActionMenu *scriptMenu;
+};
+
+
+KisScriptManager::KisScriptManager(KisViewManager *view)
+ : QObject(view)
+ , d(new Private())
+{
+ d->view = view;
+}
+
+KisScriptManager::~KisScriptManager()
+{
+ delete d;
+}
+
+
+void KisScriptManager::setup(KActionCollection * ac, KisActionManager *actionManager)
+{
+ d->actionCollection = ac;
+ d->actionManager = actionManager;
+
+ d->scriptMenu = new KActionMenu(i18n("Scripts"),this);
+ d->actionCollection->addAction("scripts", d->scriptMenu);
+}
+
+void KisScriptManager::updateGUI()
+{
+ if (!d->view) return;
+}
+
+void KisScriptManager::addAction(QAction *action)
+{
+ Q_ASSERT(d->actionCollection);
+ if (action->property("menu").toString().toLower() == "tools/scripts") {
+ d->scriptMenu->addAction(action);
+ }
+}
diff --git a/libs/ui/kis_script_manager.h b/libs/ui/kis_script_manager.h
new file mode 100644
index 0000000000..f8cf9aa0d9
--- /dev/null
+++ b/libs/ui/kis_script_manager.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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 KIS_SCRIPT_MANAGER_H
+#define KIS_SCRIPT_MANAGER_H
+
+#include <QObject>
+
+#include <kritaui_export.h>
+
+class QAction;
+
+class KisActionManager;
+class KisViewManager;
+class KActionCollection;
+
+/**
+ * @brief The KisScriptManager class is responsible for adding scripts to the menu
+ */
+class KRITAUI_EXPORT KisScriptManager : public QObject
+{
+ Q_OBJECT
+public:
+ explicit KisScriptManager(KisViewManager * view);
+ ~KisScriptManager();
+
+ void setup(KActionCollection * ac, KisActionManager *actionManager);
+ void updateGUI();
+
+ void addAction(QAction *action);
+
+private:
+ struct Private;
+ Private * const d;
+};
+
+#endif // KIS_SCRIPT_MANAGER_H
diff --git a/libs/ui/kis_selection_decoration.cc b/libs/ui/kis_selection_decoration.cc
index 40c186ef96..6f366237c2 100644
--- a/libs/ui/kis_selection_decoration.cc
+++ b/libs/ui/kis_selection_decoration.cc
@@ -1,202 +1,202 @@
/*
* Copyright (c) 2008 Sven Langkamp <sven.langkamp@gmail.com>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_selection_decoration.h"
#include <QPainter>
#include <QVarLengthArray>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include "kis_types.h"
#include "KisViewManager.h"
#include "kis_selection.h"
#include "kis_image.h"
#include "flake/kis_shape_selection.h"
#include "kis_pixel_selection.h"
#include "kis_update_outline_job.h"
#include "kis_selection_manager.h"
#include "canvas/kis_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include "kis_coordinates_converter.h"
#include "kis_config.h"
-#include "krita_utils.h"
+#include "kis_painting_tweaks.h"
#include "KisView.h"
static const unsigned int ANT_LENGTH = 4;
static const unsigned int ANT_SPACE = 4;
static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE;
KisSelectionDecoration::KisSelectionDecoration(QPointer<KisView>view)
: KisCanvasDecoration("selection", view),
m_signalCompressor(500 /*ms*/, KisSignalCompressor::FIRST_INACTIVE),
m_offset(0),
m_mode(Ants)
{
- KritaUtils::initAntsPen(&m_antsPen, &m_outlinePen,
- ANT_LENGTH, ANT_SPACE);
+ KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen,
+ ANT_LENGTH, ANT_SPACE);
m_antsTimer = new QTimer(this);
m_antsTimer->setInterval(150);
m_antsTimer->setSingleShot(false);
connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent()));
connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection()));
}
KisSelectionDecoration::~KisSelectionDecoration()
{
}
KisSelectionDecoration::Mode KisSelectionDecoration::mode() const
{
return m_mode;
}
void KisSelectionDecoration::setMode(Mode mode)
{
m_mode = mode;
selectionChanged();
}
bool KisSelectionDecoration::selectionIsActive()
{
KisImageWSP image = view()->image();
Q_ASSERT(image); Q_UNUSED(image);
KisSelectionSP selection = view()->selection();
return visible() && selection &&
(selection->hasPixelSelection() || selection->hasShapeSelection()) &&
selection->isVisible();
}
void KisSelectionDecoration::selectionChanged()
{
KisSelectionSP selection = view()->selection();
if (selection && selectionIsActive()) {
if ((m_mode == Ants && selection->outlineCacheValid()) ||
(m_mode == Mask && selection->thumbnailImageValid())) {
m_signalCompressor.stop();
if (m_mode == Ants) {
m_outlinePath = selection->outlineCache();
m_antsTimer->start();
} else {
m_thumbnailImage = selection->thumbnailImage();
m_thumbnailImageTransform = selection->thumbnailImageTransform();
}
if (view() && view()->canvasBase()) {
view()->canvasBase()->updateCanvas();
}
} else {
m_signalCompressor.start();
}
} else {
m_signalCompressor.stop();
m_outlinePath = QPainterPath();
m_thumbnailImage = QImage();
m_thumbnailImageTransform = QTransform();
view()->canvasBase()->updateCanvas();
m_antsTimer->stop();
}
}
void KisSelectionDecoration::slotStartUpdateSelection()
{
KisSelectionSP selection = view()->selection();
if (!selection) return;
KisConfig cfg;
QColor maskColor = cfg.selectionOverlayMaskColor();
view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, maskColor));
}
void KisSelectionDecoration::antsAttackEvent()
{
KisSelectionSP selection = view()->selection();
if (!selection) return;
if (selectionIsActive()) {
m_offset = (m_offset + 1) % ANT_ADVANCE_WIDTH;
m_antsPen.setDashOffset(m_offset);
view()->canvasBase()->updateCanvas();
}
}
void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
{
Q_UNUSED(updateRect);
Q_UNUSED(canvas);
if (!selectionIsActive()) return;
if ((m_mode == Ants && m_outlinePath.isEmpty()) ||
(m_mode == Mask && m_thumbnailImage.isNull())) return;
KisConfig cfg;
QTransform transform = converter->imageToWidgetTransform();
gc.save();
gc.setTransform(transform, false);
if (m_mode == Mask) {
gc.setRenderHints(QPainter::SmoothPixmapTransform |
QPainter::HighQualityAntialiasing, false);
gc.setTransform(m_thumbnailImageTransform, true);
gc.drawImage(QPoint(), m_thumbnailImage);
QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds());
QRect r2 = m_thumbnailImage.rect();
QPainterPath p1;
p1.addRect(r1);
QPainterPath p2;
p2.addRect(r2);
QColor maskColor = cfg.selectionOverlayMaskColor();
gc.setBrush(maskColor);
gc.setPen(Qt::NoPen);
gc.drawPath(p1 - p2);
} else /* if (m_mode == Ants) */ {
gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, cfg.antialiasSelectionOutline());
// render selection outline in white
gc.setPen(m_outlinePen);
gc.drawPath(m_outlinePath);
// render marching ants in black (above the white outline)
gc.setPen(m_antsPen);
gc.drawPath(m_outlinePath);
}
gc.restore();
}
void KisSelectionDecoration::setVisible(bool v)
{
KisCanvasDecoration::setVisible(v);
selectionChanged();
}
diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc
index 54ae085a09..ac07af685c 100644
--- a/libs/ui/kis_selection_manager.cc
+++ b/libs/ui/kis_selection_manager.cc
@@ -1,624 +1,609 @@
/*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
*
* The outline algorith 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 "KisDocument.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/KisPasteActionFactory.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "operations/kis_operation_configuration.h"
//new
#include "kis_recorded_path_paint_action.h"
#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_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_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");
m_invert->setOperationID("invertselection");
- actionManager->registerOperation(new KisInvertSelectionOperaton);
+ 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("convert_to_vector_selection");
connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection()));
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(const 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) {
- KoSelection * selection = m_imageView->canvasBase()->globalShapeManager()->selection();
- Q_ASSERT(selection);
- connect(selection, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
+ connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
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(const 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()
- && m_view->canvasBase()->shapeManager()
- && m_view->canvasBase()->shapeManager()->selection()
- && m_view->canvasBase()->shapeManager()->selection()->count()) {
- return m_view->canvasBase()->shapeManager()->selection()->count() > 0;
+ if (m_view && m_view->canvasBase()) {
+ return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0;
}
return false;
}
bool KisSelectionManager::haveShapesInClipboard()
{
- KisShapeLayer *shapeLayer =
- dynamic_cast<KisShapeLayer*>(m_view->activeLayer().data());
-
- if (shapeLayer) {
- const QMimeData* data = QApplication::clipboard()->mimeData();
- if (data) {
- QStringList mimeTypes = m_view->canvasBase()->toolProxy()->supportedPasteMimeTypes();
- Q_FOREACH (const QString & mimeType, mimeTypes) {
- if (data->hasFormat(mimeType)) {
- return true;
- }
- }
- }
- }
- return false;
+ KoSvgPaste paste;
+ return paste.hasShapes();
}
bool KisSelectionManager::havePixelSelectionWithPixels()
{
KisSelectionSP selection = m_view->selection();
if (selection && selection->hasPixelSelection()) {
return !selection->pixelSelection()->selectedRect().isEmpty();
}
return false;
}
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_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(m_view);
+ factory.run(false, m_view);
}
void KisSelectionManager::pasteAt()
{
- //XXX
+ KisPasteActionFactory factory;
+ factory.run(true, 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);
}
void KisSelectionManager::convertToVectorSelection()
{
KisSelectionToVectorActionFactory 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();
- KoShapeStroke* border = new KoShapeStroke(0, Qt::lightGray);
+ 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(0);
+ 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->resourceProvider()->resourceManager(),
KisPainter::StrokeStyleBrush,
KisPainter::FillStyleNone);
Q_FOREACH (KoShape* shape, shapes) {
QTransform matrix = shape->absoluteTransformation(0) * 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->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
bool isVectorLayer = false;
if (currentNode->inherits("KisShapeLayer")) {
isVectorLayer = true;
}
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;
}
diff --git a/libs/ui/kis_selection_manager.h b/libs/ui/kis_selection_manager.h
index 9c640baa24..ea6cb67ee2 100644
--- a/libs/ui/kis_selection_manager.h
+++ b/libs/ui/kis_selection_manager.h
@@ -1,181 +1,171 @@
/*
* 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 <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);
virtual ~KisSelectionManager();
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 cutToNewLayer();
void selectAll();
void deselect();
void invert();
void clear();
void fillForegroundColor();
void fillBackgroundColor();
void fillPattern();
void fillForegroundColorOpacity();
void fillBackgroundColorOpacity();
void fillPatternOpacity();
void reselect();
void convertToVectorSelection();
void convertShapesToVectorSelection();
void convertToShape();
void copySelectionToNewLayer();
void toggleDisplaySelection();
void shapeSelectionChanged();
void imageResizeToSelection();
void paintSelectedShapes();
void slotToggleSelectionDecoration();
void slotStrokeSelection();
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 editabl and has some pixels selected in the pixel selection
+ /// Checks if the current selection is editable and has some pixels selected in the pixel selection
bool havePixelSelectionWithPixels();
- // the following functions are needed for the siox tool
- // they might be also useful on its own
- void erode();
- void dilate();
-
- void paint(QPainter& gc, const KoViewConverter &converter);
-
private:
void fill(const KoColor& color, bool fillWithPattern, const QString& transactionText);
void updateStatusBar();
- void copyFromDevice(KisPaintDeviceSP device);
- void applySelectionFilter(KisSelectionFilter *filter);
-
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_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;
QList<QAction*> m_pluginActions;
QPointer<KisSelectionDecoration> m_selectionDecoration;
};
#endif // KIS_SELECTION_MANAGER_
diff --git a/libs/ui/kis_splash_screen.cpp b/libs/ui/kis_splash_screen.cpp
index a194d44dc5..dc083f6d7c 100644
--- a/libs/ui/kis_splash_screen.cpp
+++ b/libs/ui/kis_splash_screen.cpp
@@ -1,201 +1,200 @@
/*
* Copyright (c) 2014 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_splash_screen.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmap>
#include <QCheckBox>
#include <kis_debug.h>
#include <QFile>
#include <KisPart.h>
#include <KisApplication.h>
#include <kis_icon.h>
#include <klocalizedstring.h>
#include <kconfig.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <QIcon>
KisSplashScreen::KisSplashScreen(const QString &version, const QPixmap &pixmap, bool themed, QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, Qt::SplashScreen | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | f),
m_themed(themed)
{
setupUi(this);
setWindowIcon(KisIconUtils::loadIcon("calligrakrita"));
// Maintain the aspect ratio on high DPI screens when scaling
lblSplash->setPixmap(pixmap);
+ setFixedWidth(pixmap.width());
QString color = colorString();
lblVersion->setText(i18n("Version: %1", version));
lblVersion->setStyleSheet("color:" + color);
bnClose->hide();
connect(bnClose, SIGNAL(clicked()), this, SLOT(close()));
chkShowAtStartup->hide();
connect(chkShowAtStartup, SIGNAL(toggled(bool)), this, SLOT(toggleShowAtStartup(bool)));
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false);
chkShowAtStartup->setChecked(hideSplash);
connect(lblRecent, SIGNAL(linkActivated(QString)), SLOT(linkClicked(QString)));
connect(&m_timer, SIGNAL(timeout()), SLOT(raise()));
// hide these labels by default
lblLinks->setVisible(false);
lblRecent->setVisible(false);
line->setVisible(false);
-
-
m_timer.setSingleShot(true);
m_timer.start(10);
}
void KisSplashScreen::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateText();
}
void KisSplashScreen::updateText()
{
QString color = colorString();
KConfigGroup cfg2( KSharedConfig::openConfig(), "RecentFiles");
int i = 1;
QString recent = i18n("<html>"
"<head/>"
"<body>"
"<p><b><span style=\" color:%1;\">Recent Files</span></b></p>", color);
QString path;
QStringList recentfiles;
QFontMetrics metrics(lblRecent->font());
do {
path = cfg2.readPathEntry(QString("File%1").arg(i), QString());
if (!path.isEmpty()) {
QString name = cfg2.readPathEntry(QString("Name%1").arg(i), QString());
QUrl url(path);
if (name.isEmpty()) {
name = url.fileName();
}
name = metrics.elidedText(name, Qt::ElideMiddle, lblRecent->width());
if (!url.isLocalFile() || QFile::exists(url.toLocalFile())) {
recentfiles.insert(0, QString("<p><a href=\"%1\"><span style=\"color:%3;\">%2</span></a></p>").arg(path).arg(name).arg(color));
}
}
i++;
} while (!path.isEmpty() || i <= 8);
recent += recentfiles.join("\n");
recent += "</body>"
"</html>";
lblRecent->setText(recent);
}
void KisSplashScreen::displayLinks() {
QString color = colorString();
lblLinks->setTextFormat(Qt::RichText);
lblLinks->setText(i18n("<html>"
"<head/>"
"<body>"
"<p><span style=\" color:%1;\"><b>Links</b></span></p>"
"<p><a href=\"https://krita.org/support-us/\"><span style=\" text-decoration: underline; color:%1;\">Support Krita</span></a></p>"
"<p><a href=\"https://docs.krita.org/Category:Getting_Started\"><span style=\" text-decoration: underline; color:%1;\">Getting Started</span></a></p>"
"<p><a href=\"https://docs.krita.org/\"><span style=\" text-decoration: underline; color:%1;\">Manual</span></a></p>"
"<p><a href=\"https://krita.org/\"><span style=\" text-decoration: underline; color:%1;\">Krita Website</span></a></p>"
"<p><a href=\"https://forum.kde.org/viewforum.php?f=136\"><span style=\" text-decoration: underline; color:%1;\">User Community</span></a></p>"
"<p><a href=\"https://quickgit.kde.org/?p=krita.git\"><span style=\" text-decoration: underline; color:%1;\">Source Code</span></a></p>"
"<p><a href=\"https://store.steampowered.com/app/280680/\"><span style=\" text-decoration: underline; color:%1;\">Krita on Steam</span></a></p>"
"</body>"
"</html>", color));
lblLinks->setVisible(true);
updateText();
}
void KisSplashScreen::displayRecentFiles() {
lblRecent->setVisible(true);
line->setVisible(true);
}
QString KisSplashScreen::colorString() const
{
QString color = "#FFFFFF";
if (m_themed && qApp->palette().background().color().value() > 100) {
color = "#000000";
}
return color;
}
void KisSplashScreen::repaint()
{
QWidget::repaint();
QApplication::flush();
}
void KisSplashScreen::show()
{
QRect r(QPoint(), sizeHint());
resize(r.size());
move(QApplication::desktop()->availableGeometry().center() - r.center());
if (isVisible()) {
repaint();
}
m_timer.setSingleShot(true);
m_timer.start(1);
QWidget::show();
}
void KisSplashScreen::toggleShowAtStartup(bool toggle)
{
KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen");
cfg.writeEntry("HideSplashAfterStartup", toggle);
}
void KisSplashScreen::linkClicked(const QString &link)
{
KisPart::instance()->openExistingFile(QUrl::fromLocalFile(link));
if (isTopLevel()) {
close();
}
}
diff --git a/libs/ui/kis_stopgradient_editor.cpp b/libs/ui/kis_stopgradient_editor.cpp
index 9c33bf1f91..b185f040be 100644
--- a/libs/ui/kis_stopgradient_editor.cpp
+++ b/libs/ui/kis_stopgradient_editor.cpp
@@ -1,136 +1,187 @@
/*
* 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 "kis_stopgradient_editor.h"
#include <QPainter>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <KoColorSpace.h>
#include <resources/KoStopGradient.h>
#include "kis_debug.h"
-#include "widgets/kis_gradient_slider_widget.h"
#include <kis_icon_utils.h>
/****************************** KisStopGradientEditor ******************************/
-KisStopGradientEditor::KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption)
- : QWidget(parent), m_gradient(gradient)
+KisStopGradientEditor::KisStopGradientEditor(QWidget *parent)
+ : QWidget(parent),
+ m_gradient(0)
{
- setObjectName(name);
setupUi(this);
- setWindowTitle(caption);
-
- connect(gradientSlider, SIGNAL(sigSelectedStop(int)), this, SLOT(stopChanged(int)));
- gradientSlider->setGradientResource(m_gradient);
- nameedit->setText(gradient->name());
+ connect(gradientSlider, SIGNAL(sigSelectedStop(int)), this, SLOT(stopChanged(int)));
connect(nameedit, SIGNAL(editingFinished()), this, SLOT(nameChanged()));
-
connect(colorButton, SIGNAL(changed(const KoColor&)), SLOT(colorChanged(const KoColor&)));
-
+
+ opacitySlider->setPrefix(i18n("Opacity: "));
opacitySlider->setRange(0.0, 1.0, 2);
connect(opacitySlider, SIGNAL(valueChanged(qreal)), this, SLOT(opacityChanged(qreal)));
-
+
+
buttonReverse->setIcon(KisIconUtils::loadIcon("mirrorAxis-HorizontalMove"));
KisIconUtils::updateIcon(buttonReverse);
- connect(buttonReverse, SIGNAL(pressed()), this, SLOT(reverse()));
-
- stopChanged(gradientSlider->selectedStop());
+ connect(buttonReverse, SIGNAL(pressed()), SLOT(reverse()));
+
+ buttonReverseSecond->setIcon(KisIconUtils::loadIcon("mirrorAxis-HorizontalMove"));
+ KisIconUtils::updateIcon(buttonReverseSecond);
+ connect(buttonReverseSecond, SIGNAL(clicked()), SLOT(reverse()));
+
+ setCompactMode(false);
+
+ setGradient(0);
+ stopChanged(-1);
+}
+
+KisStopGradientEditor::KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption)
+ : KisStopGradientEditor(parent)
+{
+ setObjectName(name);
+ setWindowTitle(caption);
+
+ setGradient(gradient);
}
-void KisStopGradientEditor::activate()
+void KisStopGradientEditor::setCompactMode(bool value)
{
- paramChanged();
+ lblName->setVisible(!value);
+ buttonReverse->setVisible(!value);
+ nameedit->setVisible(!value);
+
+ buttonReverseSecond->setVisible(value);
+}
+
+void KisStopGradientEditor::setGradient(KoStopGradient *gradient)
+{
+ m_gradient = gradient;
+ setEnabled(m_gradient);
+
+ if (m_gradient) {
+ gradientSlider->setGradientResource(m_gradient);
+ nameedit->setText(gradient->name());
+ stopChanged(gradientSlider->selectedStop());
+ }
+
+ emit sigGradientChanged();
+}
+
+void KisStopGradientEditor::notifyGlobalColorChanged(const KoColor &color)
+{
+ if (colorButton->isEnabled() &&
+ color != colorButton->color()) {
+
+ colorButton->setColor(color);
+ }
+}
+
+boost::optional<KoColor> KisStopGradientEditor::currentActiveStopColor() const
+{
+ if (!colorButton->isEnabled()) return boost::none;
+ return colorButton->color();
}
void KisStopGradientEditor::stopChanged(int stop)
{
- KoColor color = m_gradient->stops()[stop].second;
- opacitySlider->setValue(color.opacityF());
+ const bool hasStopSelected = stop >= 0;
+
+ opacitySlider->setEnabled(hasStopSelected);
+ colorButton->setEnabled(hasStopSelected);
+ stopLabel->setEnabled(hasStopSelected);
+
+ if (hasStopSelected) {
+ KoColor color = m_gradient->stops()[stop].second;
+ opacitySlider->setValue(color.opacityF());
- color.setOpacity(1.0);
- colorButton->setColor(color);
-
- paramChanged();
+ color.setOpacity(1.0);
+ colorButton->setColor(color);
+ }
+
+ emit sigGradientChanged();
}
void KisStopGradientEditor::colorChanged(const KoColor& color)
{
QList<KoGradientStop> stops = m_gradient->stops();
int currentStop = gradientSlider->selectedStop();
double t = stops[currentStop].first;
KoColor c(color, stops[currentStop].second.colorSpace());
c.setOpacity(stops[currentStop].second.opacityU8());
stops.removeAt(currentStop);
stops.insert(currentStop, KoGradientStop(t, c));
m_gradient->setStops(stops);
+ gradientSlider->update();
- paramChanged();
+ emit sigGradientChanged();
}
void KisStopGradientEditor::opacityChanged(qreal value)
{
QList<KoGradientStop> stops = m_gradient->stops();
int currentStop = gradientSlider->selectedStop();
double t = stops[currentStop].first;
KoColor c = stops[currentStop].second;
c.setOpacity(value);
stops.removeAt(currentStop);
stops.insert(currentStop, KoGradientStop(t, c));
m_gradient->setStops(stops);
+ gradientSlider->update();
- paramChanged();
+ emit sigGradientChanged();
}
void KisStopGradientEditor::nameChanged()
{
m_gradient->setName(nameedit->text());
-}
-void KisStopGradientEditor::paramChanged()
-{
- m_gradient->updatePreview();
- gradientSlider->update();
+ emit sigGradientChanged();
}
void KisStopGradientEditor::reverse()
{
QList<KoGradientStop> stops = m_gradient->stops();
QList<KoGradientStop> reversedStops;
- for(const KoGradientStop& stop : stops)
- {
+ for(const KoGradientStop& stop : stops) {
reversedStops.push_front(KoGradientStop(1 - stop.first, stop.second));
}
m_gradient->setStops(reversedStops);
- gradientSlider->setSeletectStop(stops.size()-1 -gradientSlider->selectedStop());
- paramChanged();
+ gradientSlider->setSelectedStop(stops.size() - 1 - gradientSlider->selectedStop());
+
+ emit sigGradientChanged();
}
diff --git a/libs/ui/kis_stopgradient_editor.h b/libs/ui/kis_stopgradient_editor.h
index c3017caa1b..65dfb4d335 100644
--- a/libs/ui/kis_stopgradient_editor.h
+++ b/libs/ui/kis_stopgradient_editor.h
@@ -1,46 +1,58 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2016 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_STOPGRADIENT_EDITOR_H_
#define _KIS_STOPGRADIENT_EDITOR_H_
+#include "kritaui_export.h"
#include "ui_wdgstopgradienteditor.h"
+#include <boost/optional.hpp>
class KoStopGradient;
-class KisStopGradientEditor : public QWidget, public Ui::KisWdgStopGradientEditor
+class KRITAUI_EXPORT KisStopGradientEditor : public QWidget, public Ui::KisWdgStopGradientEditor
{
Q_OBJECT
public:
+ KisStopGradientEditor(QWidget *parent);
KisStopGradientEditor(KoStopGradient* gradient, QWidget *parent, const char* name, const QString& caption);
- void activate();
+
+ void setCompactMode(bool value);
+
+ void setGradient(KoStopGradient* gradient);
+
+ void notifyGlobalColorChanged(const KoColor &color);
+
+ boost::optional<KoColor> currentActiveStopColor() const;
+
+Q_SIGNALS:
+ void sigGradientChanged();
+
private:
KoStopGradient* m_gradient;
private Q_SLOTS:
void stopChanged(int stop);
void colorChanged(const KoColor& color);
void opacityChanged(qreal value);
void nameChanged();
void reverse();
-
- void paramChanged();
};
#endif
diff --git a/libs/ui/kis_zoom_manager.cc b/libs/ui/kis_zoom_manager.cc
index fe08dac64e..54d700ee10 100644
--- a/libs/ui/kis_zoom_manager.cc
+++ b/libs/ui/kis_zoom_manager.cc
@@ -1,336 +1,345 @@
/*
* Copyright (C) 2006, 2010 Boudewijn Rempt <boud@valdyas.org>
* Copyright (C) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_zoom_manager.h"
#include <QGridLayout>
#include <kactioncollection.h>
#include <ktoggleaction.h>
#include <ktoggleaction.h>
#include <kis_debug.h>
#include <KisView.h>
#include <KoZoomAction.h>
#include <KoRuler.h>
#include <KoZoomHandler.h>
#include <KoZoomController.h>
#include <KoCanvasControllerWidget.h>
#include <KoRulerController.h>
#include <KoUnit.h>
#include <KoDpi.h>
#include "KisDocument.h"
#include "KisViewManager.h"
#include "canvas/kis_canvas2.h"
#include "kis_coordinates_converter.h"
#include "kis_image.h"
#include "kis_statusbar.h"
#include "kis_config.h"
#include "krita_utils.h"
#include "kis_canvas_resource_provider.h"
#include "kis_lod_transform.h"
#include "kis_snap_line_strategy.h"
class KisZoomController : public KoZoomController
{
public:
KisZoomController(KoCanvasController *co, KisCoordinatesConverter *zh, KActionCollection *actionCollection, KoZoomAction::SpecialButtons specialButtons, QObject *parent)
: KoZoomController(co, zh, actionCollection, specialButtons, parent),
m_converter(zh)
{
}
protected:
QSize documentToViewport(const QSizeF &size) override {
QRectF docRect(QPointF(), size);
return m_converter->documentToWidget(docRect).toRect().size();
}
private:
KisCoordinatesConverter *m_converter;
};
KisZoomManager::KisZoomManager(QPointer<KisView> view, KoZoomHandler * zoomHandler,
KoCanvasController * canvasController)
: m_view(view)
, m_zoomHandler(zoomHandler)
, m_canvasController(canvasController)
, m_horizontalRuler(0)
, m_verticalRuler(0)
, m_zoomAction(0)
, m_zoomActionWidget(0)
{
}
KisZoomManager::~KisZoomManager()
{
if (m_zoomActionWidget && !m_zoomActionWidget->parent()) {
delete m_zoomActionWidget;
}
}
void KisZoomManager::setup(KActionCollection * actionCollection)
{
KisImageWSP image = m_view->image();
if (!image) return;
connect(image, SIGNAL(sigSizeChanged(const QPointF &, const QPointF &)), this, SLOT(setMinMaxZoom()));
KisCoordinatesConverter *converter =
dynamic_cast<KisCoordinatesConverter*>(m_zoomHandler);
m_zoomController = new KisZoomController(m_canvasController, converter, actionCollection, KoZoomAction::AspectMode, this);
m_zoomHandler->setZoomMode(KoZoomMode::ZOOM_PIXELS);
m_zoomHandler->setZoom(1.0);
m_zoomController->setPageSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()));
m_zoomController->setDocumentSize(QSizeF(image->width() / image->xRes(), image->height() / image->yRes()), true);
m_zoomAction = m_zoomController->zoomAction();
setMinMaxZoom();
m_zoomActionWidget = m_zoomAction->createWidget(0);
// Put the canvascontroller in a layout so it resizes with us
QGridLayout * layout = new QGridLayout(m_view);
layout->setSpacing(0);
layout->setMargin(0);
m_view->setLayout(layout);
m_view->document()->setUnit(KoUnit(KoUnit::Pixel));
m_horizontalRuler = new KoRuler(m_view, Qt::Horizontal, m_zoomHandler);
m_horizontalRuler->setShowMousePosition(true);
m_horizontalRuler->createGuideToolConnection(m_view->canvasBase());
m_horizontalRuler->setVisible(false); // this prevents the rulers from flashing on to off when a new document is created
m_verticalRuler = new KoRuler(m_view, Qt::Vertical, m_zoomHandler);
m_verticalRuler->setShowMousePosition(true);
m_verticalRuler->createGuideToolConnection(m_view->canvasBase());
m_verticalRuler->setVisible(false);
QList<QAction*> unitActions = m_view->createChangeUnitActions(true);
m_horizontalRuler->setPopupActionList(unitActions);
m_verticalRuler->setPopupActionList(unitActions);
connect(m_view->document(), SIGNAL(unitChanged(const KoUnit&)), SLOT(applyRulersUnit(const KoUnit&)));
layout->addWidget(m_horizontalRuler, 0, 1);
layout->addWidget(m_verticalRuler, 1, 0);
layout->addWidget(static_cast<KoCanvasControllerWidget*>(m_canvasController), 1, 1);
connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetXChanged(int)),
this, SLOT(pageOffsetChanged()));
connect(m_canvasController->proxyObject, SIGNAL(canvasOffsetYChanged(int)),
this, SLOT(pageOffsetChanged()));
connect(m_zoomController, SIGNAL(zoomChanged(KoZoomMode::Mode, qreal)),
this, SLOT(slotZoomChanged(KoZoomMode::Mode, qreal)));
connect(m_zoomController, SIGNAL(aspectModeChanged(bool)),
this, SLOT(changeAspectMode(bool)));
applyRulersUnit(m_view->document()->unit());
}
void KisZoomManager::updateImageBoundsSnapping()
{
const QRectF docRect = m_view->canvasBase()->coordinatesConverter()->imageRectInDocumentPixels();
const QPointF docCenter = docRect.center();
KoSnapGuide *snapGuide = m_view->canvasBase()->snapGuide();
{
KisSnapLineStrategy *boundsSnap =
new KisSnapLineStrategy(KoSnapGuide::DocumentBoundsSnapping);
boundsSnap->addLine(Qt::Horizontal, docRect.y());
boundsSnap->addLine(Qt::Horizontal, docRect.bottom());
boundsSnap->addLine(Qt::Vertical, docRect.x());
boundsSnap->addLine(Qt::Vertical, docRect.right());
snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentBoundsSnapping, boundsSnap);
}
{
KisSnapLineStrategy *centerSnap =
new KisSnapLineStrategy(KoSnapGuide::DocumentCenterSnapping);
centerSnap->addLine(Qt::Horizontal, docCenter.y());
centerSnap->addLine(Qt::Vertical, docCenter.x());
snapGuide->overrideSnapStrategy(KoSnapGuide::DocumentCenterSnapping, centerSnap);
}
}
void KisZoomManager::updateMouseTrackingConnections()
{
bool value = m_horizontalRuler->isVisible() &&
m_verticalRuler->isVisible() &&
m_horizontalRuler->showMousePosition() &&
m_verticalRuler->showMousePosition();
m_mouseTrackingConnections.clear();
if (value) {
connect(m_canvasController->proxyObject,
SIGNAL(canvasMousePositionChanged(const QPoint &)),
SLOT(mousePositionChanged(const QPoint &)));
}
}
KoRuler* KisZoomManager::horizontalRuler() const
{
return m_horizontalRuler;
}
KoRuler* KisZoomManager::verticalRuler() const
{
return m_verticalRuler;
}
+qreal KisZoomManager::zoom() const
+{
+ qreal zoomX;
+ qreal zoomY;
+ m_zoomHandler->zoom(&zoomX, &zoomY);
+ qDebug() << zoomX << zoomY;
+ return zoomX;
+}
+
void KisZoomManager::mousePositionChanged(const QPoint &viewPos)
{
QPoint pt = viewPos - m_rulersOffset;
m_horizontalRuler->updateMouseCoordinate(pt.x());
m_verticalRuler->updateMouseCoordinate(pt.y());
}
void KisZoomManager::setShowRulers(bool show)
{
m_horizontalRuler->setVisible(show);
m_verticalRuler->setVisible(show);
updateMouseTrackingConnections();
}
void KisZoomManager::setRulersTrackMouse(bool value)
{
m_horizontalRuler->setShowMousePosition(value);
m_verticalRuler->setShowMousePosition(value);
updateMouseTrackingConnections();
}
void KisZoomManager::applyRulersUnit(const KoUnit &baseUnit)
{
if (m_view && m_view->image()) {
m_horizontalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->xRes()));
m_verticalRuler->setUnit(KoUnit(baseUnit.type(), m_view->image()->yRes()));
}
}
void KisZoomManager::setMinMaxZoom()
{
KisImageWSP image = m_view->image();
if (!image) return;
QSize imageSize = image->size();
qreal minDimension = qMin(imageSize.width(), imageSize.height());
qreal minZoom = qMin(100.0 / minDimension, 0.1);
m_zoomAction->setMinimumZoom(minZoom);
m_zoomAction->setMaximumZoom(90.0);
}
void KisZoomManager::updateGUI()
{
QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels();
QSize documentSize = m_view->canvasBase()->viewConverter()->viewToDocument(widgetRect).toAlignedRect().size();
m_horizontalRuler->setRulerLength(documentSize.width());
m_verticalRuler->setRulerLength(documentSize.height());
applyRulersUnit(m_horizontalRuler->unit());
}
QWidget *KisZoomManager::zoomActionWidget() const
{
return m_zoomActionWidget;
}
void KisZoomManager::slotZoomChanged(KoZoomMode::Mode mode, qreal zoom)
{
Q_UNUSED(mode);
Q_UNUSED(zoom);
m_view->canvasBase()->notifyZoomChanged();
qreal humanZoom = zoom * 100.0;
// XXX: KOMVC -- this is very irritating in MDI mode
if (m_view->viewManager()) {
m_view->viewManager()->
showFloatingMessage(
i18nc("floating message about zoom", "Zoom: %1 %",
KritaUtils::prettyFormatReal(humanZoom)),
QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter);
}
const qreal effectiveZoom =
m_view->canvasBase()->coordinatesConverter()->effectiveZoom();
m_view->canvasBase()->resourceManager()->setResource(KisCanvasResourceProvider::EffectiveZoom, effectiveZoom);
}
void KisZoomManager::slotScrollAreaSizeChanged()
{
pageOffsetChanged();
updateGUI();
}
void KisZoomManager::changeAspectMode(bool aspectMode)
{
KisImageWSP image = m_view->image();
KoZoomMode::Mode newMode = KoZoomMode::ZOOM_CONSTANT;
qreal newZoom = m_zoomHandler->zoom();
qreal resolutionX = aspectMode ? image->xRes() : POINT_TO_INCH(static_cast<qreal>(KoDpi::dpiX()));
qreal resolutionY = aspectMode ? image->yRes() : POINT_TO_INCH(static_cast<qreal>(KoDpi::dpiY()));
m_zoomController->setZoom(newMode, newZoom, resolutionX, resolutionY);
m_view->canvasBase()->notifyZoomChanged();
}
void KisZoomManager::pageOffsetChanged()
{
QRectF widgetRect = m_view->canvasBase()->coordinatesConverter()->imageRectInWidgetPixels();
m_rulersOffset = widgetRect.topLeft().toPoint();
m_horizontalRuler->setOffset(m_rulersOffset.x());
m_verticalRuler->setOffset(m_rulersOffset.y());
}
void KisZoomManager::zoomTo100()
{
m_zoomController->setZoom(KoZoomMode::ZOOM_CONSTANT, 1.0);
m_view->canvasBase()->notifyZoomChanged();
}
diff --git a/libs/ui/kis_zoom_manager.h b/libs/ui/kis_zoom_manager.h
index f54b655c0c..f4f2082d21 100644
--- a/libs/ui/kis_zoom_manager.h
+++ b/libs/ui/kis_zoom_manager.h
@@ -1,100 +1,105 @@
/*
* 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_ZOOM_MANAGER
#define KIS_ZOOM_MANAGER
#include <QObject>
#include <QPointer>
#include <klocalizedstring.h>
#include <KoZoomMode.h>
#include <KoZoomAction.h>
#include <KoZoomHandler.h>
#include <KoZoomController.h>
#include "kis_signal_auto_connection.h"
#include "KisView.h"
class KoZoomHandler;
class KoZoomAction;
class KoRuler;
class KoUnit;
class KoCanvasController;
class QPoint;
+#include "kritaui_export.h"
+
/**
- The zoom manager handles all user actions related to zooming
- and unzooming. The actual computation of zoom levels and things
- are the job of KoZoomHandler or its descendants
-*/
-class KisZoomManager : public QObject
+ * The zoom manager handles all user actions related to zooming
+ * and unzooming. The actual computation of zoom levels and things
+ * are the job of KoZoomHandler or its descendants
+ */
+class KRITAUI_EXPORT KisZoomManager : public QObject
{
Q_OBJECT
public:
KisZoomManager(QPointer<KisView> view, KoZoomHandler*, KoCanvasController *);
~KisZoomManager();
void setup(KActionCollection * actionCollection);
void updateGUI();
KoZoomController * zoomController() const {
return m_zoomController;
}
void updateImageBoundsSnapping();
QWidget *zoomActionWidget() const;
KoRuler *horizontalRuler() const;
KoRuler *verticalRuler() const;
+ qreal zoom() const;
+
public Q_SLOTS:
void slotZoomChanged(KoZoomMode::Mode mode, qreal zoom);
void slotScrollAreaSizeChanged();
void setShowRulers(bool show);
void setRulersTrackMouse(bool value);
void mousePositionChanged(const QPoint &viewPos);
void changeAspectMode(bool aspectMode);
void pageOffsetChanged();
void zoomTo100();
void applyRulersUnit(const KoUnit &baseUnit);
void setMinMaxZoom();
+
private:
void updateMouseTrackingConnections();
private:
QPointer<KisView> m_view;
KoZoomHandler * m_zoomHandler;
KoCanvasController *m_canvasController;
KoZoomController *m_zoomController;
KoRuler * m_horizontalRuler;
KoRuler * m_verticalRuler;
KoZoomAction * m_zoomAction;
QPointer<QWidget> m_zoomActionWidget;
QPoint m_rulersOffset;
KisSignalAutoConnectionsStore m_mouseTrackingConnections;
};
#endif
diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp
index 8819ee25d7..18578809eb 100644
--- a/libs/ui/opengl/kis_opengl.cpp
+++ b/libs/ui/opengl/kis_opengl.cpp
@@ -1,225 +1,225 @@
/*
* Copyright (c) 2007 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "opengl/kis_opengl.h"
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QApplication>
#include <QDesktopWidget>
#include <QPixmapCache>
#include <QDir>
#include <QFile>
#include <QDesktopServices>
#include <QMessageBox>
#include <QWindow>
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kis_config.h>
namespace
{
bool initialized = false;
bool NeedsFenceWorkaround = false;
bool NeedsPixmapCacheWorkaround = false;
int glMajorVersion = 0;
int glMinorVersion = 0;
bool supportsDeprecatedFunctions = false;
QString Renderer;
}
void KisOpenGL::initialize()
{
if (initialized) return;
setDefaultFormat();
// we need a QSurface active to get our GL functions from the context
QWindow surface;
surface.setSurfaceType( QSurface::OpenGLSurface );
surface.create();
QOpenGLContext context;
context.create();
if (!context.isValid()) return;
context.makeCurrent( &surface );
QOpenGLFunctions *funcs = context.functions();
funcs->initializeOpenGLFunctions();
+#ifndef GL_RENDERER
+# define GL_RENDERER 0x1F01
+#endif
+ Renderer = QString((const char*)funcs->glGetString(GL_RENDERER));
+ /**
+ * Warn about Intel's broken video drivers
+ */
+#if defined Q_OS_WIN
+ KisConfig cfg;
+ if (cfg.useOpenGL() && Renderer.startsWith("Intel") && !cfg.readEntry("WarnedAboutIntel", false)) {
+ QMessageBox::information(0,
+ i18nc("@title:window", "Krita: Warning"),
+ i18n("You have an Intel(R) HD Graphics video adapter.\n"
+ "If you experience problems like a crash, a black or blank screen,"
+ "please update your display driver to the latest version.\n\n"
+ "If Krita crashes, it will disable OpenGL rendering. Please restart Krita in that case.\n After updating your drivers you can re-enable OpenGL in Krita's Settings.\n"));
+ cfg.writeEntry("WarnedAboutIntel", true);
+ }
+#endif
qDebug() << "OpenGL Info";
qDebug() << " Vendor: " << reinterpret_cast<const char *>(funcs->glGetString(GL_VENDOR));
- qDebug() << " Renderer: " << reinterpret_cast<const char *>(funcs->glGetString(GL_RENDERER));
+ qDebug() << " Renderer: " << Renderer;
qDebug() << " Version: " << reinterpret_cast<const char *>(funcs->glGetString(GL_VERSION));
qDebug() << " Shading language: " << reinterpret_cast<const char *>(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION));
qDebug() << " Requested format: " << QSurfaceFormat::defaultFormat();
qDebug() << " Current format: " << context.format();
-
+
glMajorVersion = context.format().majorVersion();
glMinorVersion = context.format().minorVersion();
supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions);
qDebug() << " Version:" << glMajorVersion << "." << glMinorVersion;
qDebug() << " Supports deprecated functions" << supportsDeprecatedFunctions;
initialized = true;
}
void KisOpenGL::initializeContext(QOpenGLContext *ctx)
{
+ KisConfig cfg;
initialize();
dbgUI << "OpenGL: Opening new context";
// Double check we were given the version we requested
QSurfaceFormat format = ctx->format();
QOpenGLFunctions *f = ctx->functions();
f->initializeOpenGLFunctions();
-#ifndef GL_RENDERER
-# define GL_RENDERER 0x1F01
-#endif
- Renderer = QString((const char*)f->glGetString(GL_RENDERER));
-
QFile log(QDesktopServices::storageLocation(QDesktopServices::TempLocation) + "/krita-opengl.txt");
log.open(QFile::WriteOnly);
QString vendor((const char*)f->glGetString(GL_VENDOR));
log.write(vendor.toLatin1());
log.write(", ");
log.write(Renderer.toLatin1());
log.write(", ");
QString version((const char*)f->glGetString(GL_VERSION));
log.write(version.toLatin1());
log.close();
// Check if we have a bugged driver that needs fence workaround
bool isOnX11 = false;
#ifdef HAVE_X11
isOnX11 = true;
#endif
- KisConfig cfg;
+
if ((isOnX11 && Renderer.startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) {
NeedsFenceWorkaround = true;
}
/**
* NVidia + Qt's openGL don't play well together and one cannot
* draw a pixmap on a widget more than once in one rendering cycle.
*
* It can be workarounded by drawing strictly via QPixmapCache and
* only when the pixmap size in bigger than doubled size of the
* display framebuffer. That is for 8-bit HD display, you should have
* a cache bigger than 16 MiB. Don't ask me why. (DK)
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=361709
*
* TODO: check if this workaround is still needed after merging
* Qt5+openGL3 branch.
*/
if (vendor.toUpper().contains("NVIDIA")) {
NeedsPixmapCacheWorkaround = true;
const QRect screenSize = QApplication::desktop()->screenGeometry();
const int minCacheSize = 20 * 1024;
const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB
QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize));
}
- /**
- * Warn about Intel's broken video drivers
- */
-#if defined Q_OS_WIN
- if (cfg.useOpenGL() && Renderer.startsWith("Intel") && !cfg.readEntry("WarnedAboutIntel", false)) {
- QMessageBox::information(0,
- i18nc("@title:window", "Krita: Warning"),
- i18n("You have an Intel(R) HD Graphics video adapter.\n"
- "If you experience problems like a crash, a black or blank screen,"
- "please update your display driver to the latest version.\n\n"
- "If Krita crashes, it will disable OpenGL rendering. Please restart Krita in that case.\n After updating your drivers you can re-enable OpenGL in Krita's Settings.\n"));
- cfg.writeEntry("WarnedAboutIntel", true);
- }
-#endif
-
}
// XXX Temporary function to allow LoD on OpenGL3 without triggering
// all of the other 3.2 functionality, can be removed once we move to Qt5.7
bool KisOpenGL::supportsLoD()
{
initialize();
return (glMajorVersion * 100 + glMinorVersion) >= 300;
}
bool KisOpenGL::hasOpenGL3()
{
initialize();
return (glMajorVersion * 100 + glMinorVersion) >= 302;
}
bool KisOpenGL::supportsFenceSync()
{
initialize();
return glMajorVersion >= 3;
}
bool KisOpenGL::needsFenceWorkaround()
{
initialize();
return NeedsFenceWorkaround;
}
bool KisOpenGL::needsPixmapCacheWorkaround()
{
initialize();
return NeedsPixmapCacheWorkaround;
}
void KisOpenGL::setDefaultFormat()
{
QSurfaceFormat format;
#ifdef Q_OS_OSX
format.setVersion(3, 2);
format.setProfile(QSurfaceFormat::CoreProfile);
#else
// XXX This can be removed once we move to Qt5.7
format.setVersion(3, 0);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOptions(QSurfaceFormat::DeprecatedFunctions);
#endif
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
format.setSwapInterval(0); // Disable vertical refresh syncing
QSurfaceFormat::setDefaultFormat(format);
}
bool KisOpenGL::hasOpenGL()
{
return ((glMajorVersion * 100 + glMinorVersion) >= 201);
//return (glMajorVersion >= 3 && supportsDeprecatedFunctions);
}
diff --git a/libs/ui/operations/kis_operation_registry.cpp b/libs/ui/operations/kis_operation_registry.cpp
index 9b5d109dc3..c351e840e4 100644
--- a/libs/ui/operations/kis_operation_registry.cpp
+++ b/libs/ui/operations/kis_operation_registry.cpp
@@ -1,52 +1,53 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
* 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_operation_registry.h"
#include <QGlobalStatic>
#include "actions/kis_selection_action_factories.h"
+#include "actions/KisPasteActionFactory.h"
Q_GLOBAL_STATIC(KisOperationRegistry, s_instance)
KisOperationRegistry* KisOperationRegistry::instance()
{
return s_instance;
}
KisOperationRegistry::KisOperationRegistry()
{
add(new KisSelectAllActionFactory);
add(new KisDeselectActionFactory);
add(new KisReselectActionFactory);
add(new KisFillActionFactory);
add(new KisClearActionFactory);
add(new KisImageResizeToSelectionActionFactory);
add(new KisCutCopyActionFactory);
add(new KisCopyMergedActionFactory);
add(new KisPasteActionFactory);
add(new KisPasteNewActionFactory);
}
KisOperationRegistry::~KisOperationRegistry()
{
Q_FOREACH (const QString &id, keys()) {
delete get(id);
}
}
diff --git a/libs/ui/tests/CMakeLists.txt b/libs/ui/tests/CMakeLists.txt
index 62b3fb2edb..7d110b286e 100644
--- a/libs/ui/tests/CMakeLists.txt
+++ b/libs/ui/tests/CMakeLists.txt
@@ -1,175 +1,177 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
#add_subdirectory(scratchpad)
include_directories(${CMAKE_SOURCE_DIR}/libs/image/metadata
${CMAKE_SOURCE_DIR}/sdk/tests )
include(ECMAddTests)
macro_add_unittest_definitions()
ecm_add_tests(
kis_image_view_converter_test.cpp
squeezedcombobox_test.cpp
kis_shape_selection_test.cpp
kis_recorded_action_editor_test.cpp
kis_doc2_test.cpp
kis_coordinates_converter_test.cpp
kis_grid_config_test.cpp
kis_stabilized_events_sampler_test.cpp
kis_derived_resources_test.cpp
kis_brush_hud_properties_config_test.cpp
+ kis_shape_commands_test.cpp
+ kis_stop_gradient_editor_test.cpp
NAME_PREFIX "krita-ui-"
LINK_LIBRARIES kritaui Qt5::Test
)
ecm_add_tests(
kis_file_layer_test.cpp
kis_multinode_property_test.cpp
NAME_PREFIX "krita-ui-"
LINK_LIBRARIES kritaui kritaimage Qt5::Test
)
ecm_add_test( kis_selection_decoration_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-KisSelectionDecorationTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_node_dummies_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisNodeDummiesGraphTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_node_shapes_graph_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisNodeShapesGraphTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_model_index_converter_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisModelIndexConverterTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_categorized_list_model_test.cpp modeltest.cpp
TEST_NAME krita-ui-KisCategorizedListModelTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_resource_server_provider_test.cpp modeltest.cpp
TEST_NAME krita-ui-KisResourceServerProviderTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
ecm_add_test( kis_node_juggler_compressed_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-image-BaseNodeTest
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
set(kis_node_view_test_SRCS kis_node_view_test.cpp ../../../sdk/tests/testutil.cpp)
qt5_add_resources(kis_node_view_test_SRCS ${krita_QRCS})
ecm_add_test(${kis_node_view_test_SRCS}
TEST_NAME krita-image-kis_node_view_test
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
##### Tests that currently fail and should be fixed #####
include(KritaAddBrokenUnitTest)
krita_add_broken_unit_test(
kis_node_model_test.cpp modeltest.cpp
TEST_NAME krita-ui-kis_node_model_test
LINK_LIBRARIES kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_shape_controller_test.cpp kis_dummies_facade_base_test.cpp
TEST_NAME krita-ui-kis_shape_controller_test
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_prescaled_projection_test.cpp
TEST_NAME krita-ui-kis_prescaled_projection_test
LINK_LIBRARIES kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_exiv2_test.cpp
TEST_NAME krita-ui-KisExiv2Test
LINK_LIBRARIES kritaimage kritaui Qt5::Test)
krita_add_broken_unit_test(
kis_clipboard_test.cpp
TEST_NAME krita-ui-KisClipboardTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
freehand_stroke_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-FreehandStrokeTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
fill_processing_visitor_test.cpp ${CMAKE_SOURCE_DIR}/sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-FillProcessingVisitorTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
filter_stroke_test.cpp ../../../sdk/tests/stroke_testing_utils.cpp
TEST_NAME krita-ui-FilterStrokeTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_selection_manager_test.cpp
TEST_NAME krita-ui-KisSelectionManagerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
#set_tests_properties(krita-ui-KisSelectionManagerTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test(
kis_node_manager_test.cpp
TEST_NAME krita-ui-KisNodeManagerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_dummies_facade_test.cpp kis_dummies_facade_base_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisDummiesFacadeTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_zoom_and_pan_test.cpp ../../../sdk/tests/testutil.cpp
TEST_NAME krita-ui-KisZoomAndPanTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
#set_tests_properties(krita-ui-KisZoomAndPanTest PROPERTIES TIMEOUT 300)
krita_add_broken_unit_test(
kis_action_manager_test.cpp
TEST_NAME krita-ui-KisActionManagerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_categories_mapper_test.cpp testing_categories_mapper.cpp
TEST_NAME krita-ui-KisCategoriesMapperTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_asl_layer_style_serializer_test.cpp
TEST_NAME krita-ui-KisAslLayerStyleSerializerTest
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_animation_exporter_test.cpp
TEST_NAME kritaui-animation_exporter_test
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_animation_importer_test.cpp
TEST_NAME kritaui-animation_importer_test
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
kis_animation_frame_cache_test.cpp
TEST_NAME kritaui-animation_frame_cache_test
LINK_LIBRARIES kritaui kritaimage Qt5::Test)
krita_add_broken_unit_test(
ResourceBundleTest.cpp
TEST_NAME krita-resourcemanager-ResourceBundleTest
LINK_LIBRARIES kritaui kritalibbrush kritalibpaintop Qt5::Test)
# FIXME this test doesn't compile
#ecm_add_test(
# kis_input_manager_test.cpp ../../../sdk/tests/testutil.cpp
# TEST_NAME krita-ui-KisInputManagerTest
# LINK_LIBRARIES kritaui kritaimage Qt5::Test)
diff --git a/libs/ui/tests/kis_animation_exporter_test.cpp b/libs/ui/tests/kis_animation_exporter_test.cpp
index 81d5dd14e2..f2e2a9751a 100644
--- a/libs/ui/tests/kis_animation_exporter_test.cpp
+++ b/libs/ui/tests/kis_animation_exporter_test.cpp
@@ -1,99 +1,99 @@
/*
* Copyright (c) 2015 Jouni Pentikäinen <joupent@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_animation_exporter_test.h"
#include "kis_animation_exporter.h"
#include <QTest>
#include <testutil.h>
#include "KisPart.h"
#include "kis_image.h"
#include "KisDocument.h"
#include "kis_image_animation_interface.h"
#include "KoColor.h"
#include "kis_time_range.h"
#include "kis_keyframe_channel.h"
void KisAnimationExporterTest::testAnimationExport()
{
KisDocument *document = KisPart::instance()->createDocument();
QRect rect(0,0,512,512);
QRect fillRect(10,0,502,512);
TestUtil::MaskParent p(rect);
document->setCurrentImage(p.image);
const KoColorSpace *cs = p.image->colorSpace();
KUndo2Command parentCommand;
p.layer->enableAnimation();
- KisKeyframeChannel *rasterChannel = p.layer->getKeyframeChannel(KisKeyframeChannel::Content.id());
+ KisKeyframeChannel *rasterChannel = p.layer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
rasterChannel->addKeyframe(1, &parentCommand);
rasterChannel->addKeyframe(2, &parentCommand);
p.image->animationInterface()->setFullClipRange(KisTimeRange::fromTime(0, 2));
KisPaintDeviceSP dev = p.layer->paintDevice();
dev->fill(fillRect, KoColor(Qt::red, cs));
QImage frame0 = dev->convertToQImage(0, rect);
p.image->animationInterface()->switchCurrentTimeAsync(1);
p.image->waitForDone();
dev->fill(fillRect, KoColor(Qt::green, cs));
QImage frame1 = dev->convertToQImage(0, rect);
p.image->animationInterface()->switchCurrentTimeAsync(2);
p.image->waitForDone();
dev->fill(fillRect, KoColor(Qt::blue, cs));
QImage frame2 = dev->convertToQImage(0, rect);
KisAnimationExportSaver exporter(document, "export-test.png", 0, 2);
QSignalSpy spy(document, SIGNAL(sigProgress(int)));
QVERIFY(spy.isValid());
exporter.exportAnimation();
// If we had Qt5 already:
// spy.wait(100);
// spy.wait(100);
// spy.wait(100);
// But in the meanwhile...
QTest::qWait(1000);
//QCOMPARE(spy.count(), 3);
//QCOMPARE(spy.at(0).at(0).value<int>(), 0);
//QCOMPARE(spy.at(1).at(0).value<int>(), 1);
//QCOMPARE(spy.at(2).at(0).value<int>(), 2);
// FIXME: Export doesn't seem to work from unit tests
QImage exported;
exported.load("export-test0000.png");
QCOMPARE(exported, frame0);
exported.load("export-test0001.png");
QCOMPARE(exported, frame1);
exported.load("export-test0002.png");
QCOMPARE(exported, frame2);
}
QTEST_MAIN(KisAnimationExporterTest)
diff --git a/libs/ui/tests/kis_shape_commands_test.cpp b/libs/ui/tests/kis_shape_commands_test.cpp
new file mode 100644
index 0000000000..03ca57c682
--- /dev/null
+++ b/libs/ui/tests/kis_shape_commands_test.cpp
@@ -0,0 +1,229 @@
+/*
+ * 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_shape_commands_test.h"
+
+#include <QTest>
+
+#include "kis_global.h"
+
+#include "kis_shape_layer.h"
+#include <KoPathShape.h>
+#include <KoColorBackground.h>
+#include "testutil.h"
+
+#include <KisPart.h>
+#include <KisDocument.h>
+
+#include <KoShapeGroup.h>
+#include <KoShapeGroupCommand.h>
+
+
+void KisShapeCommandsTest::testGrouping()
+{
+ TestUtil::ExternalImageChecker chk("grouping", "shape_commands_test");
+
+ QRect refRect(0,0,64,64);
+
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+ TestUtil::MaskParent p(refRect);
+
+ const qreal resolution = 72.0 / 72.0;
+ p.image->setResolution(resolution, resolution);
+
+ doc->setCurrentImage(p.image);
+
+
+ KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75);
+
+ {
+ KoPathShape* path = new KoPathShape();
+ path->setShapeId(KoPathShapeId);
+ path->moveTo(QPointF(5, 5));
+ path->lineTo(QPointF(5, 55));
+ path->lineTo(QPointF(55, 55));
+ path->lineTo(QPointF(55, 5));
+ path->close();
+ path->normalize();
+ path->setBackground(toQShared(new KoColorBackground(Qt::red)));
+
+ path->setName("shape1");
+ path->setZIndex(1);
+ shapeLayer->addShape(path);
+ }
+
+ {
+ KoPathShape* path = new KoPathShape();
+ path->setShapeId(KoPathShapeId);
+ path->moveTo(QPointF(30, 30));
+ path->lineTo(QPointF(30, 60));
+ path->lineTo(QPointF(60, 60));
+ path->lineTo(QPointF(60, 30));
+ path->close();
+ path->normalize();
+ path->setBackground(toQShared(new KoColorBackground(Qt::green)));
+
+ path->setName("shape2");
+ path->setZIndex(2);
+ shapeLayer->addShape(path);
+ }
+
+ p.image->addNode(shapeLayer);
+
+ shapeLayer->setDirty();
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_layer_update");
+
+ QList<KoShape*> shapes = shapeLayer->shapes();
+
+ KoShapeGroup *group = new KoShapeGroup();
+ group->setName("group_shape");
+ shapeLayer->addShape(group);
+
+ QScopedPointer<KoShapeGroupCommand> cmd(
+ new KoShapeGroupCommand(group, shapes, false, true, true));
+
+ cmd->redo();
+
+ shapeLayer->setDirty();
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_layer_update");
+
+ cmd->undo();
+
+ shapeLayer->setDirty();
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_layer_update");
+
+ QVERIFY(chk.testPassed());
+
+}
+
+void KisShapeCommandsTest::testResizeShape(bool normalizeGroup)
+{
+ TestUtil::ExternalImageChecker chk("resize_shape", "shape_commands_test");
+
+ QRect refRect(0,0,64,64);
+
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+ TestUtil::MaskParent p(refRect);
+
+ const qreal resolution = 72.0 / 72.0;
+ p.image->setResolution(resolution, resolution);
+
+ doc->setCurrentImage(p.image);
+
+ KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75);
+
+ {
+ KoPathShape* path = new KoPathShape();
+ path->setShapeId(KoPathShapeId);
+ path->moveTo(QPointF(5, 5));
+ path->lineTo(QPointF(5, 55));
+ path->lineTo(QPointF(55, 55));
+ path->lineTo(QPointF(55, 5));
+ path->close();
+ path->normalize();
+ path->setBackground(toQShared(new KoColorBackground(Qt::red)));
+
+ path->setName("shape1");
+ path->setZIndex(1);
+ shapeLayer->addShape(path);
+ }
+
+ {
+ KoPathShape* path = new KoPathShape();
+ path->setShapeId(KoPathShapeId);
+ path->moveTo(QPointF(30, 30));
+ path->lineTo(QPointF(30, 60));
+ path->lineTo(QPointF(60, 60));
+ path->lineTo(QPointF(60, 30));
+ path->close();
+ path->normalize();
+ path->setBackground(toQShared(new KoColorBackground(Qt::green)));
+
+ path->setName("shape2");
+ path->setZIndex(2);
+ shapeLayer->addShape(path);
+ }
+
+ p.image->addNode(shapeLayer);
+
+ shapeLayer->setDirty();
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_layer_update");
+
+ QList<KoShape*> shapes = shapeLayer->shapes();
+
+ KoShapeGroup *group = new KoShapeGroup();
+ group->setName("group_shape");
+ shapeLayer->addShape(group);
+
+ QScopedPointer<KoShapeGroupCommand> cmd(
+ new KoShapeGroupCommand(group, shapes, false, true, normalizeGroup));
+
+ cmd->redo();
+
+ shapeLayer->setDirty();
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_layer_update");
+
+ qDebug() << "Before:";
+ qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeft));
+ qDebug() << ppVar(group->absolutePosition(KoFlake::BottomRight));
+ qDebug() << ppVar(group->outlineRect());
+ qDebug() << ppVar(group->transformation());
+
+ QCOMPARE(group->absolutePosition(KoFlake::TopLeft), QPointF(5,5));
+ QCOMPARE(group->absolutePosition(KoFlake::BottomRight), QPointF(60,60));
+
+
+ const QPointF stillPoint = group->absolutePosition(KoFlake::BottomRight);
+ KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, false, true, QTransform());
+
+ qDebug() << "After:";
+ qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeft));
+ qDebug() << ppVar(group->absolutePosition(KoFlake::BottomRight));
+ qDebug() << ppVar(group->outlineRect());
+ qDebug() << ppVar(group->transformation());
+
+ QCOMPARE(group->absolutePosition(KoFlake::TopLeft), QPointF(-6,-17));
+ QCOMPARE(group->absolutePosition(KoFlake::BottomRight), QPointF(60,60));
+}
+
+void KisShapeCommandsTest::testResizeShape()
+{
+ testResizeShape(false);
+}
+
+void KisShapeCommandsTest::testResizeShapeNormalized()
+{
+ testResizeShape(true);
+}
+
+QTEST_MAIN(KisShapeCommandsTest)
diff --git a/libs/ui/tests/kis_shape_commands_test.h b/libs/ui/tests/kis_shape_commands_test.h
new file mode 100644
index 0000000000..7836429ba1
--- /dev/null
+++ b/libs/ui/tests/kis_shape_commands_test.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_SHAPE_COMMANDS_TEST_H
+#define __KIS_SHAPE_COMMANDS_TEST_H
+
+#include <QtTest>
+
+class KisShapeCommandsTest : public QObject
+{
+ Q_OBJECT
+ void testResizeShape(bool normalizeGroup);
+
+private Q_SLOTS:
+ void testGrouping();
+ void testResizeShape();
+ void testResizeShapeNormalized();
+};
+
+#endif /* __KIS_SHAPE_COMMANDS_TEST_H */
diff --git a/libs/ui/tests/kis_stop_gradient_editor_test.cpp b/libs/ui/tests/kis_stop_gradient_editor_test.cpp
new file mode 100644
index 0000000000..4d332ad294
--- /dev/null
+++ b/libs/ui/tests/kis_stop_gradient_editor_test.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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_stop_gradient_editor_test.h"
+
+#include <QTest>
+#include <QDialog>
+#include <QVBoxLayout>
+#include <QLinearGradient>
+
+#include "kis_debug.h"
+#include "kis_stopgradient_editor.h"
+
+
+void KisStopGradientEditorTest::test()
+{
+ QLinearGradient gradient;
+ QScopedPointer<KoStopGradient> koGradient(KoStopGradient::fromQGradient(&gradient));
+ QDialog dlg;
+
+ KisStopGradientEditor *widget = new KisStopGradientEditor(&dlg);
+ widget->setGradient(koGradient.data());
+
+ QVBoxLayout *layout = new QVBoxLayout(&dlg);
+ layout->setContentsMargins(0,0,0,0);
+
+ layout->addWidget(widget);
+ dlg.setLayout(layout);
+ dlg.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ //dlg.exec();
+ qWarning() << "WARNING: showing of the dialogs in the unittest is disabled!";
+}
+
+QTEST_MAIN(KisStopGradientEditorTest)
diff --git a/libs/ui/tests/kis_stop_gradient_editor_test.h b/libs/ui/tests/kis_stop_gradient_editor_test.h
new file mode 100644
index 0000000000..b3f2ba7499
--- /dev/null
+++ b/libs/ui/tests/kis_stop_gradient_editor_test.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_STOP_GRADIENT_EDITOR_TEST_H
+#define __KIS_STOP_GRADIENT_EDITOR_TEST_H
+
+#include <QtTest/QtTest>
+
+class KisStopGradientEditorTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+
+ void test();
+};
+
+#endif /* __KIS_STOP_GRADIENT_EDITOR_TEST_H */
diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp
index 8265f56562..765a0a05f6 100644
--- a/libs/ui/tool/kis_resources_snapshot.cpp
+++ b/libs/ui/tool/kis_resources_snapshot.cpp
@@ -1,356 +1,389 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_resources_snapshot.h"
#include <KoColor.h>
#include <resources/KoAbstractGradient.h>
#include <KoCompositeOpRegistry.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_registry.h>
#include <kis_threaded_text_rendering_workaround.h>
#include <resources/KoPattern.h>
#include "kis_canvas_resource_provider.h"
#include "filter/kis_filter_configuration.h"
#include "kis_image.h"
#include "kis_paint_device.h"
#include "kis_paint_layer.h"
#include "recorder/kis_recorded_paint_action.h"
#include "kis_selection.h"
#include "kis_selection_mask.h"
#include "kis_algebra_2d.h"
struct KisResourcesSnapshot::Private {
Private()
: currentPattern(0)
, currentGradient(0)
, currentGenerator(0)
, compositeOp(0)
{
}
KisImageSP image;
KisDefaultBoundsBaseSP bounds;
KoColor currentFgColor;
KoColor currentBgColor;
KoPattern *currentPattern;
KoAbstractGradient *currentGradient;
KisPaintOpPresetSP currentPaintOpPreset;
KisNodeSP currentNode;
qreal currentExposure;
KisFilterConfigurationSP currentGenerator;
QPointF axesCenter;
bool mirrorMaskHorizontal;
bool mirrorMaskVertical;
quint8 opacity;
QString compositeOpId;
const KoCompositeOp *compositeOp;
KisPainter::StrokeStyle strokeStyle;
KisPainter::FillStyle fillStyle;
bool globalAlphaLock;
qreal effectiveZoom;
bool presetAllowsLod;
KisSelectionSP selectionOverride;
};
KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds)
: m_d(new Private())
{
m_d->image = image;
if (!bounds) {
bounds = new KisDefaultBounds(m_d->image);
}
m_d->bounds = bounds;
m_d->currentFgColor = resourceManager->resource(KoCanvasResourceManager::ForegroundColor).value<KoColor>();
m_d->currentBgColor = resourceManager->resource(KoCanvasResourceManager::BackgroundColor).value<KoColor>();
m_d->currentPattern = resourceManager->resource(KisCanvasResourceProvider::CurrentPattern).value<KoPattern*>();
m_d->currentGradient = resourceManager->resource(KisCanvasResourceProvider::CurrentGradient).value<KoAbstractGradient*>();
/**
* We should deep-copy the preset, so that long-runnign actions
* will have correct brush parameters. Theoretically this cloniong
* can be expensive, but according to measurements, it takes
* something like 0.1 ms for an average preset.
*/
m_d->currentPaintOpPreset = resourceManager->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>()->clone();
#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset);
#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
m_d->currentExposure = resourceManager->resource(KisCanvasResourceProvider::HdrExposure).toDouble();
m_d->currentGenerator = resourceManager->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value<KisFilterConfiguration*>();
QPointF relativeAxesCenter(0.5, 0.5);
if (m_d->image) {
relativeAxesCenter = m_d->image->mirrorAxesCenter();
}
m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds());
m_d->mirrorMaskHorizontal = resourceManager->resource(KisCanvasResourceProvider::MirrorHorizontal).toBool();
m_d->mirrorMaskVertical = resourceManager->resource(KisCanvasResourceProvider::MirrorVertical).toBool();
qreal normOpacity = resourceManager->resource(KisCanvasResourceProvider::Opacity).toDouble();
m_d->opacity = quint8(normOpacity * OPACITY_OPAQUE_U8);
m_d->compositeOpId = resourceManager->resource(KisCanvasResourceProvider::CurrentEffectiveCompositeOp).toString();
setCurrentNode(currentNode);
/**
* Fill and Stroke styles are not a part of the resource manager
* so the tools should set them manually
* TODO: port stroke and fill styles to be a part
* of the resource manager
*/
m_d->strokeStyle = KisPainter::StrokeStyleBrush;
m_d->fillStyle = KisPainter::FillStyleNone;
m_d->globalAlphaLock = resourceManager->resource(KisCanvasResourceProvider::GlobalAlphaLock).toBool();
m_d->effectiveZoom = resourceManager->resource(KisCanvasResourceProvider::EffectiveZoom).toDouble();
m_d->presetAllowsLod = resourceManager->resource(KisCanvasResourceProvider::PresetAllowsLod).toBool();
}
+KisResourcesSnapshot::KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds)
+ : m_d(new Private())
+{
+ m_d->image = image;
+ if (!bounds) {
+ bounds = new KisDefaultBounds(m_d->image);
+ }
+ m_d->bounds = bounds;
+
+#ifdef HAVE_THREADED_TEXT_RENDERING_WORKAROUND
+ KisPaintOpRegistry::instance()->preinitializePaintOpIfNeeded(m_d->currentPaintOpPreset);
+#endif /* HAVE_THREADED_TEXT_RENDERING_WORKAROUND */
+
+ QPointF relativeAxesCenter(0.5, 0.5);
+ if (m_d->image) {
+ relativeAxesCenter = m_d->image->mirrorAxesCenter();
+ }
+ m_d->axesCenter = KisAlgebra2D::relativeToAbsolute(relativeAxesCenter, m_d->bounds->bounds());
+ m_d->opacity = OPACITY_OPAQUE_U8;
+
+ setCurrentNode(currentNode);
+
+ /**
+ * Fill and Stroke styles are not a part of the resource manager
+ * so the tools should set them manually
+ * TODO: port stroke and fill styles to be a part
+ * of the resource manager
+ */
+ m_d->strokeStyle = KisPainter::StrokeStyleBrush;
+ m_d->fillStyle = KisPainter::FillStyleNone;
+}
+
+
KisResourcesSnapshot::~KisResourcesSnapshot()
{
delete m_d;
}
void KisResourcesSnapshot::setupPainter(KisPainter* painter)
{
painter->setPaintColor(m_d->currentFgColor);
painter->setBackgroundColor(m_d->currentBgColor);
painter->setGenerator(m_d->currentGenerator);
painter->setPattern(m_d->currentPattern);
painter->setGradient(m_d->currentGradient);
QBitArray lockflags = channelLockFlags();
if (lockflags.size() > 0) {
painter->setChannelFlags(lockflags);
}
painter->setOpacity(m_d->opacity);
painter->setCompositeOp(m_d->compositeOp);
painter->setMirrorInformation(m_d->axesCenter, m_d->mirrorMaskHorizontal, m_d->mirrorMaskVertical);
painter->setStrokeStyle(m_d->strokeStyle);
painter->setFillStyle(m_d->fillStyle);
/**
* The paintOp should be initialized the last, because it may
* ask the painter for some options while initialization
*/
painter->setPaintOpPreset(m_d->currentPaintOpPreset, m_d->currentNode, m_d->image);
}
void KisResourcesSnapshot::setupPaintAction(KisRecordedPaintAction *action)
{
action->setPaintOpPreset(m_d->currentPaintOpPreset);
action->setPaintIncremental(!needsIndirectPainting());
action->setPaintColor(m_d->currentFgColor);
action->setBackgroundColor(m_d->currentBgColor);
action->setGenerator(m_d->currentGenerator);
action->setGradient(m_d->currentGradient);
action->setPattern(m_d->currentPattern);
action->setOpacity(m_d->opacity / qreal(OPACITY_OPAQUE_U8));
action->setCompositeOp(m_d->compositeOp->id());
action->setStrokeStyle(m_d->strokeStyle);
action->setFillStyle(m_d->fillStyle);
}
KisPostExecutionUndoAdapter* KisResourcesSnapshot::postExecutionUndoAdapter() const
{
return m_d->image ? m_d->image->postExecutionUndoAdapter() : 0;
}
void KisResourcesSnapshot::setCurrentNode(KisNodeSP node)
{
m_d->currentNode = node;
KisPaintDeviceSP device;
if(m_d->currentNode && (device = m_d->currentNode->paintDevice())) {
m_d->compositeOp = device->colorSpace()->compositeOp(m_d->compositeOpId);
if(!m_d->compositeOp) {
m_d->compositeOp = device->colorSpace()->compositeOp(COMPOSITE_OVER);
}
}
}
void KisResourcesSnapshot::setStrokeStyle(KisPainter::StrokeStyle strokeStyle)
{
m_d->strokeStyle = strokeStyle;
}
void KisResourcesSnapshot::setFillStyle(KisPainter::FillStyle fillStyle)
{
m_d->fillStyle = fillStyle;
}
KisNodeSP KisResourcesSnapshot::currentNode() const
{
return m_d->currentNode;
}
KisImageSP KisResourcesSnapshot::image() const
{
return m_d->image;
}
bool KisResourcesSnapshot::needsIndirectPainting() const
{
return !m_d->currentPaintOpPreset->settings()->paintIncremental();
}
QString KisResourcesSnapshot::indirectPaintingCompositeOp() const
{
return m_d->currentPaintOpPreset->settings()->indirectPaintingCompositeOp();
}
KisSelectionSP KisResourcesSnapshot::activeSelection() const
{
/**
* It is possible to have/use the snapshot without the image. Such
* usecase is present for example in the scratchpad.
*/
if (m_d->selectionOverride) {
return m_d->selectionOverride;
}
KisSelectionSP selection = m_d->image ? m_d->image->globalSelection() : 0;
KisLayerSP layer = qobject_cast<KisLayer*>(m_d->currentNode.data());
KisSelectionMaskSP mask;
if((layer = qobject_cast<KisLayer*>(m_d->currentNode.data()))) {
selection = layer->selection();
} else if ((mask = dynamic_cast<KisSelectionMask*>(m_d->currentNode.data())) &&
mask->selection() == selection) {
selection = 0;
}
return selection;
}
bool KisResourcesSnapshot::needsAirbrushing() const
{
return m_d->currentPaintOpPreset->settings()->isAirbrushing();
}
int KisResourcesSnapshot::airbrushingRate() const
{
return m_d->currentPaintOpPreset->settings()->rate();
}
void KisResourcesSnapshot::setOpacity(qreal opacity)
{
m_d->opacity = opacity * OPACITY_OPAQUE_U8;
}
quint8 KisResourcesSnapshot::opacity() const
{
return m_d->opacity;
}
const KoCompositeOp* KisResourcesSnapshot::compositeOp() const
{
return m_d->compositeOp;
}
QString KisResourcesSnapshot::compositeOpId() const
{
return m_d->compositeOpId;
}
KoPattern* KisResourcesSnapshot::currentPattern() const
{
return m_d->currentPattern;
}
KoColor KisResourcesSnapshot::currentFgColor() const
{
return m_d->currentFgColor;
}
KoColor KisResourcesSnapshot::currentBgColor() const
{
return m_d->currentBgColor;
}
KisPaintOpPresetSP KisResourcesSnapshot::currentPaintOpPreset() const
{
return m_d->currentPaintOpPreset;
}
QBitArray KisResourcesSnapshot::channelLockFlags() const
{
QBitArray channelFlags;
KisPaintLayer *paintLayer;
if ((paintLayer = dynamic_cast<KisPaintLayer*>(m_d->currentNode.data()))) {
channelFlags = paintLayer->channelLockFlags();
if (m_d->globalAlphaLock) {
if (channelFlags.isEmpty()) {
channelFlags = paintLayer->colorSpace()->channelFlags(true, true);
}
channelFlags &= paintLayer->colorSpace()->channelFlags(true, false);
}
}
return channelFlags;
}
qreal KisResourcesSnapshot::effectiveZoom() const
{
return m_d->effectiveZoom;
}
bool KisResourcesSnapshot::presetAllowsLod() const
{
return m_d->presetAllowsLod;
}
void KisResourcesSnapshot::setFGColorOverride(const KoColor &color)
{
m_d->currentFgColor = color;
}
void KisResourcesSnapshot::setBGColorOverride(const KoColor &color)
{
m_d->currentBgColor = color;
}
void KisResourcesSnapshot::setSelectionOverride(KisSelectionSP selection)
{
m_d->selectionOverride = selection;
}
void KisResourcesSnapshot::setBrush(const KisPaintOpPresetSP &brush)
{
m_d->currentPaintOpPreset = brush;
}
diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h
index aefc95e1e7..8051507552 100644
--- a/libs/ui/tool/kis_resources_snapshot.h
+++ b/libs/ui/tool/kis_resources_snapshot.h
@@ -1,103 +1,104 @@
/*
* Copyright (c) 2011 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_RESOURCES_SNAPSHOT_H
#define __KIS_RESOURCES_SNAPSHOT_H
#include "kis_shared.h"
#include "kis_shared_ptr.h"
#include "kis_types.h"
#include "kritaui_export.h"
#include "kis_painter.h"
#include "kis_default_bounds.h"
class KoCanvasResourceManager;
class KoCompositeOp;
class KisPainter;
class KisPostExecutionUndoAdapter;
class KisRecordedPaintAction;
class KoPattern;
/**
* @brief The KisResourcesSnapshot class takes a snapshot of the various resources
* like colors and settings used at the begin of a stroke or a recording so subsequent
* changes don't impact the running stroke. The main reason for the snapshot is that the
* user can *change* the options while the stroke is being executed in the background.
*/
class KRITAUI_EXPORT KisResourcesSnapshot : public KisShared
{
public:
KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KoCanvasResourceManager *resourceManager, KisDefaultBoundsBaseSP bounds = 0);
+ KisResourcesSnapshot(KisImageSP image, KisNodeSP currentNode, KisDefaultBoundsBaseSP bounds = 0);
~KisResourcesSnapshot();
void setupPainter(KisPainter *painter);
// XXX: This was marked as KDE_DEPRECATED, but no althernative was
// given in the apidox.
void setupPaintAction(KisRecordedPaintAction *action);
KisPostExecutionUndoAdapter* postExecutionUndoAdapter() const;
void setCurrentNode(KisNodeSP node);
void setStrokeStyle(KisPainter::StrokeStyle strokeStyle);
void setFillStyle(KisPainter::FillStyle fillStyle);
KisNodeSP currentNode() const;
KisImageSP image() const;
bool needsIndirectPainting() const;
QString indirectPaintingCompositeOp() const;
/**
* \return currently active selection. Note that it will return
* null if current node *is* the current selection. This
* is done to avoid recursive selection application when
* painting on selectgion masks.
*/
KisSelectionSP activeSelection() const;
bool needsAirbrushing() const;
int airbrushingRate() const;
void setOpacity(qreal opacity);
quint8 opacity() const;
const KoCompositeOp* compositeOp() const;
QString compositeOpId() const;
KoPattern* currentPattern() const;
KoColor currentFgColor() const;
KoColor currentBgColor() const;
KisPaintOpPresetSP currentPaintOpPreset() const;
/// @return the channel lock flags of the current node with the global override applied
QBitArray channelLockFlags() const;
qreal effectiveZoom() const;
bool presetAllowsLod() const;
void setFGColorOverride(const KoColor &color);
void setBGColorOverride(const KoColor &color);
void setSelectionOverride(KisSelectionSP selection);
void setBrush(const KisPaintOpPresetSP &brush);
private:
struct Private;
Private * const m_d;
};
typedef KisSharedPtr<KisResourcesSnapshot> KisResourcesSnapshotSP;
#endif /* __KIS_RESOURCES_SNAPSHOT_H */
diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc
index 29409c7f23..763236c90b 100644
--- a/libs/ui/tool/kis_tool.cc
+++ b/libs/ui/tool/kis_tool.cc
@@ -1,698 +1,683 @@
/*
* Copyright (c) 2006, 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.
*/
#include "kis_tool.h"
#include <QCursor>
#include <QLabel>
#include <QWidget>
#include <QPolygonF>
#include <QTransform>
#include <klocalizedstring.h>
#include <QAction>
#include <kactioncollection.h>
#include <kis_icon.h>
#include <KoConfig.h>
#include <KoColorSpaceRegistry.h>
#include <KoColor.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <KoToolBase.h>
#include <KoID.h>
#include <KoPointerEvent.h>
#include <KoViewConverter.h>
#include <KoSelection.h>
#include <resources/KoAbstractGradient.h>
#include <KoSnapGuide.h>
#include <KisViewManager.h>
#include "kis_node_manager.h"
#include <kis_selection.h>
#include <kis_image.h>
#include <kis_group_layer.h>
#include <kis_adjustment_layer.h>
#include <kis_mask.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <brushengine/kis_paintop_preset.h>
#include <brushengine/kis_paintop_settings.h>
#include <resources/KoPattern.h>
#include <kis_floating_message.h>
#include "opengl/kis_opengl_canvas2.h"
#include "kis_canvas_resource_provider.h"
#include "canvas/kis_canvas2.h"
#include "kis_coordinates_converter.h"
#include "filter/kis_filter_configuration.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_cursor.h"
#include <recorder/kis_recorded_paint_action.h>
#include <kis_selection_mask.h>
#include "kis_resources_snapshot.h"
#include <KisView.h>
#include "kis_action_registry.h"
#include "kis_tool_utils.h"
struct Q_DECL_HIDDEN KisTool::Private {
QCursor cursor; // the cursor that should be shown on tool activation.
// From the canvas resources
KoPattern* currentPattern{0};
KoAbstractGradient* currentGradient{0};
KoColor currentFgColor;
KoColor currentBgColor;
float currentExposure{1.0};
KisFilterConfigurationSP currentGenerator;
QWidget* optionWidget{0};
ToolMode m_mode{HOVER_MODE};
bool m_isActive{false};
};
KisTool::KisTool(KoCanvasBase * canvas, const QCursor & cursor)
: KoToolBase(canvas)
, d(new Private)
{
d->cursor = cursor;
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetCursorStyle()));
connect(this, SIGNAL(isActiveChanged()), SLOT(resetCursorStyle()));
KActionCollection *collection = this->canvas()->canvasController()->actionCollection();
if (!collection->action("toggle_fg_bg")) {
QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("toggle_fg_bg", collection);
collection->addAction("toggle_fg_bg", toggleFgBg);
}
if (!collection->action("reset_fg_bg")) {
QAction *toggleFgBg = KisActionRegistry::instance()->makeQAction("reset_fg_bg", collection);
collection->addAction("reset_fg_bg", toggleFgBg);
}
addAction("toggle_fg_bg", dynamic_cast<QAction *>(collection->action("toggle_fg_bg")));
addAction("reset_fg_bg", dynamic_cast<QAction *>(collection->action("reset_fg_bg")));
}
KisTool::~KisTool()
{
delete d;
}
-void KisTool::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
+void KisTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
- Q_UNUSED(toolActivation);
- Q_UNUSED(shapes);
+ KoToolBase::activate(activation, shapes);
resetCursorStyle();
if (!canvas()) return;
if (!canvas()->resourceManager()) return;
d->currentFgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::ForegroundColor).value<KoColor>();
d->currentBgColor = canvas()->resourceManager()->resource(KoCanvasResourceManager::BackgroundColor).value<KoColor>();
if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentPattern)) {
d->currentPattern = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPattern).value<KoPattern*>();
}
if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGradient)) {
d->currentGradient = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGradient).value<KoAbstractGradient*>();
}
KisPaintOpPresetSP preset = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
if (preset && preset->settings()) {
preset->settings()->activate();
}
if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::HdrExposure)) {
d->currentExposure = static_cast<float>(canvas()->resourceManager()->resource(KisCanvasResourceProvider::HdrExposure).toDouble());
}
if (canvas()->resourceManager()->hasResource(KisCanvasResourceProvider::CurrentGeneratorConfiguration)) {
d->currentGenerator = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentGeneratorConfiguration).value<KisFilterConfiguration*>();
}
connect(actions().value("toggle_fg_bg"), SIGNAL(triggered()), SLOT(slotToggleFgBg()), Qt::UniqueConnection);
connect(actions().value("reset_fg_bg"), SIGNAL(triggered()), SLOT(slotResetFgBg()), Qt::UniqueConnection);
- connect(image(), SIGNAL(sigUndoDuringStrokeRequested()), SLOT(requestUndoDuringStroke()), Qt::UniqueConnection);
- connect(image(), SIGNAL(sigStrokeCancellationRequested()), SLOT(requestStrokeCancellation()), Qt::UniqueConnection);
- connect(image(), SIGNAL(sigStrokeEndRequested()), SLOT(requestStrokeEnd()), Qt::UniqueConnection);
+
d->m_isActive = true;
emit isActiveChanged();
}
void KisTool::deactivate()
{
bool result = true;
- result &= disconnect(image().data(), SIGNAL(sigUndoDuringStrokeRequested()), this, 0);
- result &= disconnect(image().data(), SIGNAL(sigStrokeCancellationRequested()), this, 0);
- result &= disconnect(image().data(), SIGNAL(sigStrokeEndRequested()), this, 0);
result &= disconnect(actions().value("toggle_fg_bg"), 0, this, 0);
result &= disconnect(actions().value("reset_fg_bg"), 0, this, 0);
if (!result) {
warnKrita << "WARNING: KisTool::deactivate() failed to disconnect"
<< "some signal connections. Your actions might be executed twice!";
}
d->m_isActive = false;
emit isActiveChanged();
-}
-
-void KisTool::requestUndoDuringStroke()
-{
- /**
- * Default implementation just cancells the stroke
- */
- requestStrokeCancellation();
-}
-void KisTool::requestStrokeCancellation()
-{
-}
-
-void KisTool::requestStrokeEnd()
-{
+ KoToolBase::deactivate();
}
void KisTool::canvasResourceChanged(int key, const QVariant & v)
{
switch (key) {
case(KoCanvasResourceManager::ForegroundColor):
d->currentFgColor = v.value<KoColor>();
break;
case(KoCanvasResourceManager::BackgroundColor):
d->currentBgColor = v.value<KoColor>();
break;
case(KisCanvasResourceProvider::CurrentPattern):
d->currentPattern = static_cast<KoPattern *>(v.value<void *>());
break;
case(KisCanvasResourceProvider::CurrentGradient):
d->currentGradient = static_cast<KoAbstractGradient *>(v.value<void *>());
break;
case(KisCanvasResourceProvider::HdrExposure):
d->currentExposure = static_cast<float>(v.toDouble());
break;
case(KisCanvasResourceProvider::CurrentGeneratorConfiguration):
d->currentGenerator = static_cast<KisFilterConfiguration*>(v.value<void *>());
break;
case(KisCanvasResourceProvider::CurrentPaintOpPreset):
emit statusTextChanged(v.value<KisPaintOpPresetSP>()->name());
break;
case(KisCanvasResourceProvider::CurrentKritaNode):
resetCursorStyle();
break;
default:
break; // Do nothing
};
}
void KisTool::updateSettingsViews()
{
}
QPointF KisTool::widgetCenterInWidgetPixels()
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
const KisCoordinatesConverter *converter = kritaCanvas->coordinatesConverter();
return converter->flakeToWidget(converter->flakeCenterPoint());
}
QPointF KisTool::convertDocumentToWidget(const QPointF& pt)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
Q_ASSERT(kritaCanvas);
return kritaCanvas->coordinatesConverter()->documentToWidget(pt);
}
QPointF KisTool::convertToPixelCoord(KoPointerEvent *e)
{
if (!image())
return e->point;
return image()->documentToPixel(e->point);
}
QPointF KisTool::convertToPixelCoord(const QPointF& pt)
{
if (!image())
return pt;
return image()->documentToPixel(pt);
}
QPointF KisTool::convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset, bool useModifiers)
{
if (!image())
return e->point;
KoSnapGuide *snapGuide = canvas()->snapGuide();
QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier);
return image()->documentToPixel(pos);
}
QPointF KisTool::convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset)
{
if (!image())
return pt;
KoSnapGuide *snapGuide = canvas()->snapGuide();
QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier);
return image()->documentToPixel(pos);
}
QPoint KisTool::convertToIntPixelCoord(KoPointerEvent *e)
{
if (!image())
return e->point.toPoint();
return image()->documentToIntPixel(e->point);
}
QPointF KisTool::viewToPixel(const QPointF &viewCoord) const
{
if (!image())
return viewCoord;
return image()->documentToPixel(canvas()->viewConverter()->viewToDocument(viewCoord));
}
QRectF KisTool::convertToPt(const QRectF &rect)
{
if (!image())
return rect;
QRectF r;
//We add 1 in the following to the extreme coords because a pixel always has size
r.setCoords(int(rect.left()) / image()->xRes(), int(rect.top()) / image()->yRes(),
int(1 + rect.right()) / image()->xRes(), int(1 + rect.bottom()) / image()->yRes());
return r;
}
QPointF KisTool::pixelToView(const QPoint &pixelCoord) const
{
if (!image())
return pixelCoord;
QPointF documentCoord = image()->pixelToDocument(pixelCoord);
return canvas()->viewConverter()->documentToView(documentCoord);
}
QPointF KisTool::pixelToView(const QPointF &pixelCoord) const
{
if (!image())
return pixelCoord;
QPointF documentCoord = image()->pixelToDocument(pixelCoord);
return canvas()->viewConverter()->documentToView(documentCoord);
}
QRectF KisTool::pixelToView(const QRectF &pixelRect) const
{
if (!image())
return pixelRect;
QPointF topLeft = pixelToView(pixelRect.topLeft());
QPointF bottomRight = pixelToView(pixelRect.bottomRight());
return QRectF(topLeft, bottomRight);
}
QPainterPath KisTool::pixelToView(const QPainterPath &pixelPolygon) const
{
QTransform matrix;
qreal zoomX, zoomY;
canvas()->viewConverter()->zoom(&zoomX, &zoomY);
matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes());
return matrix.map(pixelPolygon);
}
QPolygonF KisTool::pixelToView(const QPolygonF &pixelPath) const
{
QTransform matrix;
qreal zoomX, zoomY;
canvas()->viewConverter()->zoom(&zoomX, &zoomY);
matrix.scale(zoomX/image()->xRes(), zoomY/ image()->yRes());
return matrix.map(pixelPath);
}
void KisTool::updateCanvasPixelRect(const QRectF &pixelRect)
{
canvas()->updateCanvas(convertToPt(pixelRect));
}
void KisTool::updateCanvasViewRect(const QRectF &viewRect)
{
canvas()->updateCanvas(canvas()->viewConverter()->viewToDocument(viewRect));
}
KisImageWSP KisTool::image() const
{
// For now, krita tools only work in krita, not for a krita shape. Krita shapes are for 2.1
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
if (kisCanvas) {
return kisCanvas->currentImage();
}
return 0;
}
QCursor KisTool::cursor() const
{
return d->cursor;
}
void KisTool::notifyModified() const
{
if (image()) {
image()->setModified();
}
}
KoPattern * KisTool::currentPattern()
{
return d->currentPattern;
}
KoAbstractGradient * KisTool::currentGradient()
{
return d->currentGradient;
}
KisPaintOpPresetSP KisTool::currentPaintOpPreset()
{
return canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value<KisPaintOpPresetSP>();
}
KisNodeSP KisTool::currentNode() const
{
KisNodeSP node = canvas()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value<KisNodeWSP>();
return node;
}
KisNodeList KisTool::selectedNodes() const
{
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
KisViewManager* viewManager = kiscanvas->viewManager();
return viewManager->nodeManager()->selectedNodes();
}
KoColor KisTool::currentFgColor()
{
return d->currentFgColor;
}
KoColor KisTool::currentBgColor()
{
return d->currentBgColor;
}
KisImageWSP KisTool::currentImage()
{
return image();
}
KisFilterConfigurationSP KisTool::currentGenerator()
{
return d->currentGenerator;
}
void KisTool::setMode(ToolMode mode) {
d->m_mode = mode;
}
KisTool::ToolMode KisTool::mode() const {
return d->m_mode;
}
+void KisTool::setCursor(const QCursor &cursor)
+{
+ d->cursor = cursor;
+}
+
KisTool::AlternateAction KisTool::actionToAlternateAction(ToolAction action) {
KIS_ASSERT_RECOVER_RETURN_VALUE(action != Primary, Secondary);
return (AlternateAction)action;
}
void KisTool::activatePrimaryAction()
{
resetCursorStyle();
}
void KisTool::deactivatePrimaryAction()
{
resetCursorStyle();
}
void KisTool::beginPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
}
void KisTool::beginPrimaryDoubleClickAction(KoPointerEvent *event)
{
beginPrimaryAction(event);
}
void KisTool::continuePrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
}
void KisTool::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
}
bool KisTool::primaryActionSupportsHiResEvents() const
{
return false;
}
void KisTool::activateAlternateAction(AlternateAction action)
{
Q_UNUSED(action);
}
void KisTool::deactivateAlternateAction(AlternateAction action)
{
Q_UNUSED(action);
}
void KisTool::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
Q_UNUSED(event);
Q_UNUSED(action);
}
void KisTool::beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action)
{
beginAlternateAction(event, action);
}
void KisTool::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
Q_UNUSED(event);
Q_UNUSED(action);
}
void KisTool::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
Q_UNUSED(event);
Q_UNUSED(action);
}
void KisTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
Q_UNUSED(event);
}
void KisTool::mouseTripleClickEvent(KoPointerEvent *event)
{
mouseDoubleClickEvent(event);
}
void KisTool::mousePressEvent(KoPointerEvent *event)
{
Q_UNUSED(event);
}
void KisTool::mouseReleaseEvent(KoPointerEvent *event)
{
Q_UNUSED(event);
}
void KisTool::mouseMoveEvent(KoPointerEvent *event)
{
Q_UNUSED(event);
}
void KisTool::deleteSelection()
{
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
KisViewManager* viewManager = kiscanvas->viewManager();
viewManager->blockUntilOperationsFinished(image());
if (!KisToolUtils::clearImage(image(), resources->currentNode(), resources->activeSelection())) {
KoToolBase::deleteSelection();
}
}
void KisTool::setupPaintAction(KisRecordedPaintAction* action)
{
action->setPaintColor(currentFgColor());
action->setBackgroundColor(currentBgColor());
}
QWidget* KisTool::createOptionWidget()
{
d->optionWidget = new QLabel(i18n("No options"));
d->optionWidget->setObjectName("SpecialSpacer");
return d->optionWidget;
}
#define NEAR_VAL -1000.0
#define FAR_VAL 1000.0
#define PROGRAM_VERTEX_ATTRIBUTE 0
void KisTool::paintToolOutline(QPainter* painter, const QPainterPath &path)
{
KisOpenGLCanvas2 *canvasWidget = dynamic_cast<KisOpenGLCanvas2 *>(canvas()->canvasWidget());
if (canvasWidget) {
painter->beginNativePainting();
canvasWidget->paintToolOutline(path);
painter->endNativePainting();
}
else {
painter->setCompositionMode(QPainter::RasterOp_SourceXorDestination);
painter->setPen(QColor(128, 255, 128));
painter->drawPath(path);
}
}
void KisTool::resetCursorStyle()
{
useCursor(d->cursor);
}
bool KisTool::overrideCursorIfNotEditable()
{
// override cursor for canvas iff this tool is active
// and we can't paint on the active layer
if (isActive()) {
KisNodeSP node = currentNode();
if (node && !node->isEditable()) {
canvas()->setCursor(Qt::ForbiddenCursor);
return true;
}
}
return false;
}
bool KisTool::isActive() const
{
return d->m_isActive;
}
void KisTool::slotToggleFgBg()
{
KoCanvasResourceManager* resourceManager = canvas()->resourceManager();
KoColor newFg = resourceManager->backgroundColor();
KoColor newBg = resourceManager->foregroundColor();
/**
* NOTE: Some of color selectors do not differentiate foreground
* and background colors, so if one wants them to end up
* being set up to foreground color, it should be set the
* last.
*/
resourceManager->setBackgroundColor(newBg);
resourceManager->setForegroundColor(newFg);
}
void KisTool::slotResetFgBg()
{
KoCanvasResourceManager* resourceManager = canvas()->resourceManager();
// see a comment in slotToggleFgBg()
resourceManager->setBackgroundColor(KoColor(Qt::white, KoColorSpaceRegistry::instance()->rgb8()));
resourceManager->setForegroundColor(KoColor(Qt::black, KoColorSpaceRegistry::instance()->rgb8()));
}
void KisTool::setCurrentNodeLocked(bool locked)
{
if (currentNode()) {
currentNode()->setSystemLocked(locked, false);
}
}
bool KisTool::nodeEditable()
{
KisNodeSP node = currentNode();
if (!node) {
return false;
}
bool nodeEditable = node->isEditable();
if (!nodeEditable) {
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
QString message;
if (!node->visible() && node->userLocked()) {
message = i18n("Layer is locked and invisible.");
} else if (node->userLocked()) {
message = i18n("Layer is locked.");
} else if(!node->visible()) {
message = i18n("Layer is invisible.");
} else {
message = i18n("Group not editable.");
}
kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked"));
}
return nodeEditable;
}
bool KisTool::selectionEditable()
{
KisCanvas2 * kisCanvas = static_cast<KisCanvas2*>(canvas());
KisViewManager * view = kisCanvas->viewManager();
bool editable = view->selectionEditable();
if (!editable) {
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
kiscanvas->viewManager()->showFloatingMessage(i18n("Local selection is locked."), KisIconUtils::loadIcon("object-locked"));
}
return editable;
}
void KisTool::listenToModifiers(bool listen)
{
Q_UNUSED(listen);
}
bool KisTool::listeningToModifiers()
{
return false;
}
diff --git a/libs/ui/tool/kis_tool.h b/libs/ui/tool/kis_tool.h
index 0f321ab8bc..fe5fcd761e 100644
--- a/libs/ui/tool/kis_tool.h
+++ b/libs/ui/tool/kis_tool.h
@@ -1,370 +1,346 @@
/*
* 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_TOOL_H_
#define KIS_TOOL_H_
#include <QCursor>
#include <KoColor.h>
#include <KoToolBase.h>
#include <KoID.h>
#include <KoCanvasResourceManager.h>
#include <kritaui_export.h>
#include <kis_types.h>
#define PRESS_CONDITION(_event, _mode, _button, _modifier) \
(this->mode() == (_mode) && (_event)->button() == (_button) && \
(_event)->modifiers() == (_modifier))
#define PRESS_CONDITION_WB(_event, _mode, _button, _modifier) \
(this->mode() == (_mode) && (_event)->button() & (_button) && \
(_event)->modifiers() == (_modifier))
#define PRESS_CONDITION_OM(_event, _mode, _button, _modifier) \
(this->mode() == (_mode) && (_event)->button() == (_button) && \
((_event)->modifiers() & (_modifier) || \
(_event)->modifiers() == Qt::NoModifier))
#define RELEASE_CONDITION(_event, _mode, _button) \
(this->mode() == (_mode) && (_event)->button() == (_button))
#define RELEASE_CONDITION_WB(_event, _mode, _button) \
(this->mode() == (_mode) && (_event)->button() & (_button))
#define MOVE_CONDITION(_event, _mode) (this->mode() == (_mode))
#ifdef __GNUC__
#define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come to" << __func__ << "while being mode" << _mode << "!"
#else
#define WARN_WRONG_MODE(_mode) warnKrita << "Unexpected tool event has come while being mode" << _mode << "!"
#endif
#define CHECK_MODE_SANITY_OR_RETURN(_mode) if (mode() != _mode) { WARN_WRONG_MODE(mode()); return; }
class KoCanvasBase;
class KoPattern;
class KoAbstractGradient;
class KisFilterConfiguration;
class QPainter;
class QPainterPath;
class QPolygonF;
class KisRecordedPaintAction;
/// Definitions of the toolgroups of Krita
static const QString TOOL_TYPE_SHAPE = "0 Krita/Shape"; // Geometric shapes like ellipses and lines
static const QString TOOL_TYPE_TRANSFORM = "2 Krita/Transform"; // Tools that transform the layer;
static const QString TOOL_TYPE_FILL = "3 Krita/Fill"; // Tools that fill parts of the canvas
static const QString TOOL_TYPE_VIEW = "4 Krita/View"; // Tools that affect the canvas: pan, zoom, etc.
static const QString TOOL_TYPE_SELECTION = "5 Krita/Select"; // Tools that select pixels
//activation id for Krita tools, Krita tools are always active and handle locked and invisible layers by themself
static const QString KRITA_TOOL_ACTIVATION_ID = "flake/always";
class KRITAUI_EXPORT KisTool
: public KoToolBase
{
Q_OBJECT
Q_PROPERTY(bool isActive READ isActive NOTIFY isActiveChanged)
public:
enum { FLAG_USES_CUSTOM_PRESET=0x01, FLAG_USES_CUSTOM_COMPOSITEOP=0x02 };
KisTool(KoCanvasBase * canvas, const QCursor & cursor);
virtual ~KisTool();
virtual int flags() const { return 0; }
void deleteSelection();
// KoToolBase Implementation.
public:
/**
* Called by KisToolProxy when the primary action of the tool is
* going to be started now, that is when all the modifiers are
* pressed and the only thing left is just to press the mouse
* button. On coming of this callback the tool is supposed to
* prepare the cursor and/or the outline to show the user shat is
* going to happen next
*/
virtual void activatePrimaryAction();
/**
* Called by KisToolProxy when the primary is no longer possible
* to be started now, e.g. when its modifiers and released. The
* tool is supposed revert all the preparetions it has doen in
* activatePrimaryAction().
*/
virtual void deactivatePrimaryAction();
/**
* Called by KisToolProxy when a primary action for the tool is
* started. The \p event stores the original event that
* started the stroke. The \p event is _accepted_ by default. If
* the tool decides to ignore this particular action (e.g. when
* the node is not editable), it should call event->ignore(). Then
* no further continuePrimaryAction() or endPrimaryAction() will
* be called until the next user action.
*/
virtual void beginPrimaryAction(KoPointerEvent *event);
/**
* Called by KisToolProxy when the primary action is in progress
* of pointer movement. If the tool has ignored the event in
* beginPrimaryAction(), this method will not be called.
*/
virtual void continuePrimaryAction(KoPointerEvent *event);
/**
* Called by KisToolProxy when the primary action is being
* finished, that is while mouseRelease or tabletRelease event.
* If the tool has ignored the event in beginPrimaryAction(), this
* method will not be called.
*/
virtual void endPrimaryAction(KoPointerEvent *event);
/**
* The same as beginPrimaryAction(), but called when the stroke is
* started by a double-click
*
* \see beginPrimaryAction()
*/
virtual void beginPrimaryDoubleClickAction(KoPointerEvent *event);
/**
* Returns true if the tool can handle (and wants to handle) a
* very tight flow of input events from the tablet
*/
virtual bool primaryActionSupportsHiResEvents() const;
enum ToolAction {
Primary,
AlternateChangeSize,
AlternatePickFgNode,
AlternatePickBgNode,
AlternatePickFgImage,
AlternatePickBgImage,
AlternateSecondary,
AlternateThird,
AlternateFourth,
AlternateFifth,
Alternate_NONE = 10000
};
// Technically users are allowed to configure this, but nobody ever would do that.
// So these can basically be thought of as aliases to ctrl+click, etc.
enum AlternateAction {
ChangeSize = AlternateChangeSize, // Default: Shift+Left click
PickFgNode = AlternatePickFgNode, // Default: Ctrl+Alt+Left click
PickBgNode = AlternatePickBgNode, // Default: Ctrl+Alt+Right click
PickFgImage = AlternatePickFgImage, // Default: Ctrl+Left click
PickBgImage = AlternatePickBgImage, // Default: Ctrl+Right click
Secondary = AlternateSecondary,
Third = AlternateThird,
Fourth = AlternateFourth,
Fifth = AlternateFifth,
NONE = 10000
};
static AlternateAction actionToAlternateAction(ToolAction action);
virtual void activateAlternateAction(AlternateAction action);
virtual void deactivateAlternateAction(AlternateAction action);
virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action);
virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action);
virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action);
virtual void beginAlternateDoubleClickAction(KoPointerEvent *event, AlternateAction action);
void mousePressEvent(KoPointerEvent *event);
void mouseDoubleClickEvent(KoPointerEvent *event);
void mouseTripleClickEvent(KoPointerEvent *event);
void mouseReleaseEvent(KoPointerEvent *event);
void mouseMoveEvent(KoPointerEvent *event);
bool isActive() const;
public Q_SLOTS:
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape*> &shapes);
virtual void deactivate();
virtual void canvasResourceChanged(int key, const QVariant & res);
// Implement this slot in case there are any widgets or properties which need
// to be updated after certain operations, to reflect the inner state correctly.
// At the moment this is used for smoothing options in the freehand brush, but
// this will likely be expanded.
virtual void updateSettingsViews();
Q_SIGNALS:
void isActiveChanged();
protected:
// conversion methods are also needed by the paint information builder
friend class KisToolPaintingInformationBuilder;
/// Convert from native (postscript points) to image pixel
/// coordinates.
QPointF convertToPixelCoord(KoPointerEvent *e);
QPointF convertToPixelCoord(const QPointF& pt);
QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset = QPointF(), bool useModifiers = true);
QPointF convertToPixelCoordAndSnap(const QPointF& pt, const QPointF &offset = QPointF());
protected:
QPointF widgetCenterInWidgetPixels();
QPointF convertDocumentToWidget(const QPointF& pt);
/// Convert from native (postscript points) to integer image pixel
/// coordinates. This truncates the floating point components and
/// should be used in preference to QPointF::toPoint(), which rounds,
/// to ensure the cursor acts on the pixel it is visually over.
QPoint convertToIntPixelCoord(KoPointerEvent *e);
QRectF convertToPt(const QRectF &rect);
QPointF viewToPixel(const QPointF &viewCoord) const;
/// Convert an integer pixel coordinate into a view coordinate.
/// The view coordinate is at the centre of the pixel.
QPointF pixelToView(const QPoint &pixelCoord) const;
/// Convert a floating point pixel coordinate into a view coordinate.
QPointF pixelToView(const QPointF &pixelCoord) const;
/// Convert a pixel rectangle into a view rectangle.
QRectF pixelToView(const QRectF &pixelRect) const;
/// Convert a pixel path into a view path
QPainterPath pixelToView(const QPainterPath &pixelPath) const;
/// Convert a pixel polygon into a view path
QPolygonF pixelToView(const QPolygonF &pixelPolygon) const;
/// Update the canvas for the given rectangle in image pixel coordinates.
void updateCanvasPixelRect(const QRectF &pixelRect);
/// Update the canvas for the given rectangle in view coordinates.
void updateCanvasViewRect(const QRectF &viewRect);
virtual QWidget* createOptionWidget();
/**
* To determine whether this tool will change its behavior when
* modifier keys are pressed
*/
virtual bool listeningToModifiers();
/**
* Request that this tool no longer listen to modifier keys
* (Responding to the request is optional)
*/
virtual void listenToModifiers(bool listen);
protected:
KisImageWSP image() const;
QCursor cursor() const;
/// Call this to set the document modified
void notifyModified() const;
KisImageWSP currentImage();
KoPattern* currentPattern();
KoAbstractGradient *currentGradient();
KisNodeSP currentNode() const;
KisNodeList selectedNodes() const;
KoColor currentFgColor();
KoColor currentBgColor();
KisPaintOpPresetSP currentPaintOpPreset();
KisFilterConfigurationSP currentGenerator();
virtual void setupPaintAction(KisRecordedPaintAction* action);
/// paint the path which is in view coordinates, default paint mode is XOR_MODE, BW_MODE is also possible
/// never apply transformations to the painter, they would be useless, if drawing in OpenGL mode. The coordinates in the path should be in view coordinates.
void paintToolOutline(QPainter * painter, const QPainterPath &path);
/// Sets the systemLocked for the current node, this will not deactivate the tool buttons
void setCurrentNodeLocked(bool locked);
/// Checks checks if the current node is editable
bool nodeEditable();
/// Checks checks if the selection is editable, only applies to local selection as global selection is always editable
bool selectionEditable();
/// Override the cursor appropriately if current node is not editable
bool overrideCursorIfNotEditable();
protected:
enum ToolMode {
HOVER_MODE,
PAINT_MODE,
SECONDARY_PAINT_MODE,
MIRROR_AXIS_SETUP_MODE,
GESTURE_MODE,
PAN_MODE,
OTHER // not used now
};
virtual void setMode(ToolMode mode);
virtual ToolMode mode() const;
+ void setCursor(const QCursor &cursor);
protected Q_SLOTS:
/**
* Called whenever the configuration settings change.
*/
virtual void resetCursorStyle();
- /**
- * Called when the user requested undo while the stroke is
- * active. If you tool supports undo of the part of its actions,
- * override this method and do the needed work there.
- *
- * NOTE: Default implementation forwards this request to
- * requestStrokeCancellation() method, so that the stroke
- * would be cancelled.
- */
- virtual void requestUndoDuringStroke();
-
- /**
- * Called when the user requested the cancellation of the current
- * stroke. If you tool supports cancelling, override this method
- * and do the needed work there
- */
- virtual void requestStrokeCancellation();
-
- /**
- * Called when the image decided that the stroke should better be
- * ended. If you tool supports long strokes, override this method
- * and do the needed work there
- */
- virtual void requestStrokeEnd();
-
private Q_SLOTS:
void slotToggleFgBg();
void slotResetFgBg();
private:
struct Private;
Private* const d;
};
#endif // KIS_TOOL_H_
diff --git a/libs/ui/tool/kis_tool_freehand.cc b/libs/ui/tool/kis_tool_freehand.cc
index e51ac51dbe..bb2870203e 100644
--- a/libs/ui/tool/kis_tool_freehand.cc
+++ b/libs/ui/tool/kis_tool_freehand.cc
@@ -1,458 +1,460 @@
/*
* kis_tool_freehand.cc - part of Krita
*
* Copyright (c) 2003-2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Bart Coppens <kde@bartcoppens.be>
* Copyright (c) 2007,2008,2010 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_freehand.h"
#include <QPainter>
#include <QRect>
#include <QThreadPool>
#include <QApplication>
#include <QDesktopWidget>
+#include <Eigen/Core>
+
#include <kis_icon.h>
#include <KoPointerEvent.h>
#include <KoViewConverter.h>
#include <KoCanvasController.h>
//pop up palette
#include <kis_canvas_resource_provider.h>
// Krita/image
#include <kis_layer.h>
#include <kis_paint_layer.h>
#include <kis_painter.h>
#include <brushengine/kis_paintop.h>
#include <kis_selection.h>
#include <brushengine/kis_paintop_preset.h>
// Krita/ui
#include "kis_abstract_perspective_grid.h"
#include "kis_config.h"
#include "canvas/kis_canvas2.h"
#include "kis_cursor.h"
#include <KisViewManager.h>
#include <kis_painting_assistants_decoration.h>
#include "kis_painting_information_builder.h"
#include "kis_tool_freehand_helper.h"
#include "kis_recording_adapter.h"
#include "strokes/freehand_stroke.h"
using namespace std::placeholders; // For _1 placeholder
KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText)
: KisToolPaint(canvas, cursor),
m_paintopBasedPickingInAction(false),
m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1))
{
m_assistant = false;
m_magnetism = 1.0;
m_only_one_assistant = true;
setSupportOutline(true);
setMaskSyntheticEvents(true); // Disallow mouse events from finger presses.
m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this);
m_recordingAdapter = new KisRecordingAdapter();
m_helper = new KisToolFreehandHelper(m_infoBuilder, transactionText, m_recordingAdapter);
connect(m_helper, SIGNAL(requestExplicitUpdateOutline()),
SLOT(explicitUpdateOutline()));
}
KisToolFreehand::~KisToolFreehand()
{
delete m_helper;
delete m_recordingAdapter;
delete m_infoBuilder;
}
KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const
{
return m_helper->smoothingOptions();
}
void KisToolFreehand::resetCursorStyle()
{
KisConfig cfg;
switch (cfg.newCursorStyle()) {
case CURSOR_STYLE_NO_CURSOR:
useCursor(KisCursor::blankCursor());
break;
case CURSOR_STYLE_POINTER:
useCursor(KisCursor::arrowCursor());
break;
case CURSOR_STYLE_SMALL_ROUND:
useCursor(KisCursor::roundCursor());
break;
case CURSOR_STYLE_CROSSHAIR:
useCursor(KisCursor::crossCursor());
break;
case CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
useCursor(KisCursor::triangleRightHandedCursor());
break;
case CURSOR_STYLE_TRIANGLE_LEFTHANDED:
useCursor(KisCursor::triangleLeftHandedCursor());
break;
case CURSOR_STYLE_BLACK_PIXEL:
useCursor(KisCursor::pixelBlackCursor());
break;
case CURSOR_STYLE_WHITE_PIXEL:
useCursor(KisCursor::pixelWhiteCursor());
break;
case CURSOR_STYLE_TOOLICON:
default:
KisToolPaint::resetCursorStyle();
break;
}
}
KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const
{
return m_infoBuilder;
}
KisRecordingAdapter* KisToolFreehand::recordingAdapter() const
{
return m_recordingAdapter;
}
void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper)
{
delete m_helper;
m_helper = helper;
}
int KisToolFreehand::flags() const
{
return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET;
}
void KisToolFreehand::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KisToolPaint::activate(activation, shapes);
}
void KisToolFreehand::deactivate()
{
if (mode() == PAINT_MODE) {
endStroke();
setMode(KisTool::HOVER_MODE);
}
KisToolPaint::deactivate();
}
void KisToolFreehand::initStroke(KoPointerEvent *event)
{
setCurrentNodeLocked(true);
m_helper->initPaint(event, canvas()->resourceManager(),
image(),
currentNode(),
image().data());
}
void KisToolFreehand::doStroke(KoPointerEvent *event)
{
//set canvas information here?//
KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
if (canvas2) {
m_helper->setCanvasHorizontalMirrorState(canvas2->xAxisMirrored());
m_helper->setCanvasRotation(canvas2->rotationAngle());
}
m_helper->paint(event);
}
void KisToolFreehand::endStroke()
{
m_helper->endPaint();
setCurrentNodeLocked(false);
}
bool KisToolFreehand::primaryActionSupportsHiResEvents() const
{
return true;
}
void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event)
{
// FIXME: workaround for the Duplicate Op
tryPickByPaintOp(event, PickFgImage);
requestUpdateOutline(event->point, event);
NodePaintAbility paintability = nodePaintAbility();
if (!nodeEditable() || paintability != PAINT) {
if(paintability == KisToolPaint::VECTOR){
KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask.");
kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
}
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
if (canvas2) {
canvas2->viewManager()->disableControls();
}
initStroke(event);
}
void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
requestUpdateOutline(event->point, event);
/**
* Actual painting
*/
doStroke(event);
}
void KisToolFreehand::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
endStroke();
if (m_assistant && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->endStroke();
}
notifyModified();
KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
if (canvas2) {
canvas2->viewManager()->enableControls();
}
setMode(KisTool::HOVER_MODE);
}
bool KisToolFreehand::tryPickByPaintOp(KoPointerEvent *event, AlternateAction action)
{
if (action != PickFgNode && action != PickFgImage) return false;
/**
* FIXME: we need some better way to implement modifiers
* for a paintop level. This method is used in DuplicateOp only!
*/
QPointF pos = adjustPosition(event->point, event->point);
qreal perspective = 1.0;
Q_FOREACH (const QPointer<KisAbstractPerspectiveGrid> grid, static_cast<KisCanvas2*>(canvas())->viewManager()->resourceProvider()->perspectiveGrids()) {
if (grid && grid->contains(pos)) {
perspective = grid->distance(pos);
break;
}
}
if (!currentPaintOpPreset()) {
return false;
}
bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->
mousePressEvent(KisPaintInformation(convertToPixelCoord(event->point),
pressureToCurve(event->pressure()),
event->xTilt(), event->yTilt(),
event->rotation(),
event->tangentialPressure(),
perspective, 0, 0),
event->modifiers(),
currentNode());
return !paintOpIgnoredEvent;
}
void KisToolFreehand::activateAlternateAction(AlternateAction action)
{
if (action != ChangeSize) {
KisToolPaint::activateAlternateAction(action);
return;
}
useCursor(KisCursor::blankCursor());
setOutlineEnabled(true);
}
void KisToolFreehand::deactivateAlternateAction(AlternateAction action)
{
if (action != ChangeSize) {
KisToolPaint::deactivateAlternateAction(action);
return;
}
resetCursorStyle();
setOutlineEnabled(false);
}
void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (tryPickByPaintOp(event, action)) {
m_paintopBasedPickingInAction = true;
return;
}
if (action != ChangeSize) {
KisToolPaint::beginAlternateAction(event, action);
return;
}
setMode(GESTURE_MODE);
m_initialGestureDocPoint = event->point;
m_initialGestureGlobalPoint = QCursor::pos();
m_lastDocumentPoint = event->point;
m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize();
}
void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) return;
if (action != ChangeSize) {
KisToolPaint::continueAlternateAction(event, action);
return;
}
QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint);
QPointF actualWidgetPosition = convertDocumentToWidget(event->point);
QPointF offset = actualWidgetPosition - lastWidgetPosition;
KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
QRect screenRect = QApplication::desktop()->screenGeometry();
qreal scaleX = 0;
qreal scaleY = 0;
canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY);
const qreal maxBrushSize = KisConfig().readEntry("maximumBrushSize", 1000);
const qreal effectiveMaxDragSize = 0.5 * screenRect.width();
const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX);
const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize;
const qreal sizeDiff = scaleCoeff * offset.x() ;
if (qAbs(sizeDiff) > 0.01) {
KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
const qreal newSize = qBound(0.01, m_lastPaintOpSize + sizeDiff, maxBrushSize);
settings->setPaintOpSize(newSize);
requestUpdateOutline(m_initialGestureDocPoint, 0);
//m_brushResizeCompressor.start(newSize);
m_lastDocumentPoint = event->point;
m_lastPaintOpSize = newSize;
}
}
void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
if (tryPickByPaintOp(event, action) || m_paintopBasedPickingInAction) {
m_paintopBasedPickingInAction = false;
return;
}
if (action != ChangeSize) {
KisToolPaint::endAlternateAction(event, action);
return;
}
QCursor::setPos(m_initialGestureGlobalPoint);
requestUpdateOutline(m_initialGestureDocPoint, 0);
setMode(HOVER_MODE);
}
bool KisToolFreehand::wantsAutoScroll() const
{
return false;
}
void KisToolFreehand::setAssistant(bool assistant)
{
m_assistant = assistant;
}
void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant)
{
m_only_one_assistant = assistant;
}
void KisToolFreehand::slotDoResizeBrush(qreal newSize)
{
KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
settings->setPaintOpSize(newSize);
requestUpdateOutline(m_initialGestureDocPoint, 0);
}
QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin)
{
if (m_assistant && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant);
QPointF ap = static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin);
return (1.0 - m_magnetism) * point + m_magnetism * ap;
}
return point;
}
qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint)
{
qreal perspective = 1.0;
Q_FOREACH (const QPointer<KisAbstractPerspectiveGrid> grid, static_cast<KisCanvas2*>(canvas())->viewManager()->resourceProvider()->perspectiveGrids()) {
if (grid && grid->contains(documentPoint)) {
perspective = grid->distance(documentPoint);
break;
}
}
return perspective;
}
void KisToolFreehand::explicitUpdateOutline()
{
requestUpdateOutline(m_outlineDocPoint, 0);
}
QPainterPath KisToolFreehand::getOutlinePath(const QPointF &documentPos,
const KoPointerEvent *event,
KisPaintOpSettings::OutlineMode outlineMode)
{
QPointF imagePos = currentImage()->documentToPixel(documentPos);
if (currentPaintOpPreset())
return m_helper->paintOpOutline(imagePos,
event,
currentPaintOpPreset()->settings(),
outlineMode);
else
return QPainterPath();
}
diff --git a/libs/ui/tool/kis_tool_freehand.h b/libs/ui/tool/kis_tool_freehand.h
index 1f3b1e8726..a2827dc1c5 100644
--- a/libs/ui/tool/kis_tool_freehand.h
+++ b/libs/ui/tool/kis_tool_freehand.h
@@ -1,139 +1,141 @@
/*
* Copyright (c) 2003-2008 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_FREEHAND_H_
#define KIS_TOOL_FREEHAND_H_
+#include <Eigen/Core>
+
#include <brushengine/kis_paint_information.h>
#include <brushengine/kis_paintop_settings.h>
#include <kis_distance_information.h>
#include "kis_types.h"
#include "kis_tool_paint.h"
#include "kis_smoothing_options.h"
#include "kis_signal_compressor_with_param.h"
#include "kritaui_export.h"
class KoPointerEvent;
class KoCanvasBase;
class KisPaintingInformationBuilder;
class KisToolFreehandHelper;
class KisRecordingAdapter;
class KRITAUI_EXPORT KisToolFreehand : public KisToolPaint
{
Q_OBJECT
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText);
virtual ~KisToolFreehand();
virtual int flags() const;
public Q_SLOTS:
virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
void deactivate();
protected:
bool tryPickByPaintOp(KoPointerEvent *event, AlternateAction action);
bool primaryActionSupportsHiResEvents() const;
void beginPrimaryAction(KoPointerEvent *event);
void continuePrimaryAction(KoPointerEvent *event);
void endPrimaryAction(KoPointerEvent *event);
void activateAlternateAction(AlternateAction action);
void deactivateAlternateAction(AlternateAction action);
void beginAlternateAction(KoPointerEvent *event, AlternateAction action);
void continueAlternateAction(KoPointerEvent *event, AlternateAction action);
void endAlternateAction(KoPointerEvent *event, AlternateAction action);
virtual bool wantsAutoScroll() const;
virtual void initStroke(KoPointerEvent *event);
virtual void doStroke(KoPointerEvent *event);
virtual void endStroke();
virtual QPainterPath getOutlinePath(const QPointF &documentPos,
const KoPointerEvent *event,
KisPaintOpSettings::OutlineMode outlineMode);
KisPaintingInformationBuilder* paintingInformationBuilder() const;
KisRecordingAdapter* recordingAdapter() const;
void resetHelper(KisToolFreehandHelper *helper);
protected Q_SLOTS:
void explicitUpdateOutline();
virtual void resetCursorStyle();
void setAssistant(bool assistant);
void setOnlyOneAssistantSnap(bool assistant);
void slotDoResizeBrush(qreal newSize);
private:
friend class KisToolFreehandPaintingInformationBuilder;
/**
* Adjusts a coordinates according to a KisPaintingAssitant,
* if available.
*/
QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin);
/**
* Calculates a coefficient for KisPaintInformation
* according to perspective grid values
*/
qreal calculatePerspective(const QPointF &documentPoint);
protected:
friend class KisViewManager;
friend class KisView;
friend class KisSketchView;
KisSmoothingOptionsSP smoothingOptions() const;
bool m_assistant;
double m_magnetism;
bool m_only_one_assistant;
private:
KisPaintingInformationBuilder *m_infoBuilder;
KisToolFreehandHelper *m_helper;
KisRecordingAdapter *m_recordingAdapter;
QPointF m_initialGestureDocPoint;
QPointF m_lastDocumentPoint;
qreal m_lastPaintOpSize;
QPoint m_initialGestureGlobalPoint;
bool m_paintopBasedPickingInAction;
KisSignalCompressorWithParam<qreal> m_brushResizeCompressor;
};
#endif // KIS_TOOL_FREEHAND_H_
diff --git a/libs/ui/tool/kis_tool_rectangle_base.cpp b/libs/ui/tool/kis_tool_rectangle_base.cpp
index 5f70717476..1ae46e4b0e 100644
--- a/libs/ui/tool/kis_tool_rectangle_base.cpp
+++ b/libs/ui/tool/kis_tool_rectangle_base.cpp
@@ -1,247 +1,247 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Boudewijn Rempt <boud@valdyas.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_tool_rectangle_base.h"
#include <QtCore/qmath.h>
#include <KoPointerEvent.h>
#include <KoCanvasBase.h>
#include <KoCanvasController.h>
#include <KoViewConverter.h>
#include "kis_rectangle_constraint_widget.h"
KisToolRectangleBase::KisToolRectangleBase(KoCanvasBase * canvas, KisToolRectangleBase::ToolType type, const QCursor & cursor)
: KisToolShape(canvas, cursor)
, m_dragStart(0, 0)
, m_dragEnd(0, 0)
, m_type(type)
, m_isRatioForced(false)
, m_isWidthForced(false)
, m_isHeightForced(false)
, m_listenToModifiers(true)
, m_forcedRatio(1.0)
, m_forcedWidth(0)
, m_forcedHeight(0)
{
}
QList<QPointer<QWidget> > KisToolRectangleBase::createOptionWidgets()
{
- QList<QPointer<QWidget> > widgetsList = KoToolBase::createOptionWidgets();
+ QList<QPointer<QWidget> > widgetsList = KisToolShape::createOptionWidgets();
widgetsList.append(new KisRectangleConstraintWidget(0, this));
return widgetsList;
}
void KisToolRectangleBase::constraintsChanged(bool forceRatio, bool forceWidth, bool forceHeight, float ratio, float width, float height)
{
m_isWidthForced = forceWidth;
m_isHeightForced = forceHeight;
m_isRatioForced = forceRatio;
m_forcedHeight = height;
m_forcedWidth = width;
m_forcedRatio = ratio;
// Avoid division by zero in size calculations
if (ratio < 0.0001f) m_isRatioForced = false;
}
void KisToolRectangleBase::paint(QPainter& gc, const KoViewConverter &converter)
{
if(mode() == KisTool::PAINT_MODE) {
paintRectangle(gc, createRect(m_dragStart, m_dragEnd));
}
KisToolPaint::paint(gc, converter);
}
void KisToolRectangleBase::deactivate()
{
updateArea();
KisToolShape::deactivate();
}
void KisToolRectangleBase::listenToModifiers(bool listen)
{
m_listenToModifiers = listen;
}
bool KisToolRectangleBase::listeningToModifiers()
{
return m_listenToModifiers;
}
void KisToolRectangleBase::beginPrimaryAction(KoPointerEvent *event)
{
if ((m_type == PAINT && (!nodeEditable() || nodePaintAbility() == NONE)) ||
(m_type == SELECT && !selectionEditable())) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false);
m_dragStart = m_dragCenter = pos;
QSizeF area = QSizeF(0,0);
applyConstraints(area, false);
m_dragEnd.setX(m_dragStart.x() + area.width());
m_dragEnd.setY(m_dragStart.y() + area.height());
event->accept();
}
bool KisToolRectangleBase::isFixedSize() {
if (m_isWidthForced && m_isHeightForced) return true;
if (m_isRatioForced && (m_isWidthForced || m_isHeightForced)) return true;
return false;
}
void KisToolRectangleBase::applyConstraints(QSizeF &area, bool overrideRatio) {
if (m_isWidthForced) {
area.setWidth(m_forcedWidth);
}
if (m_isHeightForced) {
area.setHeight(m_forcedHeight);
}
if (m_isHeightForced && m_isWidthForced) return;
if (m_isRatioForced || overrideRatio) {
float ratio = m_isRatioForced ? m_forcedRatio : 1.0f;
if (m_isWidthForced) {
area.setHeight(area.width() / ratio);
} else {
area.setWidth(area.height() * ratio);
}
}
}
void KisToolRectangleBase::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
bool constraintToggle = (event->modifiers() & Qt::ShiftModifier) && m_listenToModifiers;
bool translateMode = (event->modifiers() & Qt::AltModifier) && m_listenToModifiers;
bool expandFromCenter = (event->modifiers() & Qt::ControlModifier) && m_listenToModifiers;
bool fixedSize = isFixedSize() && !constraintToggle;
QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false);
if (fixedSize) {
m_dragStart = pos;
} else if (translateMode) {
QPointF trans = pos - m_dragEnd;
m_dragStart += trans;
m_dragEnd += trans;
}
QPointF diag = pos - m_dragStart;
QSizeF area = QSizeF(fabs(diag.x()), fabs(diag.y()));
bool overrideRatio = constraintToggle && !(m_isHeightForced || m_isWidthForced || m_isRatioForced);
if (!constraintToggle || overrideRatio) {
applyConstraints(area, overrideRatio);
}
diag = QPointF(
(diag.x() < 0) ? -area.width() : area.width(),
(diag.y() < 0) ? -area.height() : area.height()
);
// resize around center point?
if (expandFromCenter && !fixedSize) {
m_dragStart = m_dragCenter - diag / 2;
m_dragEnd = m_dragCenter + diag / 2;
} else {
m_dragEnd = m_dragStart + diag;
}
updateArea();
m_dragCenter = QPointF((m_dragStart.x() + m_dragEnd.x()) / 2,
(m_dragStart.y() + m_dragEnd.y()) / 2);
KisToolPaint::requestUpdateOutline(event->point, event);
}
void KisToolRectangleBase::endPrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
updateArea();
finishRect(createRect(m_dragStart, m_dragEnd));
event->accept();
}
QRectF KisToolRectangleBase::createRect(const QPointF &start, const QPointF &end)
{
/**
* To make the dragging user-friendly it should work in a bit
* non-obvious way: the start-drag point must be handled with
* "ceil"/"floor" (depending on the direction of the drag) and the
* end-drag point should follow usual "round" semantics.
*/
qreal x0 = start.x();
qreal y0 = start.y();
qreal x1 = end.x();
qreal y1 = end.y();
int newX0 = qRound(x0);
int newY0 = qRound(y0);
int newX1 = qRound(x1);
int newY1 = qRound(y1);
QRectF result;
result.setCoords(newX0, newY0, newX1, newY1);
return result.normalized();
}
void KisToolRectangleBase::paintRectangle(QPainter &gc, const QRectF &imageRect)
{
KIS_ASSERT_RECOVER_RETURN(canvas());
QRect viewRect = pixelToView(imageRect).toAlignedRect();
QPainterPath path;
path.addRect(viewRect);
paintToolOutline(&gc, path);
}
void KisToolRectangleBase::updateArea() {
const QRectF bound = createRect(m_dragStart, m_dragEnd);
canvas()->updateCanvas(convertToPt(bound).adjusted(-100, -100, +200, +200));
emit rectangleChanged(bound);
}
diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc
index 18fb4c550e..1bf9769f52 100644
--- a/libs/ui/tool/kis_tool_shape.cc
+++ b/libs/ui/tool/kis_tool_shape.cc
@@ -1,248 +1,257 @@
/*
* Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_shape.h"
#include <QWidget>
#include <QLayout>
#include <QComboBox>
#include <QLabel>
#include <QGridLayout>
+#include <KoUnit.h>
#include <KoShape.h>
#include <KoGradientBackground.h>
#include <KoCanvasBase.h>
#include <KoShapeController.h>
#include <KoColorBackground.h>
#include <KoPatternBackground.h>
#include <KoDocumentResourceManager.h>
#include <KoPathShape.h>
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kis_debug.h>
#include <kis_canvas_resource_provider.h>
#include <brushengine/kis_paintop_registry.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <recorder/kis_recorded_paint_action.h>
#include <recorder/kis_recorded_path_paint_action.h>
#include "kis_figure_painting_tool_helper.h"
#include <kis_system_locker.h>
#include <recorder/kis_node_query_path.h>
#include <recorder/kis_action_recorder.h>
KisToolShape::KisToolShape(KoCanvasBase * canvas, const QCursor & cursor)
: KisToolPaint(canvas, cursor)
{
m_shapeOptionsWidget = 0;
}
KisToolShape::~KisToolShape()
{
// in case the widget hasn't been shown
if (m_shapeOptionsWidget && !m_shapeOptionsWidget->parent()) {
delete m_shapeOptionsWidget;
}
}
void KisToolShape::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisToolPaint::activate(toolActivation, shapes);
m_configGroup = KSharedConfig::openConfig()->group(toolId());
}
int KisToolShape::flags() const
{
return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET;
}
QWidget * KisToolShape::createOptionWidget()
{
m_shapeOptionsWidget = new WdgGeometryOptions(0);
m_shapeOptionsWidget->cmbOutline->setCurrentIndex(KisPainter::StrokeStyleBrush);
//connect two combo box event. Inherited classes can call the slots to make appropriate changes
connect(m_shapeOptionsWidget->cmbOutline, SIGNAL(currentIndexChanged(int)), this, SLOT(outlineSettingChanged(int)));
connect(m_shapeOptionsWidget->cmbFill, SIGNAL(currentIndexChanged(int)), this, SLOT(fillSettingChanged(int)));
m_shapeOptionsWidget->cmbOutline->setCurrentIndex(m_configGroup.readEntry("outlineType", 0));
m_shapeOptionsWidget->cmbFill->setCurrentIndex(m_configGroup.readEntry("fillType", 0));
//if both settings are empty, force the outline to brush so the tool will work when first activated
if ( m_shapeOptionsWidget->cmbFill->currentIndex() == 0 &&
m_shapeOptionsWidget->cmbOutline->currentIndex() == 0)
{
m_shapeOptionsWidget->cmbOutline->setCurrentIndex(1); // brush
}
return m_shapeOptionsWidget;
}
void KisToolShape::outlineSettingChanged(int value)
{
m_configGroup.writeEntry("outlineType", value);
}
void KisToolShape::fillSettingChanged(int value)
{
m_configGroup.writeEntry("fillType", value);
}
KisPainter::FillStyle KisToolShape::fillStyle(void)
{
if (m_shapeOptionsWidget) {
return static_cast<KisPainter::FillStyle>(m_shapeOptionsWidget->cmbFill->currentIndex());
} else {
return KisPainter::FillStyleNone;
}
}
KisPainter::StrokeStyle KisToolShape::strokeStyle(void)
{
if (m_shapeOptionsWidget) {
return static_cast<KisPainter::StrokeStyle>(m_shapeOptionsWidget->cmbOutline->currentIndex());
} else {
return KisPainter::StrokeStyleNone;
}
}
+qreal KisToolShape::currentStrokeWidth() const
+{
+ const qreal sizeInPx =
+ canvas()->resourceManager()->resource(KisCanvasResourceProvider::Size).toReal();
+
+ return canvas()->unit().fromUserValue(sizeInPx);
+}
+
void KisToolShape::setupPaintAction(KisRecordedPaintAction* action)
{
KisToolPaint::setupPaintAction(action);
action->setFillStyle(fillStyle());
action->setStrokeStyle(strokeStyle());
action->setGenerator(currentGenerator());
action->setPattern(currentPattern());
action->setGradient(currentGradient());
}
void KisToolShape::addShape(KoShape* shape)
{
KoImageCollection* imageCollection = canvas()->shapeController()->resourceManager()->imageCollection();
switch(fillStyle()) {
case KisPainter::FillStyleForegroundColor:
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(currentFgColor().toQColor())));
break;
case KisPainter::FillStyleBackgroundColor:
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(currentBgColor().toQColor())));
break;
case KisPainter::FillStylePattern:
if (imageCollection) {
QSharedPointer<KoPatternBackground> fill(new KoPatternBackground(imageCollection));
if (currentPattern()) {
fill->setPattern(currentPattern()->pattern());
shape->setBackground(fill);
}
} else {
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
}
break;
case KisPainter::FillStyleGradient:
{
QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1));
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
gradient->setStops(currentGradient()->toQGradient()->stops());
QSharedPointer<KoGradientBackground> gradientFill(new KoGradientBackground(gradient));
shape->setBackground(gradientFill);
}
break;
case KisPainter::FillStyleNone:
default:
shape->setBackground(QSharedPointer<KoShapeBackground>(0));
break;
}
KUndo2Command * cmd = canvas()->shapeController()->addShape(shape);
canvas()->addCommand(cmd);
}
void KisToolShape::addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name)
{
KisNodeSP node = currentNode();
if (!node || node->systemLocked()) {
return;
}
// Get painting options
KisPaintOpPresetSP preset = currentPaintOpPreset();
// Compute the outline
- KisImageWSP image = this->image();
+ KisImageSP image = this->image();
QTransform matrix;
matrix.scale(image->xRes(), image->yRes());
matrix.translate(pathShape->position().x(), pathShape->position().y());
QPainterPath mapedOutline = matrix.map(pathShape->outline());
// Recorde the paint action
KisRecordedPathPaintAction bezierCurvePaintAction(
KisNodeQueryPath::absolutePath(node),
preset );
bezierCurvePaintAction.setPaintColor(currentFgColor());
QPointF lastPoint, nextPoint;
int elementCount = mapedOutline.elementCount();
for (int i = 0; i < elementCount; i++) {
QPainterPath::Element element = mapedOutline.elementAt(i);
switch (element.type) {
case QPainterPath::MoveToElement:
if (i != 0) {
qFatal("Unhandled"); // XXX: I am not sure the tool can produce such element, deal with it when it can
}
lastPoint = QPointF(element.x, element.y);
break;
case QPainterPath::LineToElement:
nextPoint = QPointF(element.x, element.y);
bezierCurvePaintAction.addLine(KisPaintInformation(lastPoint), KisPaintInformation(nextPoint));
lastPoint = nextPoint;
break;
case QPainterPath::CurveToElement:
nextPoint = QPointF(mapedOutline.elementAt(i + 2).x, mapedOutline.elementAt(i + 2).y);
bezierCurvePaintAction.addCurve(KisPaintInformation(lastPoint),
QPointF(mapedOutline.elementAt(i).x,
mapedOutline.elementAt(i).y),
QPointF(mapedOutline.elementAt(i + 1).x,
mapedOutline.elementAt(i + 1).y),
KisPaintInformation(nextPoint));
lastPoint = nextPoint;
break;
default:
continue;
}
}
image->actionRecorder()->addAction(bezierCurvePaintAction);
if (node->hasEditablePaintDevice()) {
KisSystemLocker locker(node);
KisFigurePaintingToolHelper helper(name,
image,
node,
canvas()->resourceManager(),
strokeStyle(),
fillStyle());
helper.paintPainterPath(mapedOutline);
} else if (node->inherits("KisShapeLayer")) {
pathShape->normalize();
addShape(pathShape);
}
notifyModified();
}
diff --git a/libs/ui/tool/kis_tool_shape.h b/libs/ui/tool/kis_tool_shape.h
index 7a14cf2cb0..5aee0e5782 100644
--- a/libs/ui/tool/kis_tool_shape.h
+++ b/libs/ui/tool/kis_tool_shape.h
@@ -1,84 +1,86 @@
/*
* Copyright (c) 2005 Adrian Page <adrian@pagenet.plus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_SHAPE_H_
#define KIS_TOOL_SHAPE_H_
#include <kritaui_export.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include "kis_tool_paint.h"
#include "kis_painter.h"
#include "ui_wdggeometryoptions.h"
class KoCanvasBase;
class KoPathShape;
class WdgGeometryOptions : public QWidget, public Ui::WdgGeometryOptions
{
Q_OBJECT
public:
WdgGeometryOptions(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
/**
* Base for tools specialized in drawing shapes
*/
class KRITAUI_EXPORT KisToolShape : public KisToolPaint
{
Q_OBJECT
public:
KisToolShape(KoCanvasBase * canvas, const QCursor & cursor);
virtual ~KisToolShape();
virtual int flags() const;
WdgGeometryOptions *m_shapeOptionsWidget;
public Q_SLOTS:
virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
virtual void outlineSettingChanged(int value);
virtual void fillSettingChanged(int value);
protected:
QWidget* createOptionWidget();
virtual KisPainter::FillStyle fillStyle();
KisPainter::StrokeStyle strokeStyle();
+ qreal currentStrokeWidth() const;
+
virtual void setupPaintAction(KisRecordedPaintAction* action);
void addShape(KoShape* shape);
void addPathShape(KoPathShape* pathShape, const KUndo2MagicString& name);
KConfigGroup m_configGroup;
};
#endif // KIS_TOOL_SHAPE_H_
diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp
new file mode 100644
index 0000000000..23858a32ae
--- /dev/null
+++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2017 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_document_aware_spin_box_unit_manager.h"
+
+#include "KisPart.h"
+#include "KisMainWindow.h"
+#include "KisView.h"
+#include "KisDocument.h"
+#include "kis_types.h"
+#include "kis_image.h"
+#include "kis_image_animation_interface.h"
+#include "kis_time_range.h"
+
+
+KisSpinBoxUnitManager* KisDocumentAwareSpinBoxUnitManagerBuilder::buildUnitManager(QObject* parent)
+{
+ return new KisDocumentAwareSpinBoxUnitManager(parent);
+}
+
+void KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox, bool setUnitFromOutsideToggle)
+{
+ KisDocumentAwareSpinBoxUnitManager* manager = new KisDocumentAwareSpinBoxUnitManager(spinBox);
+ spinBox->setUnitManager(manager);
+ spinBox->setUnitChangeFromOutsideBehavior(setUnitFromOutsideToggle);
+}
+
+KisDoubleParseUnitSpinBox* KisDocumentAwareSpinBoxUnitManager::createUnitSpinBoxWithDocumentAwarness(QWidget* parent)
+{
+ KisDoubleParseUnitSpinBox* spinBox = new KisDoubleParseUnitSpinBox(parent);
+ setDocumentAwarnessToExistingUnitSpinBox(spinBox);
+
+ return spinBox;
+}
+
+KisDocumentAwareSpinBoxUnitManager::KisDocumentAwareSpinBoxUnitManager(QObject *parent, int pPixDir):
+ KisSpinBoxUnitManager(parent)
+{
+ if (pPixDir == PIX_DIR_Y) {
+ pixDir = PIX_DIR_Y;
+ } else {
+ pixDir = PIX_DIR_X;
+ }
+
+ grantDocumentRelativeUnits(); //the purpose of this class is to manage document relative units.
+}
+
+
+qreal KisDocumentAwareSpinBoxUnitManager::getConversionFactor(int dim, QString symbol) const
+{
+ qreal factor = KisSpinBoxUnitManager::getConversionFactor(dim, symbol);
+
+ if (factor > 0) {
+ //no errors occured at a lower level, so the conversion factor has been get.
+ return factor;
+ }
+
+ factor = 1; //fall back to something natural in case document is unreachable (1 px = 1 pt = 1vw = 1vh). So a virtual document of 100x100 with a resolution of 1.
+
+ KisView* view = KisPart::instance()->currentMainwindow()->activeView();
+
+ if (view == nullptr) {
+ return factor;
+ }
+
+ KisDocument* doc = view->document();
+
+ if (doc == nullptr) {
+ return factor;
+ }
+
+ KisImage* img = doc->image().data();
+
+ if (img == nullptr) {
+ return factor;
+ }
+
+ qreal resX = img->xRes();
+ qreal resY = img->yRes();
+ qreal sizeX = img->width();
+ qreal sizeY = img->height();
+
+ switch (dim) {
+
+ case LENGTH:
+ if (symbol == "px") {
+
+ if (pixDir == PIX_DIR_X) {
+ factor = resX;
+ } else {
+ factor = resY;
+ }
+ } else if (symbol == "vw") {
+ qreal docWidth = sizeX/resX;
+
+ factor = 100.0/docWidth; //1 vw is 1% of document width, 1 vw in point is docWidth/100 so 1 point in vw is the inverse.
+ } else if (symbol == "vh") {
+ qreal docHeight = sizeY/resY;
+
+ factor = 100.0/docHeight;
+ }
+ break;
+
+ case IMLENGTH:
+
+ if (symbol == "vw") {
+ factor = 100.0/sizeX; //1 vw is 1% of document width, 1 vw in pixel is sizeX/100 so 1 pixel in vw is the inverse.
+
+ } else if (symbol == "vh") {
+ factor = 100.0/sizeY;
+ }
+ break;
+
+ case TIME:
+ {
+ if (symbol == "s") {
+ qreal fps = img->animationInterface()->framerate();
+
+ factor = 1/fps;
+ } else if (symbol == "%") {
+ const KisTimeRange & time_range = img->animationInterface()->fullClipRange();
+ qreal n_frame = time_range.end() - time_range.start();
+
+ factor = 100/n_frame;
+ }
+ }
+ break;
+
+ default:
+ break;
+
+ }
+
+ return factor;
+}
+
+qreal KisDocumentAwareSpinBoxUnitManager::getConversionConstant(int dim, QString symbol) const
+{
+ if (dim == TIME && symbol == "%") {
+ KisImage* img = KisPart::instance()->currentMainwindow()->activeView()->document()->image().data();
+ const KisTimeRange & time_range = img->animationInterface()->fullClipRange();
+ qreal n_frame = time_range.end() - time_range.start();
+
+ return -time_range.start()*100.0/n_frame;
+ }
+
+ return KisSpinBoxUnitManager::getConversionConstant(dim, symbol);
+}
diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h
new file mode 100644
index 0000000000..1456ed692a
--- /dev/null
+++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#ifndef KISDOCUMENTAWARESPINBOXUNITMANAGER_H
+#define KISDOCUMENTAWARESPINBOXUNITMANAGER_H
+
+#include "kis_spin_box_unit_manager.h"
+#include "kis_double_parse_unit_spin_box.h"
+
+#include "kritaui_export.h"
+class KisDocumentAwareSpinBoxUnitManagerBuilder : public KisSpinBoxUnitManagerBuilder
+{
+
+public:
+
+ KisSpinBoxUnitManager* buildUnitManager(QObject* parent);
+};
+
+/*!
+ * \brief The KisDocumentAwareSpinBoxUnitManager class is a KisSpinBoxUnitManager that is able to connect to the current document to compute transformation for document relative units (the ones that depend of the resolution, or the size in pixels of the image).
+ * \see KisSpinBoxUnitManager
+ */
+class KRITAUI_EXPORT KisDocumentAwareSpinBoxUnitManager : public KisSpinBoxUnitManager
+{
+ Q_OBJECT
+
+public:
+
+ enum PixDir {
+ PIX_DIR_X,
+ PIX_DIR_Y
+ }; //in case the image has not the same x and y resolution, indicate on which direction get the resolution.
+
+ //! \brief configure a KisDocumentAwareSpinBoxUnitManager for the given spinbox (make the manager a child of the spinbox and attach it to the spinbox).
+ static void setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox, bool setUnitFromOutsideToggle = false);
+
+ //! \brief create a unitSpinBox that is already document aware.
+ static KisDoubleParseUnitSpinBox* createUnitSpinBoxWithDocumentAwarness(QWidget* parent = 0);
+
+ KisDocumentAwareSpinBoxUnitManager(QObject *parent = 0, int pPixDir = PIX_DIR_X);
+
+ //! \reimp \see KisSpinBoxUnitManager
+ virtual qreal getConversionFactor(int dim, QString symbol) const;
+ //! \reimp \see KisSpinBoxUnitManager
+ virtual qreal getConversionConstant(int dim, QString symbol) const;
+
+private:
+
+ PixDir pixDir;
+};
+
+#endif // KISDOCUMENTAWARESPINBOXUNITMANAGER_H
diff --git a/libs/ui/widgets/KoDualColorButton.cpp b/libs/ui/widgets/KoDualColorButton.cpp
index 5e4f717063..7cedac6da9 100644
--- a/libs/ui/widgets/KoDualColorButton.cpp
+++ b/libs/ui/widgets/KoDualColorButton.cpp
@@ -1,381 +1,394 @@
/* 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 "dialogs/kis_dlg_internal_color_selector.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().foreground().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;
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 )->start();
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) {
+ if (foregroundRect.contains( event->pos())) {
+ if (d->tmpSelection == Foreground) {
+ if (d->popDialog) {
+#ifndef Q_OS_OSX
d->colorSelectorDialog->setPreviousColor(d->foregroundColor);
//this should toggle, but I don't know how to implement that...
d->colorSelectorDialog->show();
+#else
+ QColor c = d->displayRenderer->toQColor(d->foregroundColor);
+ c = QColorDialog::getColor(c, this);
+ if (c.isValid()) {
+ d->foregroundColor = d->displayRenderer->approximateFromRenderedQColor(c);
+ emit foregroundColorChanged(d->foregroundColor);
+ }
+#endif
}
- else
+ else {
emit pleasePopDialog( d->foregroundColor);
+ }
}
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_OSX
KoColor c = d->backgroundColor;
c = KisDlgInternalColorSelector::getModalColorDialog(c, this);
d->backgroundColor = c;
emit backgroundColorChanged(d->backgroundColor);
- /*QColor c = d->displayRenderer->toQColor(d->backgroundColor);
+#else
+ QColor c = d->displayRenderer->toQColor(d->backgroundColor);
c = QColorDialog::getColor(c, this);
if (c.isValid()) {
d->backgroundColor = d->displayRenderer->approximateFromRenderedQColor(c);
emit backgroundColorChanged(d->backgroundColor);
- }*/
+ }
+#endif
}
else
emit pleasePopDialog( d->backgroundColor);
} 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
new file mode 100644
index 0000000000..0a4a4d0775
--- /dev/null
+++ b/libs/ui/widgets/KoFillConfigWidget.cpp
@@ -0,0 +1,749 @@
+/* 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 "KoResourceSelector.h"
+#include <KoSelection.h>
+#include <KoCanvasBase.h>
+#include <KoCanvasResourceManager.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 <KoCanvasResourceManager.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),
+ fillVariant(_fillVariant),
+ noSelectionTrackingMode(false)
+ {
+ }
+
+ KoColorPopupAction *colorAction;
+ KoResourcePopupAction *gradientAction;
+ KoResourcePopupAction *patternAction;
+ QButtonGroup *group;
+
+ KoCanvasBase *canvas;
+
+ KisSignalCompressor colorChangedCompressor;
+ KisAcyclicSignalConnector shapeChangedAcyclicConnector;
+ KisAcyclicSignalConnector resourceManagerAcyclicConnector;
+
+ QSharedPointer<KoStopGradient> activeGradient;
+ KisSignalCompressor gradientChangedCompressor;
+ KoFlake::FillVariant fillVariant;
+
+ bool noSelectionTrackingMode;
+
+ Ui_KoFillConfigWidget *ui;
+
+ std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
+};
+
+KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, QWidget *parent)
+ : QWidget(parent)
+ , d(new Private(fillVariant))
+{
+ d->canvas = canvas;
+
+ d->shapeChangedAcyclicConnector.connectBackwardVoid(
+ d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
+ this, SLOT(shapeChanged()));
+
+ d->shapeChangedAcyclicConnector.connectBackwardVoid(
+ d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
+ 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()));
+
+
+ // confure 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->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);
+
+ connect(d->colorAction, SIGNAL(colorChanged(const 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->ui->stackWidget, SIGNAL(currentChanged(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) {
+ shapeChanged();
+ } else {
+ loadCurrentFillFromResourceServer();
+ }
+}
+
+void KoFillConfigWidget::deactivate()
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty());
+ d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector));
+ d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector));
+}
+
+
+void KoFillConfigWidget::setNoSelectionTrackingMode(bool value)
+{
+ d->noSelectionTrackingMode = value;
+ if (!d->noSelectionTrackingMode) {
+ shapeChanged();
+ }
+}
+
+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 == KoCanvasResourceManager::ForegroundColor && d->fillVariant == KoFlake::Fill) ||
+ (key == KoCanvasResourceManager::BackgroundColor &&
+ d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) ||
+ (key == KoCanvasResourceManager::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->ui->stackWidget->setCurrentIndex(Solid);
+ d->colorAction->setCurrentColor(color);
+ d->colorChangedCompressor.start();
+ } else if (checkedId == Gradient && key == KoCanvasResourceManager::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();
+}
+
+void KoFillConfigWidget::styleButtonPressed(int buttonId)
+{
+ switch (buttonId) {
+ case KoFillConfigWidget::None:
+ noColorSelected();
+ break;
+ case KoFillConfigWidget::Solid:
+ colorChanged();
+ break;
+ case KoFillConfigWidget::Gradient:
+ if (d->activeGradient) {
+ activeGradientChanged();
+ } 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;
+ }
+
+ if (buttonId >= None && buttonId <= Pattern) {
+ d->ui->stackWidget->setCurrentIndex(buttonId);
+ }
+}
+
+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);
+ }
+
+ emit sigFillChanged();
+}
+
+void KoFillConfigWidget::colorChanged()
+{
+ KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector);
+
+ QList<KoShape*> selectedShapes = currentShapes();
+ if (selectedShapes.isEmpty()) {
+ emit sigInternalRequestColorToResourceManager();
+ emit sigFillChanged();
+ return;
+ }
+
+ KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant);
+ KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor());
+
+ if (command) {
+ d->canvas->addCommand(command);
+ }
+
+ emit sigInternalRequestColorToResourceManager();
+ emit sigFillChanged();
+}
+
+void KoFillConfigWidget::slotProposeCurrentColorToResourceManager()
+{
+ const int checkedId = d->group->checkedId();
+
+ bool hasColor = false;
+ KoColor color;
+ KoCanvasResourceManager::CanvasResource colorSlot = KoCanvasResourceManager::ForegroundColor;
+
+
+ if (checkedId == Solid) {
+ if (d->fillVariant == KoFlake::StrokeFill) {
+ colorSlot = KoCanvasResourceManager::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) {
+ d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color));
+ }
+}
+
+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(KoCanvasResourceManager::BackgroundColor, QVariant::fromValue(color));
+ }
+
+ {
+ KoColor color = d->canvas->resourceManager()->foregroundColor();
+ slotCanvasResourceChanged(KoCanvasResourceManager::ForegroundColor, QVariant::fromValue(color));
+ }
+
+ Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
+ button->setEnabled(true);
+ }
+
+ emit sigFillChanged();
+}
+
+void KoFillConfigWidget::shapeChanged()
+{
+ QList<KoShape*> shapes = currentShapes();
+
+ if (shapes.isEmpty() ||
+ (shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) {
+
+ Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
+ button->setEnabled(!shapes.isEmpty());
+ }
+
+ d->group->button(None)->setChecked(true);
+ d->ui->stackWidget->setCurrentIndex(None);
+
+ } else {
+ Q_FOREACH (QAbstractButton *button, d->group->buttons()) {
+ button->setEnabled(true);
+ }
+
+ KoShape *shape = shapes.first();
+ updateWidget(shape);
+ }
+}
+
+void KoFillConfigWidget::updateWidget(KoShape *shape)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
+
+ StyleButton newActiveButton = None;
+ KoShapeFillWrapper wrapper(shape, d->fillVariant);
+
+ switch (wrapper.type()) {
+ case KoFlake::None:
+ break;
+ case KoFlake::Solid: {
+ QColor color = wrapper.color();
+ if (d->group->checkedId() == KoFillConfigWidget::Solid || color.alpha() > 0) {
+ newActiveButton = KoFillConfigWidget::Solid;
+ d->colorAction->setCurrentColor(wrapper.color());
+ }
+ break;
+ }
+ case KoFlake::Gradient:
+ newActiveButton = KoFillConfigWidget::Gradient;
+ uploadNewGradientBackground(wrapper.gradient());
+ updateGradientSaveButtonAvailability();
+ break;
+ case KoFlake::Pattern:
+ newActiveButton = KoFillConfigWidget::Pattern;
+ break;
+ }
+
+ d->group->button(newActiveButton)->setChecked(true);
+ d->ui->stackWidget->setCurrentIndex(newActiveButton);
+}
diff --git a/libs/ui/widgets/KoFillConfigWidget.h b/libs/ui/widgets/KoFillConfigWidget.h
new file mode 100644
index 0000000000..53306e9c40
--- /dev/null
+++ b/libs/ui/widgets/KoFillConfigWidget.h
@@ -0,0 +1,106 @@
+/* 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.
+ */
+
+#ifndef FILLCONFIGWIDGET_H
+#define FILLCONFIGWIDGET_H
+
+#include "kritaui_export.h"
+
+#include <QWidget>
+#include <QSharedPointer>
+#include <KoFlake.h>
+#include <KoFlakeTypes.h>
+
+class KoCanvasBase;
+class KoShapeBackground;
+class KoShape;
+
+/// A widget for configuring the fill of a shape
+class KRITAUI_EXPORT KoFillConfigWidget : public QWidget
+{
+ Q_OBJECT
+ enum StyleButton {
+ None = 0,
+ Solid,
+ Gradient,
+ Pattern
+ };
+
+public:
+ explicit KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, QWidget *parent);
+ ~KoFillConfigWidget();
+
+ void setNoSelectionTrackingMode(bool value);
+
+ /// Returns the list of the selected shape
+ /// If you need to use only one shape, call currentShape()
+ QList<KoShape*> currentShapes();
+
+ KoShapeStrokeSP createShapeStroke();
+
+ void activate();
+ void deactivate();
+
+private Q_SLOTS:
+ void styleButtonPressed(int buttonId);
+
+ void noColorSelected();
+
+ /// apply color changes to the selected shape
+ void colorChanged();
+
+ /// the pattern of the fill changed, apply the changes
+ void patternChanged(QSharedPointer<KoShapeBackground> background);
+
+ void shapeChanged();
+
+ void slotUpdateFillTitle();
+
+ void slotCanvasResourceChanged(int key, const QVariant &value);
+
+ void slotSavePredefinedGradientClicked();
+
+ void activeGradientChanged();
+ void gradientResourceChanged();
+
+ void slotGradientTypeChanged();
+ void slotGradientRepeatChanged();
+
+ void slotProposeCurrentColorToResourceManager();
+
+Q_SIGNALS:
+ void sigFillChanged();
+
+ void sigInternalRequestColorToResourceManager();
+
+private:
+ /// update the widget with the KoShape background
+ void updateWidget(KoShape *shape);
+
+ void uploadNewGradientBackground(const QGradient *gradient);
+ void setNewGradientBackgroundToShape();
+ void updateGradientSaveButtonAvailability();
+ void loadCurrentFillFromResourceServer();
+
+ class Private;
+ Private * const d;
+};
+
+#endif // FILLCONFIGWIDGET_H
diff --git a/libs/ui/widgets/KoFillConfigWidget.ui b/libs/ui/widgets/KoFillConfigWidget.ui
new file mode 100644
index 0000000000..ab910263f6
--- /dev/null
+++ b/libs/ui/widgets/KoFillConfigWidget.ui
@@ -0,0 +1,375 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>KoFillConfigWidget</class>
+ <widget class="QWidget" name="KoFillConfigWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>779</width>
+ <height>697</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_3">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QToolButton" name="btnNoFill">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>No fill</string>
+ </property>
+ <property name="text">
+ <string>None</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnSolidFill">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Solid color fill</string>
+ </property>
+ <property name="text">
+ <string>Solid</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnGradientFill">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Gradient fill</string>
+ </property>
+ <property name="text">
+ <string>Gradient</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnPatternFill">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Pattern fill</string>
+ </property>
+ <property name="text">
+ <string>Pattern</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>5</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="lblFillTitle">
+ <property name="text">
+ <string notr="true">TypeText</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>10</width>
+ <height>5</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="stackWidget">
+ <property name="currentIndex">
+ <number>2</number>
+ </property>
+ <widget class="QWidget" name="pageNone"/>
+ <widget class="QWidget" name="pageSolid">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="KoColorPopupButton" name="btnChooseSolidColor">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::NoArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnSolidColorPick">
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pageGradient">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="KisStopGradientEditor" name="wdgGradientEditor" native="true"/>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="cmbGradientType">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>Linear</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Radial</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Repeat:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="cmbGradientRepeat">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <item>
+ <property name="text">
+ <string>None</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Repeat</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Reflect</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="KoColorPopupButton" name="btnChoosePredefinedGradient">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="popupMode">
+ <enum>QToolButton::InstantPopup</enum>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ <property name="arrowType">
+ <enum>Qt::NoArrow</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="btnSaveGradient">
+ <property name="text">
+ <string>...</string>
+ </property>
+ <property name="checkable">
+ <bool>false</bool>
+ </property>
+ <property name="autoRaise">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="pagePattern"/>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>13</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KoColorPopupButton</class>
+ <extends>QToolButton</extends>
+ <header>KoColorPopupButton.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KisStopGradientEditor</class>
+ <extends>QWidget</extends>
+ <header>kis_stopgradient_editor.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/libs/ui/widgets/KoShapeFillWrapper.cpp b/libs/ui/widgets/KoShapeFillWrapper.cpp
new file mode 100644
index 0000000000..4430eefada
--- /dev/null
+++ b/libs/ui/widgets/KoShapeFillWrapper.cpp
@@ -0,0 +1,370 @@
+/*
+ * 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 "KoShapeFillWrapper.h"
+
+#include <KoShape.h>
+#include <QList>
+#include <QBrush>
+#include <KoColorBackground.h>
+#include <KoGradientBackground.h>
+#include <KoPatternBackground.h>
+#include <KoShapeStroke.h>
+#include <KoShapeBackgroundCommand.h>
+#include <KoShapeStrokeCommand.h>
+#include <KoStopGradient.h>
+
+#include "kis_assert.h"
+#include "kis_debug.h"
+#include "kis_global.h"
+
+#include <KoFlakeUtils.h>
+
+struct ShapeBackgroundFetchPolicy
+{
+ typedef KoFlake::FillType Type;
+
+ typedef QSharedPointer<KoShapeBackground> PointerType;
+ static PointerType getBackground(KoShape *shape) {
+ return shape->background();
+ }
+ static Type type(KoShape *shape) {
+ QSharedPointer<KoShapeBackground> background = shape->background();
+ QSharedPointer<KoColorBackground> colorBackground = qSharedPointerDynamicCast<KoColorBackground>(background);
+ QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(background);
+ QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(background);
+
+ return colorBackground ? Type::Solid :
+ gradientBackground ? Type::Gradient :
+ patternBackground ? Type::Pattern : Type::None;
+ }
+
+ static QColor color(KoShape *shape) {
+ QSharedPointer<KoColorBackground> colorBackground = qSharedPointerDynamicCast<KoColorBackground>(shape->background());
+ return colorBackground ? colorBackground->color() : QColor();
+ }
+
+ static const QGradient* gradient(KoShape *shape) {
+ QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
+ return gradientBackground ? gradientBackground->gradient() : 0;
+ }
+
+ static QTransform gradientTransform(KoShape *shape) {
+ QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
+ return gradientBackground ? gradientBackground->transform() : QTransform();
+ }
+
+ static bool compareTo(PointerType p1, PointerType p2) {
+ return p1->compareTo(p2.data());
+ }
+};
+
+struct ShapeStrokeFillFetchPolicy
+{
+ typedef KoFlake::FillType Type;
+
+ typedef KoShapeStrokeModelSP PointerType;
+ static PointerType getBackground(KoShape *shape) {
+ return shape->stroke();
+ }
+ static Type type(KoShape *shape) {
+ KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
+ if (!stroke) return Type::None;
+
+ // TODO: patterns are not supported yet!
+ return stroke->lineBrush().gradient() ? Type::Gradient : Type::Solid;
+ }
+
+ static QColor color(KoShape *shape) {
+ KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
+ return stroke ? stroke->color() : QColor();
+ }
+
+ static const QGradient* gradient(KoShape *shape) {
+ KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
+ return stroke ? stroke->lineBrush().gradient() : 0;
+ }
+
+ static QTransform gradientTransform(KoShape *shape) {
+ KoShapeStrokeSP stroke = qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke());
+ return stroke ? stroke->lineBrush().transform() : QTransform();
+ }
+
+ static bool compareTo(PointerType p1, PointerType p2) {
+ return p1->compareFillTo(p2.data());
+ }
+};
+
+
+template <class Policy>
+bool compareBackgrounds(const QList<KoShape*> shapes)
+{
+ if (shapes.size() == 1) return true;
+
+ typename Policy::PointerType bg =
+ Policy::getBackground(shapes.first());
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ if (
+ !(
+ (!bg && !Policy::getBackground(shape)) ||
+ (bg && Policy::compareTo(bg, Policy::getBackground(shape)))
+ )) {
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/******************************************************************************/
+/* KoShapeFillWrapper::Private */
+/******************************************************************************/
+
+struct KoShapeFillWrapper::Private
+{
+ QList<KoShape*> shapes;
+ KoFlake::FillVariant fillVariant= KoFlake::Fill;
+
+ QSharedPointer<KoShapeBackground> applyFillGradientStops(KoShape *shape, const QGradient *srcQGradient);
+ void applyFillGradientStops(KoShapeStrokeSP shapeStroke, const QGradient *stopGradient);
+};
+
+QSharedPointer<KoShapeBackground> KoShapeFillWrapper::Private::applyFillGradientStops(KoShape *shape, const QGradient *stopGradient)
+{
+ QGradientStops stops = stopGradient->stops();
+
+ if (!shape || !stops.count()) {
+ return QSharedPointer<KoShapeBackground>();
+ }
+
+ KoGradientBackground *newGradient = 0;
+ QSharedPointer<KoGradientBackground> oldGradient = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
+ if (oldGradient) {
+ // just copy the gradient and set the new stops
+ QGradient *g = KoFlake::mergeGradient(oldGradient->gradient(), stopGradient);
+ newGradient = new KoGradientBackground(g);
+ newGradient->setTransform(oldGradient->transform());
+ }
+ else {
+ // No gradient yet, so create a new one.
+ QScopedPointer<QLinearGradient> fakeShapeGradient(new QLinearGradient(QPointF(0, 0), QPointF(1, 1)));
+ fakeShapeGradient->setCoordinateMode(QGradient::ObjectBoundingMode);
+
+ QGradient *g = KoFlake::mergeGradient(fakeShapeGradient.data(), stopGradient);
+ newGradient = new KoGradientBackground(g);
+ }
+ return QSharedPointer<KoGradientBackground>(newGradient);
+}
+
+void KoShapeFillWrapper::Private::applyFillGradientStops(KoShapeStrokeSP shapeStroke, const QGradient *stopGradient)
+{
+ QGradientStops stops = stopGradient->stops();
+ if (!stops.count()) return;
+
+ QLinearGradient fakeShapeGradient(QPointF(0, 0), QPointF(1, 1));
+ fakeShapeGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
+ QTransform gradientTransform;
+ const QGradient *shapeGradient = 0;
+
+ {
+ QBrush brush = shapeStroke->lineBrush();
+ gradientTransform = brush.transform();
+ shapeGradient = brush.gradient() ? brush.gradient() : &fakeShapeGradient;
+ }
+
+ {
+ QScopedPointer<QGradient> g(KoFlake::mergeGradient(shapeGradient, stopGradient));
+ QBrush newBrush = *g;
+ newBrush.setTransform(gradientTransform);
+ shapeStroke->setLineBrush(newBrush);
+ }
+}
+
+/******************************************************************************/
+/* KoShapeFillWrapper */
+/******************************************************************************/
+
+KoShapeFillWrapper::KoShapeFillWrapper(KoShape *shape, KoFlake::FillVariant fillVariant)
+ : m_d(new Private())
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(shape);
+ m_d->shapes << shape;
+ m_d->fillVariant= fillVariant;
+}
+
+
+KoShapeFillWrapper::KoShapeFillWrapper(QList<KoShape*> shapes, KoFlake::FillVariant fillVariant)
+ : m_d(new Private())
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(!shapes.isEmpty());
+ m_d->shapes = shapes;
+ m_d->fillVariant= fillVariant;
+}
+
+KoShapeFillWrapper::~KoShapeFillWrapper()
+{
+}
+
+bool KoShapeFillWrapper::isMixedFill() const
+{
+ if (m_d->shapes.isEmpty()) return false;
+
+ return m_d->fillVariant == KoFlake::Fill ?
+ !compareBackgrounds<ShapeBackgroundFetchPolicy>(m_d->shapes) :
+ !compareBackgrounds<ShapeStrokeFillFetchPolicy>(m_d->shapes);
+}
+
+KoFlake::FillType KoShapeFillWrapper::type() const
+{
+ if (m_d->shapes.isEmpty() || isMixedFill()) return KoFlake::None;
+
+ KoShape *shape = m_d->shapes.first();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, KoFlake::None);
+
+ return m_d->fillVariant == KoFlake::Fill ?
+ ShapeBackgroundFetchPolicy::type(shape) :
+ ShapeStrokeFillFetchPolicy::type(shape);
+}
+
+QColor KoShapeFillWrapper::color() const
+{
+ // this check guarantees that the shapes list is not empty and
+ // the fill is not mixed!
+ if (type() != KoFlake::Solid) return QColor();
+
+ KoShape *shape = m_d->shapes.first();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, QColor());
+
+ return m_d->fillVariant == KoFlake::Fill ?
+ ShapeBackgroundFetchPolicy::color(shape) :
+ ShapeStrokeFillFetchPolicy::color(shape);
+}
+
+const QGradient* KoShapeFillWrapper::gradient() const
+{
+ // this check guarantees that the shapes list is not empty and
+ // the fill is not mixed!
+ if (type() != KoFlake::Gradient) return 0;
+
+ KoShape *shape = m_d->shapes.first();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, 0);
+
+ return m_d->fillVariant == KoFlake::Fill ?
+ ShapeBackgroundFetchPolicy::gradient(shape) :
+ ShapeStrokeFillFetchPolicy::gradient(shape);
+}
+
+QTransform KoShapeFillWrapper::gradientTransform() const
+{
+ // this check guarantees that the shapes list is not empty and
+ // the fill is not mixed!
+ if (type() != KoFlake::Gradient) return QTransform();
+
+ KoShape *shape = m_d->shapes.first();
+ KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(shape, QTransform());
+
+ return m_d->fillVariant == KoFlake::Fill ?
+ ShapeBackgroundFetchPolicy::gradientTransform(shape) :
+ ShapeStrokeFillFetchPolicy::gradientTransform(shape);
+}
+
+KUndo2Command *KoShapeFillWrapper::setColor(const QColor &color)
+{
+ KUndo2Command *command = 0;
+
+ if (m_d->fillVariant == KoFlake::Fill) {
+ QSharedPointer<KoShapeBackground> bg;
+
+ if (color.isValid()) {
+ bg = toQShared(new KoColorBackground(color));
+ }
+
+ QSharedPointer<KoShapeBackground> fill(bg);
+ command = new KoShapeBackgroundCommand(m_d->shapes, fill);
+ } else {
+ command = KoFlake::modifyShapesStrokes(m_d->shapes,
+ [color] (KoShapeStrokeSP stroke) {
+ stroke->setLineBrush(Qt::NoBrush);
+ stroke->setColor(color.isValid() ? color : Qt::transparent);
+
+ });
+ }
+
+ return command;
+}
+
+KUndo2Command *KoShapeFillWrapper::setGradient(const QGradient *gradient, const QTransform &transform)
+{
+ KUndo2Command *command = 0;
+
+ if (m_d->fillVariant == KoFlake::Fill) {
+ QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
+
+ foreach (KoShape *shape, m_d->shapes) {
+ Q_UNUSED(shape);
+
+ KoGradientBackground *newGradient = new KoGradientBackground(KoFlake::cloneGradient(gradient));
+ newGradient->setTransform(transform);
+ newBackgrounds << toQShared(newGradient);
+ }
+
+ command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
+
+ } else {
+ command = KoFlake::modifyShapesStrokes(m_d->shapes,
+ [gradient, transform] (KoShapeStrokeSP stroke) {
+ QBrush newBrush = *gradient;
+ newBrush.setTransform(transform);
+
+ stroke->setLineBrush(newBrush);
+ stroke->setColor(Qt::transparent);
+ });
+ }
+
+ return command;
+}
+
+KUndo2Command* KoShapeFillWrapper::applyGradient(const QGradient *gradient)
+{
+ return setGradient(gradient, gradientTransform());
+}
+
+KUndo2Command* KoShapeFillWrapper::applyGradientStopsOnly(const QGradient *gradient)
+{
+ KUndo2Command *command = 0;
+
+ if (m_d->fillVariant == KoFlake::Fill) {
+ QList<QSharedPointer<KoShapeBackground>> newBackgrounds;
+
+ foreach (KoShape *shape, m_d->shapes) {
+ newBackgrounds << m_d->applyFillGradientStops(shape, gradient);
+ }
+
+ command = new KoShapeBackgroundCommand(m_d->shapes, newBackgrounds);
+
+ } else {
+ command = KoFlake::modifyShapesStrokes(m_d->shapes,
+ [this, gradient] (KoShapeStrokeSP stroke) {
+ m_d->applyFillGradientStops(stroke, gradient);
+ });
+ }
+
+ return command;
+}
diff --git a/libs/ui/widgets/KoShapeFillWrapper.h b/libs/ui/widgets/KoShapeFillWrapper.h
new file mode 100644
index 0000000000..c014f68c35
--- /dev/null
+++ b/libs/ui/widgets/KoShapeFillWrapper.h
@@ -0,0 +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.
+ */
+
+#ifndef KOSHAPEFILLWRAPPER_H
+#define KOSHAPEFILLWRAPPER_H
+
+#include "kritaui_export.h"
+#include <QScopedPointer>
+#include <QList>
+#include <KoFlake.h>
+
+class KUndo2Command;
+class KoShape;
+class QColor;
+class QTransform;
+class QGradient;
+
+class KRITAUI_EXPORT KoShapeFillWrapper
+{
+public:
+ KoShapeFillWrapper(KoShape *shape, KoFlake::FillVariant fillVariant);
+ KoShapeFillWrapper(QList<KoShape*> shapes, KoFlake::FillVariant fillVariant);
+
+ ~KoShapeFillWrapper();
+
+ bool isMixedFill() const;
+ KoFlake::FillType type() const;
+
+ QColor color() const;
+ const QGradient* gradient() const;
+ QTransform gradientTransform() const;
+
+ KUndo2Command* setColor(const QColor &color);
+
+ KUndo2Command* setGradient(const QGradient *gradient, const QTransform &transform);
+ KUndo2Command* applyGradient(const QGradient *gradient);
+ KUndo2Command* applyGradientStopsOnly(const QGradient *gradient);
+
+private:
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // KOSHAPEFILLWRAPPER_H
diff --git a/libs/ui/widgets/KoStrokeConfigWidget.cpp b/libs/ui/widgets/KoStrokeConfigWidget.cpp
new file mode 100644
index 0000000000..6c7d6dfc04
--- /dev/null
+++ b/libs/ui/widgets/KoStrokeConfigWidget.cpp
@@ -0,0 +1,777 @@
+/* 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>
+#include <QSignalMapper>
+
+// 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 <KoCanvasResourceManager.h>
+#include <KoDocumentResourceManager.h>
+#include <KoSelection.h>
+#include <KoShapeController.h>
+#include <KoShapeStrokeCommand.h>
+#include <KoShapeStrokeModel.h>
+#include <KoSelectedShapesProxy.h>
+#include <KoFillConfigWidget.h>
+#include <KoFlakeUtils.h>
+#include <KoCanvasBase.h>
+
+#include "kis_canvas_resource_provider.h"
+#include "kis_acyclic_signal_connector.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->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()
+ : canvas(0),
+ active(true),
+ allowLocalUnitManagement(true),
+ fillConfigWidget(0),
+ noSelectionTrackingMode(false)
+ {
+ }
+
+ KoLineStyleSelector *lineStyle;
+ KisDoubleParseUnitSpinBox *lineWidth;
+ KoMarkerSelector *startMarkerSelector;
+ KoMarkerSelector *midMarkerSelector;
+ KoMarkerSelector *endMarkerSelector;
+
+ QToolButton *capNJoinButton;
+ CapNJoinMenu *capNJoinMenu;
+
+ QWidget *spacer;
+
+ KoCanvasBase *canvas;
+
+ bool active;
+ bool allowLocalUnitManagement;
+
+ KoFillConfigWidget *fillConfigWidget;
+ bool noSelectionTrackingMode;
+
+ KisAcyclicSignalConnector shapeChangedAcyclicConnector;
+ KisAcyclicSignalConnector resourceManagerAcyclicConnector;
+
+ std::vector<KisAcyclicSignalConnector::Blocker> deactivationLocks;
+};
+
+
+KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent)
+ : QWidget(parent)
+ , d(new Private())
+{
+ setObjectName("Stroke widget");
+ QVBoxLayout *mainLayout = new QVBoxLayout(this);
+ mainLayout->setMargin(2);
+
+ { // connect the canvas
+ d->shapeChangedAcyclicConnector.connectBackwardVoid(
+ canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
+ this, SLOT(selectionChanged()));
+
+ d->shapeChangedAcyclicConnector.connectBackwardVoid(
+ canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()),
+ this, SLOT(selectionChanged()));
+
+ d->resourceManagerAcyclicConnector.connectBackwardResourcePair(
+ canvas->resourceManager(), SIGNAL(canvasResourceChanged(int, const QVariant&)),
+ this, SLOT(canvasResourceChanged(int, const QVariant &)));
+
+ d->canvas = canvas;
+ }
+
+ {
+ QHBoxLayout *markersLineLayout = new QHBoxLayout();
+
+ QList<KoMarker*> emptyMarkers;
+
+ d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this);
+ d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker"));
+ d->startMarkerSelector->updateMarkers(emptyMarkers);
+ markersLineLayout->addWidget(d->startMarkerSelector);
+
+ d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this);
+ d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker"));
+ d->midMarkerSelector->updateMarkers(emptyMarkers);
+ markersLineLayout->addWidget(d->midMarkerSelector);
+
+ d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this);
+ d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker"));
+ d->endMarkerSelector->updateMarkers(emptyMarkers);
+ markersLineLayout->addWidget(d->endMarkerSelector);
+
+ mainLayout->addLayout(markersLineLayout);
+ }
+
+
+ {
+ QHBoxLayout *styleLineLayout = new QHBoxLayout();
+
+ // Line style
+ d->lineStyle = new KoLineStyleSelector(this);
+ d->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style"));
+ d->lineStyle->setMinimumWidth(70);
+ d->lineStyle->setLineStyle(Qt::SolidLine, QVector<qreal>());
+ styleLineLayout->addWidget(d->lineStyle);
+
+ mainLayout->addLayout(styleLineLayout);
+ }
+
+ QHBoxLayout *widthLineLayout = 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);
+ widthLineLayout->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->setUnit(KoUnit(KoUnit::Point));
+ d->lineWidth->setToolTip(i18n("Set line width of actual selection"));
+ widthLineLayout->addWidget(d->lineWidth);
+
+ d->capNJoinButton = new QToolButton(this);
+ d->capNJoinButton->setMinimumHeight(25);
+ d->capNJoinMenu = new CapNJoinMenu(this);
+ d->capNJoinButton->setMenu(d->capNJoinMenu);
+ d->capNJoinButton->setText("...");
+ d->capNJoinButton->setPopupMode(QToolButton::InstantPopup);
+
+ widthLineLayout->addWidget(d->capNJoinButton);
+
+ mainLayout->addLayout(widthLineLayout);
+
+ { // add separator line
+ QFrame* line = new QFrame();
+ line->setFrameShape(QFrame::HLine);
+ mainLayout->addWidget(line);
+ }
+
+ {
+ d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, this);
+ mainLayout->addWidget(d->fillConfigWidget);
+ connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged()));
+
+ d->fillConfigWidget->layout()->setMargin(0);
+ }
+
+ // Spacer
+ d->spacer = new QWidget();
+ d->spacer->setObjectName("SpecialSpacer");
+ mainLayout->addWidget(d->spacer);
+
+ connect(d->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges()));
+ connect(d->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 correclty
+ QSignalMapper *mapper = new QSignalMapper(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());
+ }
+ }
+
+ selectionChanged();
+
+ d->fillConfigWidget->activate();
+ deactivate();
+}
+
+KoStrokeConfigWidget::~KoStrokeConfigWidget()
+{
+ delete d;
+}
+
+void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value)
+{
+ d->fillConfigWidget->setNoSelectionTrackingMode(value);
+ d->noSelectionTrackingMode = value;
+ if (!d->noSelectionTrackingMode) {
+ selectionChanged();
+ }
+}
+
+// ----------------------------------------------------------------
+// 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();
+}
+
+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->lineWidth->setEnabled(enabled);
+ d->capNJoinMenu->setEnabled(enabled);
+ d->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 completly transfered 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));
+ }
+
+ d->lineWidth->setUnit(newUnit);
+ d->capNJoinMenu->miterLimit->setUnit(newUnit);
+
+ d->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->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();
+ } else {
+ loadCurrentStrokeFillFromResourceServer();
+ }
+}
+
+void KoStrokeConfigWidget::deactivate()
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty());
+
+ 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->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->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()
+{
+ KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
+ if (!selection) return;
+
+ QList<KoShape*> shapes = selection->selectedEditableShapes();
+
+ 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->lineWidth->changeValue(stroke->lineWidth());
+ } else {
+ d->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->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes());
+ } else {
+ d->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));
+ }
+ }
+
+ blockChildSignals(false);
+
+ updateStyleControlsAvailability(!shapes.isEmpty());
+}
+
+void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value)
+{
+ switch (key) {
+ case KoCanvasResourceManager::Unit:
+ // we request the whole selection to reload because the
+ // unit of the stroke width depends on the selected shape
+ selectionChanged();
+ break;
+ case KisCanvasResourceProvider::Size:
+ if (d->noSelectionTrackingMode) {
+ d->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/KoStrokeConfigWidget.h b/libs/ui/widgets/KoStrokeConfigWidget.h
new file mode 100644
index 0000000000..6384b52a6a
--- /dev/null
+++ b/libs/ui/widgets/KoStrokeConfigWidget.h
@@ -0,0 +1,114 @@
+/* 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 Rob Buis <buis@kde.org>
+ * Copyright (C) 2004 Laurent Montel <montel@kde.org>
+ * Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
+ * Copyright (C) 2005 Inge Wallin <inge@lysator.liu.se>
+ * Copyright (C) 2005, 2011 Thomas Zander <zander@kde.org>
+ * Copyright (C) 2005-2008 Jan Hambrecht <jaham@gmx.net>
+ * Copyright (C) 2006 C. Boemann <cbo@boemann.dk>
+ * Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
+ * Copyright (C) 2011 Thorsten Zachmann <zachmann@kde.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 STROKECONFIGWIDGET_H
+#define STROKECONFIGWIDGET_H
+
+#include "kritaui_export.h"
+
+#include <QWidget>
+#include <KoFlakeTypes.h>
+#include <KoFlake.h>
+
+class KoUnit;
+class KoShapeStrokeModel;
+class KoShapeStroke;
+class KoMarker;
+class KoCanvasBase;
+class KoShapeStroke;
+
+class KisSpinBoxUnitManager;
+
+/// A widget for configuring the stroke of a shape
+class KRITAUI_EXPORT KoStrokeConfigWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget *parent);
+ ~KoStrokeConfigWidget();
+
+ void setNoSelectionTrackingMode(bool value);
+
+ // Getters
+ Qt::PenStyle lineStyle() const;
+ QVector<qreal> lineDashes() const;
+ qreal lineWidth() const;
+ QColor color() const;
+ qreal miterLimit() const;
+ KoMarker *startMarker() const;
+ KoMarker *endMarker() const;
+ Qt::PenCapStyle capStyle() const;
+ Qt::PenJoinStyle joinStyle() const;
+
+ /**
+ * Creates KoShapeStroke object filled with the options
+ * configured by the widget. The caller is in charge of
+ * deletion of the returned object
+ */
+ KoShapeStrokeSP createShapeStroke();
+
+ void setActive(bool active);
+
+ void updateStyleControlsAvailability(bool enabled);
+ void setUnitManagers(KisSpinBoxUnitManager* managerLineWidth, KisSpinBoxUnitManager* managerMitterLimit);
+
+ void activate();
+ void deactivate();
+
+private Q_SLOTS:
+
+ void updateMarkers(const QList<KoMarker*> &markers);
+
+ void canvasResourceChanged(int key, const QVariant &value);
+
+ /// selection has changed
+ void selectionChanged();
+
+ /// apply line changes to the selected shapes
+ void applyDashStyleChanges();
+ void applyLineWidthChanges();
+ void applyJoinCapChanges();
+
+ /// apply marker changes to the selected shape
+ void applyMarkerChanges(int rawPosition);
+
+Q_SIGNALS:
+ void sigStrokeChanged();
+
+private:
+ void setUnit(const KoUnit &unit, KoShape *representativeShape);
+ void blockChildSignals(bool block);
+ void loadCurrentStrokeFillFromResourceServer();
+
+private:
+ class Private;
+ Private * const d;
+};
+
+#endif // SHADOWCONFIGWIDGET_H
diff --git a/libs/ui/widgets/kis_color_button.cpp b/libs/ui/widgets/kis_color_button.cpp
index 7448e9adea..ec0a1426b5 100644
--- a/libs/ui/widgets/kis_color_button.cpp
+++ b/libs/ui/widgets/kis_color_button.cpp
@@ -1,342 +1,369 @@
/* 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 <QtCore/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 <dialogs/kis_dlg_internal_color_selector.h>
class KisColorButton::KisColorButtonPrivate
{
public:
KisColorButtonPrivate(KisColorButton *q);
void _k_chooseColor();
void _k_colorChosen();
KisColorButton *q;
KoColor m_defaultColor;
bool m_bdefaultColor : 1;
bool m_alphaChannel : 1;
KoColor col;
QPoint mPos;
-
+#ifndef Q_OS_OSX
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;
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;
}
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().background().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());
}
QSize KisColorButton::minimumSizeHint() const
{
QStyleOptionButton opt;
d->initStyleOption(&opt);
return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this).
expandedTo(QApplication::globalStrut());
}
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)->start();
setDown(false);
}
}
void KisColorButton::KisColorButtonPrivate::_k_chooseColor()
{
+#ifndef Q_OS_OSX
KisDlgInternalColorSelector *dialog = dialogPtr.data();
+#else
+ QColorDialog *dialog = dialogPtr.data();
+#endif
if (dialog) {
+#ifndef Q_OS_OSX
dialog->setPreviousColor(q->color());
+#else
+ dialog->setCurrentColor(q->color().toQColor());
+#endif
dialog->show();
dialog->raise();
dialog->activateWindow();
return;
}
KisDlgInternalColorSelector::Config cfg;
-
+#ifndef Q_OS_OSX
dialog = new KisDlgInternalColorSelector(q,
q->color(),
cfg,
i18n("Choose a color"));
- //dialog->setOption(QColorDialog::ShowAlphaChannel, m_alphaChannel);
+#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;
- dialog->setPreviousColor(q->color());
+#ifndef Q_OS_OSX
+ dialog->setPreviousColor(q->color());
+#else
+ dialog->setCurrentColor(q->color().toQColor());
+#endif
dialog->show();
}
void KisColorButton::KisColorButtonPrivate::_k_colorChosen()
{
+#ifndef Q_OS_OSX
KisDlgInternalColorSelector *dialog = dialogPtr.data();
+#else
+ QColorDialog *dialog = dialogPtr.data();
+#endif
if (!dialog) {
return;
}
-
+#ifndef Q_OS_OSX
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/ui/widgets/kis_preset_chooser.cpp b/libs/ui/widgets/kis_preset_chooser.cpp
index bb931df1c6..9fa4e3e3b7 100644
--- a/libs/ui/widgets/kis_preset_chooser.cpp
+++ b/libs/ui/widgets/kis_preset_chooser.cpp
@@ -1,342 +1,343 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2009 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
* Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_preset_chooser.h"
#include <QVBoxLayout>
#include <QPainter>
#include <QAbstractItemDelegate>
#include <QStyleOptionViewItem>
#include <QSortFilterProxyModel>
#include <QApplication>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <KoResourceItemChooser.h>
#include <KoResourceModel.h>
#include <KoResourceServerAdapter.h>
#include <KoResourceItemChooserSync.h>
#include "KoResourceItemView.h"
#include <brushengine/kis_paintop_settings.h>
#include <brushengine/kis_paintop_preset.h>
#include "kis_resource_server_provider.h"
#include "kis_global.h"
#include "kis_slider_spin_box.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include <kis_icon.h>
/// The resource item delegate for rendering the resource preview
class KisPresetDelegate : public QAbstractItemDelegate
{
public:
KisPresetDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent), m_showText(false), m_useDirtyPresets(false) {}
~KisPresetDelegate() override {}
/// reimplemented
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
/// reimplemented
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override {
return option.decorationSize;
}
void setShowText(bool showText) {
m_showText = showText;
}
void setUseDirtyPresets(bool value) {
m_useDirtyPresets = value;
}
private:
bool m_showText;
bool m_useDirtyPresets;
};
void KisPresetDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
painter->save();
painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
if (! index.isValid())
return;
KisPaintOpPreset* preset = static_cast<KisPaintOpPreset*>(index.internalPointer());
QImage preview = preset->image();
if(preview.isNull()) {
return;
}
QRect paintRect = option.rect.adjusted(1, 1, -1, -1);
if (!m_showText) {
painter->drawImage(paintRect.x(), paintRect.y(),
preview.scaled(paintRect.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
QSize pixSize(paintRect.height(), paintRect.height());
painter->drawImage(paintRect.x(), paintRect.y(),
preview.scaled(pixSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
// Put an asterisk after the preset if it is dirty. This will help in case the pixmap icon is too small
QString dirtyPresetIndicator = QString("");
if (m_useDirtyPresets && preset->isPresetDirty()) {
dirtyPresetIndicator = QString("*");
}
painter->drawText(pixSize.width() + 10, option.rect.y() + option.rect.height() - 10, preset->name().append(dirtyPresetIndicator));
}
if (m_useDirtyPresets && preset->isPresetDirty()) {
const QIcon icon = KisIconUtils::loadIcon(koIconName("dirty-preset"));
QPixmap pixmap = icon.pixmap(QSize(15,15));
painter->drawPixmap(paintRect.x() + 3, paintRect.y() + 3, pixmap);
}
if (!preset->settings() || !preset->settings()->isValid()) {
const QIcon icon = KisIconUtils::loadIcon("broken-preset");
icon.paint(painter, QRect(paintRect.x() + paintRect.height() - 25, paintRect.y() + paintRect.height() - 25, 25, 25));
}
if (option.state & QStyle::State_Selected) {
painter->setCompositionMode(QPainter::CompositionMode_HardLight);
painter->setOpacity(1.0);
painter->fillRect(option.rect, option.palette.highlight());
// highlight is not strong enough to pick out preset. draw border around it.
painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
painter->setPen(QPen(option.palette.highlight(), 4, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
QRect selectedBorder = option.rect.adjusted(2 , 2, -2, -2); // constrict the rectangle so it doesn't bleed into other presets
painter->drawRect(selectedBorder);
}
painter->restore();
}
class KisPresetProxyAdapter : public KisPaintOpPresetResourceServerAdapter
{
public:
KisPresetProxyAdapter(KisPaintOpPresetResourceServer* resourceServer)
: KisPaintOpPresetResourceServerAdapter(resourceServer)
{
setSortingEnabled(true);
}
~KisPresetProxyAdapter() override {}
QList< KoResource* > resources() override {
QList<KoResource*> serverResources =
KisPaintOpPresetResourceServerAdapter::resources();
if (m_paintopID.isEmpty()) {
return serverResources;
}
QList<KoResource*> resources;
Q_FOREACH (KoResource *resource, serverResources) {
KisPaintOpPreset *preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset && preset->paintOp().id() == m_paintopID) {
resources.append(preset);
}
}
return resources;
}
///Set id for paintop to be accept by the proxy model, if not filter is set all
///presets will be shown.
void setPresetFilter(const QString& paintOpId)
{
m_paintopID = paintOpId;
invalidate();
}
///Resets the model connected to the adapter
void invalidate() {
emitRemovingResource(0);
}
QString currentPaintOpId() const {
return m_paintopID;
}
private:
QString m_paintopID;
};
KisPresetChooser::KisPresetChooser(QWidget *parent, const char *name)
: QWidget(parent)
{
setObjectName(name);
QVBoxLayout * layout = new QVBoxLayout(this);
layout->setMargin(0);
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false);
m_adapter = QSharedPointer<KoAbstractResourceServerAdapter>(new KisPresetProxyAdapter(rserver));
m_chooser = new KoResourceItemChooser(m_adapter, this);
+ m_chooser->setObjectName("ResourceChooser");
m_chooser->setColumnCount(10);
m_chooser->setRowHeight(50);
m_delegate = new KisPresetDelegate(this);
m_chooser->setItemDelegate(m_delegate);
m_chooser->setSynced(true);
layout->addWidget(m_chooser);
connect(m_chooser, SIGNAL(resourceSelected(KoResource*)),
this, SIGNAL(resourceSelected(KoResource*)));
connect(m_chooser, SIGNAL(resourceClicked(KoResource*)),
this, SIGNAL(resourceClicked(KoResource*)));
m_mode = THUMBNAIL;
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()),
SLOT(notifyConfigChanged()));
notifyConfigChanged();
}
KisPresetChooser::~KisPresetChooser()
{
}
void KisPresetChooser::showButtons(bool show)
{
m_chooser->showButtons(show);
}
void KisPresetChooser::setViewMode(KisPresetChooser::ViewMode mode)
{
m_mode = mode;
updateViewSettings();
}
void KisPresetChooser::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
updateViewSettings();
}
void KisPresetChooser::notifyConfigChanged()
{
KisConfig cfg;
m_delegate->setUseDirtyPresets(cfg.useDirtyPresets());
- setIconSize(cfg.presetIconSize() );
+ setIconSize(cfg.presetIconSize());
updateViewSettings();
}
void KisPresetChooser::updateViewSettings()
{
if (m_mode == THUMBNAIL) {
m_chooser->setSynced(true);
m_delegate->setShowText(false);
} else if (m_mode == DETAIL) {
m_chooser->setSynced(false);
m_chooser->setColumnCount(1);
m_chooser->setColumnWidth(m_chooser->width());
KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance();
m_chooser->setRowHeight(chooserSync->baseLength());
m_delegate->setShowText(true);
} else if (m_mode == STRIP) {
m_chooser->setSynced(false);
m_chooser->setRowCount(1);
m_chooser->itemView()->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_chooser->itemView()->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// An offset of 7 keeps the cell exactly square, TODO: use constants, not hardcoded numbers
m_chooser->setColumnWidth(m_chooser->viewSize().height() - 7);
m_delegate->setShowText(false);
}
}
void KisPresetChooser::setCurrentResource(KoResource *resource)
{
/**
* HACK ALERT: here we use a direct call to an adapter to notify the view
* that the preset might have changed its dirty state. This state
* doesn't affect the filtering so the server's cache must not be
* invalidated!
*
* Ideally, we should call some method of KoResourceServer instead,
* but ut seems like a bit too much effort for such a small fix.
*/
if (resource == currentResource()) {
KisPresetProxyAdapter *adapter = static_cast<KisPresetProxyAdapter*>(m_adapter.data());
KisPaintOpPreset *preset = dynamic_cast<KisPaintOpPreset*>(resource);
if (preset) {
adapter->resourceChangedNoCacheInvalidation(preset);
}
}
m_chooser->setCurrentResource(resource);
}
-KoResource* KisPresetChooser::currentResource()
+KoResource* KisPresetChooser::currentResource() const
{
return m_chooser->currentResource();
}
void KisPresetChooser::showTaggingBar(bool show)
{
m_chooser->showTaggingBar(show);
}
KoResourceItemChooser *KisPresetChooser::itemChooser()
{
return m_chooser;
}
void KisPresetChooser::setPresetFilter(const QString& paintOpId)
{
KisPresetProxyAdapter *adapter = static_cast<KisPresetProxyAdapter*>(m_adapter.data());
if (adapter->currentPaintOpId() != paintOpId) {
adapter->setPresetFilter(paintOpId);
updateViewSettings();
}
}
void KisPresetChooser::setIconSize(int newSize)
{
KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance();
chooserSync->setBaseLength(newSize);
updateViewSettings();
}
int KisPresetChooser::iconSize()
{
KoResourceItemChooserSync* chooserSync = KoResourceItemChooserSync::instance();
return chooserSync->baseLength();
}
void KisPresetChooser::saveIconSize()
{
// save icon size
KisConfig cfg;
cfg.setPresetIconSize(iconSize());
}
diff --git a/libs/ui/widgets/kis_preset_chooser.h b/libs/ui/widgets/kis_preset_chooser.h
index 9215870cac..bf273c6af0 100644
--- a/libs/ui/widgets/kis_preset_chooser.h
+++ b/libs/ui/widgets/kis_preset_chooser.h
@@ -1,93 +1,93 @@
/*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (C) 2011 Silvio Heinrich <plassy@web.de>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_ITEM_CHOOSER_H_
#define KIS_ITEM_CHOOSER_H_
#include <QWidget>
#include <kritaui_export.h>
#include <KoID.h>
class KoAbstractResourceServerAdapter;
class KisPresetDelegate;
class KoResourceItemChooser;
class KoResource;
/**
* A special type of item chooser that can contain extra widgets that show
* more information about the currently selected item. Reimplement update()
* to extract that information and fill the appropriate widgets.
*/
class KRITAUI_EXPORT KisPresetChooser : public QWidget
{
Q_OBJECT
public:
KisPresetChooser(QWidget *parent = 0, const char *name = 0);
virtual ~KisPresetChooser();
enum ViewMode{
THUMBNAIL, /// Shows thumbnails
DETAIL, /// Shows thumbsnails with text next to it
STRIP /// Shows thumbnails arranged in a single row
};
/// Sets a list of resources in the paintop list, when ever user press enter in the linedit of paintop_presets_popup Class
void setViewMode(ViewMode mode);
void showButtons(bool show);
void setCurrentResource(KoResource *resource);
- KoResource* currentResource();
+ KoResource* currentResource() const;
/// Sets the visibility of tagging klineEdits
void showTaggingBar(bool show);
KoResourceItemChooser *itemChooser();
void setPresetFilter(const QString& paintOpId);
/// get the base size for the icons. Used by the slider in the view options
int iconSize();
Q_SIGNALS:
void resourceSelected(KoResource *resource);
void resourceClicked(KoResource *resource);
public Q_SLOTS:
void updateViewSettings();
/// sets the icon size. Used by slider in view menu
void setIconSize(int newSize);
/// saves the icon size for the presets. called by the horizontal slider release event.
void saveIconSize();
private Q_SLOTS:
void notifyConfigChanged();
protected:
virtual void resizeEvent(QResizeEvent* event);
private:
KoResourceItemChooser *m_chooser;
KisPresetDelegate* m_delegate;
ViewMode m_mode;
QSharedPointer<KoAbstractResourceServerAdapter> m_adapter;
};
#endif // KIS_ITEM_CHOOSER_H_
diff --git a/libs/ui/widgets/kis_scratch_pad.cpp b/libs/ui/widgets/kis_scratch_pad.cpp
index 86ef4cc00f..89d7dce416 100644
--- a/libs/ui/widgets/kis_scratch_pad.cpp
+++ b/libs/ui/widgets/kis_scratch_pad.cpp
@@ -1,469 +1,469 @@
/* This file is part of the KDE project
* Copyright 2010 (C) Boudewijn Rempt <boud@valdyas.org>
* Copyright 2011 (C) Dmitry Kazakov <dimula73@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kis_scratch_pad.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QMutex>
#include <KoColorSpace.h>
#include <KoColorProfile.h>
#include <KoColorSpaceRegistry.h>
#include <KoPointerEvent.h>
#include <resources/KoAbstractGradient.h>
#include <kis_cursor.h>
#include <kis_tool_utils.h>
#include <kis_paint_layer.h>
#include <kis_paint_device.h>
#include <kis_gradient_painter.h>
#include <kis_default_bounds.h>
#include <kis_canvas_resource_provider.h>
#include "kis_config.h"
#include "kis_image.h"
#include "kis_undo_stores.h"
#include "kis_update_scheduler.h"
#include "kis_post_execution_undo_adapter.h"
#include "kis_scratch_pad_event_filter.h"
#include "kis_painting_information_builder.h"
#include "kis_tool_freehand_helper.h"
#include "kis_image_patch.h"
#include "kis_canvas_widget_base.h"
#include "kis_layer_projection_plane.h"
#include "kis_node_graph_listener.h"
class KisScratchPadNodeListener : public KisNodeGraphListener
{
public:
KisScratchPadNodeListener(KisScratchPad *scratchPad)
: m_scratchPad(scratchPad)
{
}
- void requestProjectionUpdate(KisNode *node, const QRect& rect) override {
- KisNodeGraphListener::requestProjectionUpdate(node, rect);
+ void requestProjectionUpdate(KisNode *node, const QRect& rect, bool resetAnimationCache) override {
+ KisNodeGraphListener::requestProjectionUpdate(node, rect, resetAnimationCache);
QMutexLocker locker(&m_lock);
m_scratchPad->imageUpdated(rect);
}
private:
KisScratchPad *m_scratchPad;
QMutex m_lock;
};
class KisScratchPadDefaultBounds : public KisDefaultBounds
{
public:
KisScratchPadDefaultBounds(KisScratchPad *scratchPad)
: m_scratchPad(scratchPad)
{
}
~KisScratchPadDefaultBounds() override {}
QRect bounds() const override {
return m_scratchPad->imageBounds();
}
private:
Q_DISABLE_COPY(KisScratchPadDefaultBounds)
KisScratchPad *m_scratchPad;
};
KisScratchPad::KisScratchPad(QWidget *parent)
: QWidget(parent)
, m_toolMode(HOVERING)
, m_paintLayer(0)
, m_displayProfile(0)
, m_resourceProvider(0)
{
setAutoFillBackground(false);
m_cursor = KisCursor::load("tool_freehand_cursor.png", 5, 5);
setCursor(m_cursor);
KisConfig cfg;
QImage checkImage = KisCanvasWidgetBase::createCheckersImage(cfg.checkSize());
m_checkBrush = QBrush(checkImage);
// We are not supposed to use updates here,
// so just set the listener to null
m_updateScheduler = new KisUpdateScheduler(0);
m_undoStore = new KisSurrogateUndoStore();
m_undoAdapter = new KisPostExecutionUndoAdapter(m_undoStore, m_updateScheduler);
m_nodeListener = new KisScratchPadNodeListener(this);
connect(this, SIGNAL(sigUpdateCanvas(QRect)), SLOT(slotUpdateCanvas(QRect)), Qt::QueuedConnection);
// filter will be deleted by the QObject hierarchy
m_eventFilter = new KisScratchPadEventFilter(this);
m_infoBuilder = new KisPaintingInformationBuilder();
m_helper = new KisToolFreehandHelper(m_infoBuilder);
m_scaleBorderWidth = 1;
}
KisScratchPad::~KisScratchPad() {
delete m_helper;
delete m_infoBuilder;
delete m_undoAdapter;
delete m_undoStore;
delete m_updateScheduler;
delete m_nodeListener;
}
KisScratchPad::Mode KisScratchPad::modeFromButton(Qt::MouseButton button) const
{
return
button == Qt::NoButton ? HOVERING :
button == Qt::MidButton ? PANNING :
button == Qt::RightButton ? PICKING :
PAINTING;
}
void KisScratchPad::pointerPress(KoPointerEvent *event)
{
if (m_toolMode != HOVERING) return;
m_toolMode = modeFromButton(event->button());
if (m_toolMode == PAINTING) {
beginStroke(event);
event->accept();
}
else if (m_toolMode == PANNING) {
beginPan(event);
event->accept();
}
else if (m_toolMode == PICKING) {
pick(event);
event->accept();
}
}
void KisScratchPad::pointerRelease(KoPointerEvent *event)
{
if (modeFromButton(event->button()) != m_toolMode) return;
if (m_toolMode == PAINTING) {
endStroke(event);
m_toolMode = HOVERING;
event->accept();
}
else if (m_toolMode == PANNING) {
endPan(event);
m_toolMode = HOVERING;
event->accept();
}
else if (m_toolMode == PICKING) {
event->accept();
m_toolMode = HOVERING;
}
}
void KisScratchPad::pointerMove(KoPointerEvent *event)
{
if (m_toolMode == PAINTING) {
doStroke(event);
event->accept();
}
else if (m_toolMode == PANNING) {
doPan(event);
event->accept();
}
else if (m_toolMode == PICKING) {
pick(event);
event->accept();
}
}
void KisScratchPad::beginStroke(KoPointerEvent *event)
{
KoCanvasResourceManager *resourceManager = m_resourceProvider->resourceManager();
m_helper->initPaint(event,
resourceManager,
0,
0,
m_updateScheduler,
m_paintLayer,
m_paintLayer->paintDevice()->defaultBounds());
}
void KisScratchPad::doStroke(KoPointerEvent *event)
{
m_helper->paint(event);
}
void KisScratchPad::endStroke(KoPointerEvent *event)
{
Q_UNUSED(event);
m_helper->endPaint();
}
void KisScratchPad::beginPan(KoPointerEvent *event)
{
setCursor(QCursor(Qt::ClosedHandCursor));
m_panDocPoint = event->point;
}
void KisScratchPad::doPan(KoPointerEvent *event)
{
QPointF docOffset = event->point - m_panDocPoint;
m_translateTransform.translate(-docOffset.x(), -docOffset.y());
updateTransformations();
update();
}
void KisScratchPad::endPan(KoPointerEvent *event)
{
Q_UNUSED(event);
setCursor(m_cursor);
}
void KisScratchPad::pick(KoPointerEvent *event)
{
KoColor color;
if (KisToolUtils::pick(m_paintLayer->projection(), event->point.toPoint(), &color)) {
emit colorSelected(color);
}
}
void KisScratchPad::setOnScreenResolution(qreal scaleX, qreal scaleY)
{
m_scaleBorderWidth = BORDER_SIZE(qMax(scaleX, scaleY));
m_scaleTransform = QTransform::fromScale(scaleX, scaleY);
updateTransformations();
update();
}
QTransform KisScratchPad::documentToWidget() const
{
return m_translateTransform.inverted() * m_scaleTransform;
}
QTransform KisScratchPad::widgetToDocument() const
{
return m_scaleTransform.inverted() * m_translateTransform;
}
void KisScratchPad::updateTransformations()
{
m_eventFilter->setWidgetToDocumentTransform(widgetToDocument());
}
QRect KisScratchPad::imageBounds() const
{
return widgetToDocument().mapRect(rect());
}
void KisScratchPad::imageUpdated(const QRect &rect)
{
emit sigUpdateCanvas(documentToWidget().mapRect(QRectF(rect)).toAlignedRect());
}
void KisScratchPad::slotUpdateCanvas(const QRect &rect)
{
update(rect);
}
void KisScratchPad::paintEvent ( QPaintEvent * event ) {
if(!m_paintLayer) return;
QRectF imageRect = widgetToDocument().mapRect(QRectF(event->rect()));
QRect alignedImageRect =
imageRect.adjusted(-m_scaleBorderWidth, -m_scaleBorderWidth,
m_scaleBorderWidth, m_scaleBorderWidth).toAlignedRect();
QPointF offset = alignedImageRect.topLeft();
m_paintLayer->projectionPlane()->recalculate(alignedImageRect, m_paintLayer);
KisPaintDeviceSP projection = m_paintLayer->projection();
QImage image = projection->convertToQImage(m_displayProfile,
alignedImageRect.x(),
alignedImageRect.y(),
alignedImageRect.width(),
alignedImageRect.height(),
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
QPainter gc(this);
gc.fillRect(event->rect(), m_checkBrush);
gc.setRenderHints(QPainter::SmoothPixmapTransform);
gc.drawImage(QRectF(event->rect()), image, imageRect.translated(-offset));
QBrush brush(Qt::lightGray);
QPen pen(brush, 1, Qt::DotLine);
gc.setPen(pen);
if (m_cutoutOverlay.isValid()) {
gc.drawRect(m_cutoutOverlay);
}
if(!isEnabled()) {
QColor color(Qt::lightGray);
color.setAlphaF(0.5);
QBrush disabledBrush(color);
gc.fillRect(event->rect(), disabledBrush);
}
gc.end();
}
void KisScratchPad::setupScratchPad(KisCanvasResourceProvider* resourceProvider,
const QColor &defaultColor)
{
m_resourceProvider = resourceProvider;
KisConfig cfg;
setDisplayProfile(cfg.displayProfile(QApplication::desktop()->screenNumber(this)));
connect(m_resourceProvider, SIGNAL(sigDisplayProfileChanged(const KoColorProfile*)),
SLOT(setDisplayProfile(const KoColorProfile*)));
connect(m_resourceProvider, SIGNAL(sigOnScreenResolutionChanged(qreal,qreal)),
SLOT(setOnScreenResolution(qreal,qreal)));
connect(this, SIGNAL(colorSelected(const KoColor&)),
m_resourceProvider, SLOT(slotSetFGColor(const KoColor&)));
m_defaultColor = KoColor(defaultColor, KoColorSpaceRegistry::instance()->rgb8());
KisPaintDeviceSP paintDevice =
new KisPaintDevice(m_defaultColor.colorSpace(), "scratchpad");
m_paintLayer = new KisPaintLayer(0, "ScratchPad", OPACITY_OPAQUE_U8, paintDevice);
m_paintLayer->setGraphListener(m_nodeListener);
m_paintLayer->paintDevice()->setDefaultBounds(new KisScratchPadDefaultBounds(this));
fillDefault();
}
void KisScratchPad::setCutoutOverlayRect(const QRect& rc)
{
m_cutoutOverlay = rc;
}
QImage KisScratchPad::cutoutOverlay() const
{
if(!m_paintLayer) return QImage();
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect rc = widgetToDocument().mapRect(m_cutoutOverlay);
QImage rawImage = paintDevice->convertToQImage(0, rc.x(), rc.y(), rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
QImage scaledImage = rawImage.scaled(m_cutoutOverlay.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
return scaledImage;
}
void KisScratchPad::setPresetImage(const QImage& image)
{
m_presetImage = image;
}
void KisScratchPad::paintPresetImage()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
QRect overlayRect = widgetToDocument().mapRect(m_cutoutOverlay);
QRect imageRect(QPoint(), overlayRect.size());
QImage scaledImage = m_presetImage.scaled(overlayRect.size(),
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation);
KisPaintDeviceSP device = new KisPaintDevice(paintDevice->colorSpace());
device->convertFromQImage(scaledImage, 0);
KisPainter painter(paintDevice);
painter.bitBlt(overlayRect.topLeft(), device, imageRect);
update();
}
void KisScratchPad::setDisplayProfile(const KoColorProfile *colorProfile)
{
if (colorProfile) {
m_displayProfile = colorProfile;
QWidget::update();
}
}
void KisScratchPad::fillDefault()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
paintDevice->setDefaultPixel(m_defaultColor);
paintDevice->clear();
update();
}
void KisScratchPad::fillGradient()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
KoAbstractGradient* gradient = m_resourceProvider->currentGradient();
QRect gradientRect = widgetToDocument().mapRect(rect());
paintDevice->clear();
KisGradientPainter painter(paintDevice);
painter.setGradient(gradient);
painter.setGradientShape(KisGradientPainter::GradientShapeLinear);
painter.paintGradient(gradientRect.topLeft(),
gradientRect.bottomRight(),
KisGradientPainter::GradientRepeatNone,
0.2, false,
gradientRect.left(), gradientRect.top(),
gradientRect.width(), gradientRect.height());
update();
}
void KisScratchPad::fillBackground()
{
if(!m_paintLayer) return;
KisPaintDeviceSP paintDevice = m_paintLayer->paintDevice();
paintDevice->setDefaultPixel(m_resourceProvider->bgColor());
paintDevice->clear();
update();
}
void KisScratchPad::fillLayer()
{
// TODO
}
diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp
index 5c9fb9e1f9..f2d8794e18 100644
--- a/libs/ui/widgets/kis_slider_spin_box.cpp
+++ b/libs/ui/widgets/kis_slider_spin_box.cpp
@@ -1,1029 +1,1037 @@
/* 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 "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);
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);
//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();
- KisPart::currentInputManager()->slotFocusOnEnter(false);
+
+ KisInputManager *inputManager = KisPart::instance()->currentInputManager();
+ if (inputManager) {
+ inputManager->slotFocusOnEnter(false);
+ }
}
void KisAbstractSliderSpinBox::hideEdit()
{
Q_D(KisAbstractSliderSpinBox);
d->edit->hide();
update();
- KisPart::currentInputManager()->slotFocusOnEnter(true);
+
+ KisInputManager *inputManager = KisPart::instance()->currentInputManager();
+ if (inputManager) {
+ inputManager->slotFocusOnEnter(true);
+ }
}
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();
}
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());
}
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 realvalue = ((dValues * pow(percent, d->exponentRatio)) + 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;
}
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 2fef83545b..9ffcb95bed 100644
--- a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
+++ b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp
@@ -1,184 +1,360 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2016 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "widgets/kis_stopgradient_slider_widget.h"
+#include <QWindow>
#include <QPainter>
#include <QPixmap>
#include <QMouseEvent>
#include <QPolygon>
#include <QPaintEvent>
+#include <QFontMetrics>
+#include <QStyle>
+#include <QApplication>
+#include <QStyleOptionToolButton>
-#define MARGIN 10
-#define HANDLE_SIZE 20
+#include "kis_global.h"
+#include "kis_debug.h"
+#include "krita_utils.h"
KisStopGradientSliderWidget::KisStopGradientSliderWidget(QWidget *parent, Qt::WFlags f)
: QWidget(parent, f)
, m_selectedStop(0)
, m_drag(0)
{
- setMinimumHeight(30);
+ QLinearGradient defaultGradient;
+ m_defaultGradient.reset(KoStopGradient::fromQGradient(&defaultGradient));
+
+ setGradientResource(0);
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+
+ setMouseTracking(true);
+
+ QWindow *window = this->window()->windowHandle();
+ if (window) {
+ connect(window, SIGNAL(screenChanged(QScreen*)), SLOT(updateHandleSize()));
+ }
+ updateHandleSize();
+}
+
+void KisStopGradientSliderWidget::updateHandleSize()
+{
+ QFontMetrics fm(font());
+ const int h = fm.height();
+ m_handleSize = QSize(0.34 * h, h);
+}
+
+int KisStopGradientSliderWidget::handleClickTolerance() const
+{
+ // the size of the default text!
+ return m_handleSize.height();
}
void KisStopGradientSliderWidget::setGradientResource(KoStopGradient* gradient)
{
- m_gradient = gradient;
+ m_gradient = gradient ? gradient : m_defaultGradient.data();
- emit sigSelectedStop(m_selectedStop);
+ if (m_selectedStop >= 0) {
+ m_selectedStop = qBound(0, m_selectedStop, m_gradient->stops().size() - 1);
+ emit sigSelectedStop(m_selectedStop);
+ }
+}
+
+void KisStopGradientSliderWidget::paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter)
+{
+ const QRect handlesRect = this->handlesStipeRect();
+
+ const int handleCenter = handlesRect.left() + position * handlesRect.width();
+ const int handlesHalfWidth = handlesRect.height() * 0.26; // = 1.0 / 0.66 * 0.34 / 2.0 <-- golden ratio
+
+ QPolygon triangle(3);
+ triangle[0] = QPoint(handleCenter, handlesRect.top());
+ triangle[1] = QPoint(handleCenter - handlesHalfWidth, handlesRect.bottom());
+ triangle[2] = QPoint(handleCenter + handlesHalfWidth, handlesRect.bottom());
+
+ const qreal lineWidth = 1.0;
+
+ if (!isSelected) {
+ painter->setPen(QPen(palette().text(), lineWidth));
+ painter->setBrush(QBrush(color));
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->drawPolygon(triangle);
+ } else {
+ painter->setPen(QPen(palette().highlight(), 1.5 * lineWidth));
+ painter->setBrush(QBrush(color));
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->drawPolygon(triangle);
+ }
}
void KisStopGradientSliderWidget::paintEvent(QPaintEvent* pe)
{
QWidget::paintEvent(pe);
QPainter painter(this);
- painter.fillRect(rect(), palette().background());
painter.setPen(Qt::black);
- painter.drawRect(MARGIN, MARGIN, width() - 2 * MARGIN, height() - 2 * MARGIN - HANDLE_SIZE);
+
+ const QRect previewRect = gradientStripeRect();
+ KritaUtils::renderExactRect(&painter, kisGrowRect(previewRect, 1));
+
+ painter.drawRect(previewRect);
if (m_gradient) {
- QImage image = m_gradient->generatePreview(width() - 2 * MARGIN - 2, height() - 2 * MARGIN - HANDLE_SIZE - 2);
- QPixmap pixmap(image.width(), image.height());
+ QImage image = m_gradient->generatePreview(previewRect.width(), previewRect.height());
if (!image.isNull()) {
- painter.drawImage(MARGIN + 1, MARGIN + 1, image);
+ painter.drawImage(previewRect.topLeft(), image);
}
- QPolygon triangle(3);
QList<KoGradientStop> handlePositions = m_gradient->stops();
- int position;
for (int i = 0; i < handlePositions.count(); i++) {
- position = qRound(handlePositions[i].first * (double)(width() - 2*MARGIN)) + MARGIN;
- triangle[0] = QPoint(position, height() - HANDLE_SIZE - MARGIN);
- triangle[1] = QPoint(position + (HANDLE_SIZE / 2 - 1), height() - MARGIN);
- triangle[2] = QPoint(position - (HANDLE_SIZE / 2 - 1), height() - MARGIN);
-
- if(i != m_selectedStop)
- painter.setPen(QPen(Qt::black, 2.0));
- else
- painter.setPen(QPen(palette().highlight(), 2.0));
- painter.setBrush(QBrush(handlePositions[i].second.toQColor()));
- painter.setRenderHint(QPainter::Antialiasing);
- painter.drawPolygon(triangle);
+ 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 (e->x() < MARGIN || e->x() > width() - MARGIN) {
+ if (!allowedClickRegion(handleClickTolerance()).contains(e->pos())) {
QWidget::mousePressEvent(e);
return;
}
- double t = (double)(e->x() - MARGIN) / (double)(width() - 2 * MARGIN);
- if(e->y() < height() - HANDLE_SIZE - MARGIN) {
- if(e->button() == Qt::LeftButton)
- insertStop(t);
- }
- else {
- QPolygon triangle(3);
- QList<KoGradientStop> stops = m_gradient->stops();
- int position;
- for (int i = 0; i < stops.count(); i++) {
- position = qRound(stops[i].first * (double)(width() - 2*MARGIN)) + MARGIN;
- triangle[0] = QPoint(position, height() - HANDLE_SIZE - MARGIN);
- triangle[1] = QPoint(position + (HANDLE_SIZE / 2 - 1), height() - MARGIN);
- triangle[2] = QPoint(position - (HANDLE_SIZE / 2 - 1), height() - MARGIN);
-
- if(triangle.containsPoint(e->pos(), Qt::WindingFill))
- {
- if(e->button() == Qt::LeftButton) {
- m_selectedStop = i;
- emit sigSelectedStop(m_selectedStop);
- if(m_selectedStop > 0 && m_selectedStop < stops.size()-1)
- m_drag = true;
- }
- else if (e->button() == Qt::RightButton && (i > 0 && i < stops.size()-1)) {
- QList<KoGradientStop> stops = m_gradient->stops();
- stops.removeAt(i);
- m_gradient->setStops(stops);
- if(m_selectedStop == i)
- m_selectedStop = i-1;
- else if (m_selectedStop > i)
- m_selectedStop--;
- }
- break;
- }
+ 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());
}
-void KisStopGradientSliderWidget::mouseMoveEvent(QMouseEvent * e)
+int getNewInsertPosition(const KoGradientStop &stop, const QList<KoGradientStop> &stops)
{
- if ((e->y() < MARGIN || e->y() > height() - MARGIN) || (e->x() < MARGIN || e->x() > width() - MARGIN)) {
- QWidget::mouseMoveEvent(e);
- return;
+ int result = 0;
+
+ for (int i = 0; i < stops.size(); i++) {
+ if (stop.first <= stops[i].first) break;
+
+ result = i + 1;
}
- double t = (double)(e->x() - MARGIN) / (double)(width() - 2 * MARGIN);
+ return result;
+}
+
+void KisStopGradientSliderWidget::mouseMoveEvent(QMouseEvent * e)
+{
+ updateCursor(e->pos());
+
if (m_drag) {
- QList<KoGradientStop> stops = m_gradient->stops();
+ const QRect handlesRect = this->handlesStipeRect();
+ double t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width();
- KoGradientStop dragedStop = stops[m_selectedStop];
+ QList<KoGradientStop> stops = m_gradient->stops();
- t = qBound(stops[m_selectedStop-1].first, t, stops[m_selectedStop+1].first);
- dragedStop.first = t;
+ 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);
- stops.insert(m_selectedStop, dragedStop);
+ 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);
}
- update();
+
+}
+
+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)
{
- QList<KoGradientStop> stopPositions = m_gradient->stops();
- int i = 0;
- while(stopPositions[i].first < t)
- i++;
+ 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);
- stopPositions.insert(i, KoGradientStop(t, color));
- m_gradient->setStops(stopPositions);
- m_selectedStop = i;
+ 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::setSeletectStop(int selected)
+void KisStopGradientSliderWidget::setSelectedStop(int selected)
{
m_selectedStop = selected;
emit sigSelectedStop(m_selectedStop);
+
+ update();
+}
+
+int KisStopGradientSliderWidget::minimalHeight() const
+{
+ QFontMetrics fm(font());
+ const int h = fm.height();
+
+ QStyleOptionToolButton opt;
+ QSize sz = (style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this).
+ expandedTo(QApplication::globalStrut()));
+
+ return sz.height() + m_handleSize.height();
+}
+
+QSize KisStopGradientSliderWidget::sizeHint() const
+{
+ const int h = minimalHeight();
+ return QSize(2 * h, h);
+}
+
+QSize KisStopGradientSliderWidget::minimumSizeHint() const
+{
+ const int h = minimalHeight();
+ return QSize(h, h);
}
diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.h b/libs/ui/widgets/kis_stopgradient_slider_widget.h
index d21329587b..f1ebd96a49 100644
--- a/libs/ui/widgets/kis_stopgradient_slider_widget.h
+++ b/libs/ui/widgets/kis_stopgradient_slider_widget.h
@@ -1,63 +1,82 @@
/*
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
* 2016 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_STOP_GRADIENT_SLIDER_WIDGET_H_
#define _KIS_STOP_GRADIENT_SLIDER_WIDGET_H_
#include <QWidget>
#include <QMouseEvent>
#include <QPaintEvent>
+#include <QScopedPointer>
#include <resources/KoStopGradient.h>
class KisStopGradientSliderWidget : public QWidget
{
Q_OBJECT
public:
KisStopGradientSliderWidget(QWidget *parent = 0, Qt::WFlags f = 0);
public:
virtual void paintEvent(QPaintEvent *);
void setGradientResource(KoStopGradient* gradient);
int selectedStop();
- void setSeletectStop(int selected);
+ void setSelectedStop(int selected);
+
+ QSize sizeHint() const override;
+ QSize minimumSizeHint() const override;
Q_SIGNALS:
void sigSelectedStop(int stop);
protected:
virtual void mousePressEvent(QMouseEvent * e);
virtual void mouseReleaseEvent(QMouseEvent * e);
virtual void mouseMoveEvent(QMouseEvent * e);
private Q_SLOTS:
+ void updateHandleSize();
private:
void insertStop(double t);
+ QRect sliderRect() const;
+ QRect gradientStripeRect() const;
+ QRect handlesStipeRect() const;
+ QRegion allowedClickRegion(int tolerance) const;
+
+ void updateCursor(const QPoint &pos);
+ void paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter);
+ int handleClickTolerance() const;
+ int minimalHeight() const;
+
private:
+ QScopedPointer<KoStopGradient> m_defaultGradient;
KoStopGradient* m_gradient;
int m_selectedStop;
+ KoGradientStop m_removedStop;
bool m_drag;
+ QSize m_handleSize;
+
};
#endif
diff --git a/libs/widgets/CMakeLists.txt b/libs/widgets/CMakeLists.txt
index 52c5798d0f..77711b9a7d 100644
--- a/libs/widgets/CMakeLists.txt
+++ b/libs/widgets/CMakeLists.txt
@@ -1,112 +1,111 @@
add_subdirectory( tests )
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(kritawidgets_LIB_SRCS
KoGradientEditWidget.cpp
KoVBox.cpp
KoDialog.cpp
KoGlobal.cpp
KoZoomWidget.cpp
KoTagToolButton.cpp
KoTagChooserWidget.cpp
KoTagFilterWidget.cpp
KoResourceTaggingManager.cpp
KoResourceItemChooserContextMenu.cpp
KoAspectButton.cpp
KoPagePreviewWidget.cpp
- KoPositionSelector.cpp
KoSliderCombo.cpp
KoColorPopupButton.cpp
KoConfigAuthorPage.cpp
KoUnitDoubleSpinBox.cpp
KoZoomAction.cpp
KoZoomController.cpp
KoZoomInput.cpp
KoZoomHandler.cpp
KoZoomMode.cpp
KoDpi.cpp
KoGlobal.cpp
KoColorPatch.cpp
KoColorPopupAction.cpp
KoColorSetWidget.cpp
KoColorSlider.cpp
KoEditColorSetDialog.cpp
KoTriangleColorSelector.cpp
KoResourcePopupAction.cpp
- KoFillConfigWidget.cpp
KoIconToolTip.cpp
KoResourceItemChooser.cpp
KoResourceItemChooserSync.cpp
KoResourceSelector.cpp
KoResourceModel.cpp
KoResourceItemDelegate.cpp
KoResourceItemView.cpp
KoResourceTagStore.cpp
KoRuler.cpp
#KoRulerController.cpp
KoItemToolTip.cpp
KoCheckerBoardPainter.cpp
KoResourceServerAdapter.cpp
KoResourceServerProvider.cpp
KoLineStyleSelector.cpp
KoLineStyleItemDelegate.cpp
KoLineStyleModel.cpp
KoDockWidgetTitleBar.cpp
KoDockWidgetTitleBarButton.cpp
KoResourceFiltering.cpp
KoResourceModelBase.cpp
+ KoTitledTabWidget.cpp
KoToolBoxButton.cpp
KoToolBox.cpp
KoToolBoxDocker.cpp
KoToolBoxFactory.cpp
KoToolDocker.cpp
KoPageLayoutWidget.cpp
KoPageLayoutDialog.cpp
KoShadowConfigWidget.cpp
- KoStrokeConfigWidget.cpp
KoMarkerSelector.cpp
KoMarkerModel.cpp
KoMarkerItemDelegate.cpp
KoDocumentInfoDlg.cpp
KoGlobal.cpp
KoTableView.cpp
WidgetsDebug.cpp
kis_file_name_requester.cpp
kis_double_parse_spin_box.cpp
kis_double_parse_unit_spin_box.cpp
kis_int_parse_spin_box.cpp
KisColorSelectorInterface.cpp
+ KoAnchorSelectionWidget.cpp
)
ki18n_wrap_ui( kritawidgets_LIB_SRCS
KoConfigAuthorPage.ui
koDocumentInfoAboutWidget.ui
koDocumentInfoAuthorWidget.ui
KoEditColorSet.ui
wdg_file_name_requester.ui
KoPageLayoutWidget.ui
KoShadowConfigWidget.ui
)
add_library(kritawidgets SHARED ${kritawidgets_LIB_SRCS})
generate_export_header(kritawidgets BASE_NAME kritawidgets)
target_link_libraries(kritawidgets kritaodf kritaflake kritapigment kritawidgetutils Qt5::PrintSupport KF5::CoreAddons KF5::ConfigGui KF5::GuiAddons KF5::WidgetsAddons KF5::ConfigCore KF5::Completion)
if(X11_FOUND)
target_link_libraries(kritawidgets Qt5::X11Extras ${X11_LIBRARIES})
endif()
set_target_properties(kritawidgets PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritawidgets ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/widgets/KoAnchorSelectionWidget.cpp b/libs/widgets/KoAnchorSelectionWidget.cpp
new file mode 100644
index 0000000000..9310918a3d
--- /dev/null
+++ b/libs/widgets/KoAnchorSelectionWidget.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "KoAnchorSelectionWidget.h"
+
+#include <array>
+#include <QToolButton>
+#include <QButtonGroup>
+#include <QGridLayout>
+#include <QFontMetrics>
+#include "kis_icon_utils.h"
+
+#include "kis_debug.h"
+#include "kis_signals_blocker.h"
+#include "kis_algebra_2d.h"
+
+
+struct Q_DECL_HIDDEN KoAnchorSelectionWidget::Private {
+ std::array<QToolButton*, KoFlake::NumAnchorPositions> buttons;
+ QButtonGroup *buttonGroup;
+
+};
+
+KoAnchorSelectionWidget::KoAnchorSelectionWidget(QWidget *parent)
+ : QWidget(parent),
+ m_d(new Private)
+{
+ QVector<QIcon> icons;
+
+ icons << KisIconUtils::loadIcon("arrow-topleft");
+ icons << KisIconUtils::loadIcon("arrow-up");
+ icons << KisIconUtils::loadIcon("arrow-topright");
+ icons << KisIconUtils::loadIcon("arrow-left");
+ icons << QIcon(); // center
+ icons << KisIconUtils::loadIcon("arrow-right");
+ icons << KisIconUtils::loadIcon("arrow-downleft");
+ icons << KisIconUtils::loadIcon("arrow-down");
+ icons << KisIconUtils::loadIcon("arrow-downright");
+ icons << QIcon(); // no anchor
+
+ QGridLayout *gridLayout = new QGridLayout(this);
+ gridLayout->setSpacing(0);
+ gridLayout->setContentsMargins(0,0,0,0);
+
+ m_d->buttonGroup = new QButtonGroup(this);
+
+ for (int i = 0; i < KoFlake::NumAnchorPositions; i++) {
+ QToolButton *button = new QToolButton(this);
+ button->setCheckable(true);
+ //button->setAutoRaise(true);
+ button->setAutoExclusive(true);
+ button->setIcon(icons[i]);
+ button->setFocusPolicy(Qt::NoFocus);
+ button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ if (i != KoFlake::NoAnchor) {
+ gridLayout->addWidget(button, i / 3, i % 3, Qt::AlignCenter);
+ } else {
+ button->setVisible(false);
+ }
+
+ m_d->buttonGroup->addButton(button, i);
+ m_d->buttons[i] = button;
+ }
+ connect(m_d->buttonGroup, SIGNAL(buttonClicked(int)), SLOT(slotGroupClicked(int)));
+
+ setLayout(gridLayout);
+}
+
+KoAnchorSelectionWidget::~KoAnchorSelectionWidget()
+{
+}
+
+KoFlake::AnchorPosition KoAnchorSelectionWidget::value() const
+{
+ return KoFlake::AnchorPosition(m_d->buttonGroup->checkedId());
+}
+
+QPointF KoAnchorSelectionWidget::value(const QRectF rect, bool *valid) const
+{
+ KoFlake::AnchorPosition anchor = this->value();
+ return anchorToPoint(anchor, rect, valid);
+}
+
+void KoAnchorSelectionWidget::setValue(KoFlake::AnchorPosition value)
+{
+ if (value == this->value()) return;
+
+ KisSignalsBlocker b(m_d->buttonGroup);
+
+ if (value >= 0) {
+ m_d->buttonGroup->button(int(value))->setChecked(true);
+ } else {
+ QAbstractButton *button = m_d->buttonGroup->checkedButton();
+ if (button) {
+ button->setChecked(false);
+ }
+ }
+
+ emit valueChanged(value);
+}
+
+QSize KoAnchorSelectionWidget::sizeHint() const
+{
+ const QSize minSize = minimumSizeHint();
+ const int preferredHint = qMax(minSize.height(), height());
+ return QSize(preferredHint, preferredHint);
+}
+
+QSize KoAnchorSelectionWidget::minimumSizeHint() const
+{
+ QFontMetrics metrics(this->font());
+ const int minHeight = 3 * (metrics.height() + 5);
+ return QSize(minHeight, minHeight);
+}
+
+void KoAnchorSelectionWidget::slotGroupClicked(int id)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(id >= 0 && id < KoFlake::NumAnchorPositions);
+ emit valueChanged(KoFlake::AnchorPosition(id));
+}
+
diff --git a/libs/widgets/KoAnchorSelectionWidget.h b/libs/widgets/KoAnchorSelectionWidget.h
new file mode 100644
index 0000000000..f7e83afb76
--- /dev/null
+++ b/libs/widgets/KoAnchorSelectionWidget.h
@@ -0,0 +1,53 @@
+/*
+ * 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 KOANCHORSELECTIONWIDGET_H
+#define KOANCHORSELECTIONWIDGET_H
+
+#include <QWidget>
+#include <KoFlake.h>
+
+#include "kritawidgets_export.h"
+
+
+class KRITAWIDGETS_EXPORT KoAnchorSelectionWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit KoAnchorSelectionWidget(QWidget *parent = 0);
+ ~KoAnchorSelectionWidget();
+
+ KoFlake::AnchorPosition value() const;
+ QPointF value(const QRectF rect, bool *valid) const;
+
+ void setValue(KoFlake::AnchorPosition value);
+
+ QSize sizeHint() const;
+ QSize minimumSizeHint() const;
+
+Q_SIGNALS:
+ void valueChanged(KoFlake::AnchorPosition id);
+
+public Q_SLOTS:
+ void slotGroupClicked(int id);
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
+};
+
+#endif // KOANCHORSELECTIONWIDGET_H
diff --git a/libs/widgets/KoColorPopupAction.cpp b/libs/widgets/KoColorPopupAction.cpp
index 8eba94505c..05f6b1d460 100644
--- a/libs/widgets/KoColorPopupAction.cpp
+++ b/libs/widgets/KoColorPopupAction.cpp
@@ -1,246 +1,249 @@
/* This file is part of the KDE project
* Copyright (c) 2007 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2007 Fredy Yanardi <fyanardi@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 "KoColorPopupAction.h"
#include "KoColorSetWidget.h"
#include "KoTriangleColorSelector.h"
#include "KoColorSlider.h"
#include "KoCheckerBoardPainter.h"
#include "KoResourceServer.h"
#include "KoResourceServerProvider.h"
#include <KoColorSpaceRegistry.h>
#include <KoColor.h>
#include <WidgetsDebug.h>
#include <klocalizedstring.h>
#include <QPainter>
#include <QWidgetAction>
#include <QMenu>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QToolButton>
class KoColorPopupAction::KoColorPopupActionPrivate
{
public:
KoColorPopupActionPrivate()
: colorSetWidget(0), colorChooser(0), opacitySlider(0), menu(0), checkerPainter(4)
, showFilter(true), applyMode(true), firstTime(true)
{}
~KoColorPopupActionPrivate()
{
delete colorSetWidget;
delete colorChooser;
delete opacitySlider;
delete menu;
}
KoColor currentColor;
KoColor buddyColor;
KoColorSetWidget *colorSetWidget;
KoTriangleColorSelector * colorChooser;
KoColorSlider * opacitySlider;
QMenu *menu;
KoCheckerBoardPainter checkerPainter;
bool showFilter;
bool applyMode;
bool firstTime;
};
KoColorPopupAction::KoColorPopupAction(QObject *parent)
: QAction(parent),
d(new KoColorPopupActionPrivate())
{
d->menu = new QMenu();
QWidget *widget = new QWidget(d->menu);
QWidgetAction *wdgAction = new QWidgetAction(d->menu);
d->colorSetWidget = new KoColorSetWidget(widget);
d->colorChooser = new KoTriangleColorSelector( widget );
// prevent mouse release on color selector from closing popup
d->colorChooser->setAttribute( Qt::WA_NoMousePropagation );
d->opacitySlider = new KoColorSlider( Qt::Vertical, widget );
d->opacitySlider->setFixedWidth(25);
d->opacitySlider->setRange(0, 255);
d->opacitySlider->setValue(255);
d->opacitySlider->setToolTip( i18n( "Opacity" ) );
QGridLayout * layout = new QGridLayout( widget );
layout->addWidget( d->colorSetWidget, 0, 0, 1, -1 );
layout->addWidget( d->colorChooser, 1, 0 );
layout->addWidget( d->opacitySlider, 1, 1 );
layout->setMargin(4);
wdgAction->setDefaultWidget(widget);
d->menu->addAction(wdgAction);
setMenu(d->menu);
new QHBoxLayout(d->menu);
d->menu->layout()->addWidget(widget);
d->menu->layout()->setMargin(0);
connect(this, SIGNAL(triggered()), this, SLOT(emitColorChanged()));
connect(d->colorSetWidget, SIGNAL(colorChanged(const KoColor &, bool)), this, SLOT(colorWasSelected(const KoColor &, bool)));
connect( d->colorChooser, SIGNAL( colorChanged( const QColor &) ),
this, SLOT( colorWasEdited( const QColor &) ) );
connect( d->opacitySlider, SIGNAL(valueChanged(int)),
this, SLOT(opacityWasChanged(int)));
}
KoColorPopupAction::~KoColorPopupAction()
{
delete d;
}
void KoColorPopupAction::setCurrentColor( const KoColor &color )
{
- d->colorChooser->slotSetColor( color );
-
KoColor minColor( color );
d->currentColor = minColor;
+ d->colorChooser->blockSignals(true);
+ d->colorChooser->slotSetColor(color);
+ d->colorChooser->blockSignals(false);
+
KoColor maxColor( color );
minColor.setOpacity( OPACITY_TRANSPARENT_U8 );
maxColor.setOpacity( OPACITY_OPAQUE_U8 );
+
d->opacitySlider->blockSignals( true );
d->opacitySlider->setColors( minColor, maxColor );
d->opacitySlider->setValue( color.opacityU8() );
d->opacitySlider->blockSignals( false );
updateIcon();
}
void KoColorPopupAction::setCurrentColor( const QColor &_color )
{
#ifndef NDEBUG
if (!_color.isValid()) {
warnWidgets << "Invalid color given, defaulting to black";
}
#endif
const QColor color(_color.isValid() ? _color : QColor(0,0,0,255));
setCurrentColor(KoColor(color, KoColorSpaceRegistry::instance()->rgb8() ));
}
QColor KoColorPopupAction::currentColor() const
{
return d->currentColor.toQColor();
}
KoColor KoColorPopupAction::currentKoColor() const
{
return d->currentColor;
}
void KoColorPopupAction::updateIcon()
{
QSize iconSize;
QToolButton *toolButton = dynamic_cast<QToolButton*>(parentWidget());
if (toolButton) {
iconSize = QSize(toolButton->iconSize());
} else {
iconSize = QSize(16, 16);
}
// This must be a QImage, as drawing to a QPixmap outside the
// UI thread will cause sporadic crashes.
QImage pm;
if (icon().isNull()) {
d->applyMode = false;
}
if(d->applyMode) {
pm = icon().pixmap(iconSize).toImage();
if (pm.isNull()) {
pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied);
pm.fill(Qt::transparent);
}
QPainter p(&pm);
p.fillRect(0, iconSize.height() - 4, iconSize.width(), 4, d->currentColor.toQColor());
p.end();
} else {
pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied);
pm.fill(Qt::transparent);
QPainter p(&pm);
d->checkerPainter.paint(p, QRect(QPoint(),iconSize));
p.fillRect(0, 0, iconSize.width(), iconSize.height(), d->currentColor.toQColor());
p.end();
}
setIcon(QIcon(QPixmap::fromImage(pm)));
}
void KoColorPopupAction::emitColorChanged()
{
emit colorChanged( d->currentColor );
}
void KoColorPopupAction::colorWasSelected(const KoColor &color, bool final)
{
d->currentColor = color;
if (final) {
menu()->hide();
emitColorChanged();
}
updateIcon();
}
void KoColorPopupAction::colorWasEdited( const QColor &color )
{
d->currentColor = KoColor( color, KoColorSpaceRegistry::instance()->rgb8() );
quint8 opacity = d->opacitySlider->value();
d->currentColor.setOpacity( opacity );
KoColor minColor = d->currentColor;
minColor.setOpacity( OPACITY_TRANSPARENT_U8 );
KoColor maxColor = minColor;
maxColor.setOpacity( OPACITY_OPAQUE_U8 );
d->opacitySlider->setColors( minColor, maxColor );
emitColorChanged();
updateIcon();
}
void KoColorPopupAction::opacityWasChanged( int opacity )
{
d->currentColor.setOpacity( quint8(opacity) );
emitColorChanged();
}
void KoColorPopupAction::slotTriggered(bool)
{
if (d->firstTime) {
KoResourceServer<KoColorSet>* srv = KoResourceServerProvider::instance()->paletteServer(false);
QList<KoColorSet*> palettes = srv->resources();
if (!palettes.empty()) {
d->colorSetWidget->setColorSet(palettes.first());
}
d->firstTime = false;
}
}
diff --git a/libs/widgets/KoFillConfigWidget.cpp b/libs/widgets/KoFillConfigWidget.cpp
deleted file mode 100644
index 63caf7bfb8..0000000000
--- a/libs/widgets/KoFillConfigWidget.cpp
+++ /dev/null
@@ -1,511 +0,0 @@
-/* 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 <QButtonGroup>
-#include <QLabel>
-#include <QSizePolicy>
-#include <QBitmap>
-#include <QAction>
-#include <QSharedPointer>
-
-#include <klocalizedstring.h>
-
-#include <KoGroupButton.h>
-#include <KoIcon.h>
-#include <KoColor.h>
-#include <KoColorPopupAction.h>
-#include "KoResourceServerProvider.h"
-#include "KoResourceServerAdapter.h"
-#include "KoResourceSelector.h"
-#include <KoSelection.h>
-#include <KoToolManager.h>
-#include <KoCanvasBase.h>
-#include <KoCanvasController.h>
-#include <KoCanvasResourceManager.h>
-#include <KoDocumentResourceManager.h>
-#include <KoShape.h>
-#include <KoShapeManager.h>
-#include <KoShapeController.h>
-#include <KoShapeBackground.h>
-#include <KoShapeBackgroundCommand.h>
-#include <KoColorBackground.h>
-#include <KoGradientBackground.h>
-#include <KoPatternBackground.h>
-#include <KoImageCollection.h>
-#include <KoResourcePopupAction.h>
-#include "KoZoomHandler.h"
-#include "KoColorPopupButton.h"
-
-static const char* const buttonnone[]={
- "16 16 3 1",
- "# c #000000",
- "e c #ff0000",
- "- c #ffffff",
- "################",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "#--------------#",
- "################"};
-
-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()
- : canvas(0)
- {
- }
- /// Apply the gradient stops using the shape background
- QSharedPointer<KoShapeBackground> applyFillGradientStops(KoShape *shape, const QGradientStops &stops)
- {
- if (! shape || ! stops.count()) {
- return QSharedPointer<KoShapeBackground>();
- }
-
- KoGradientBackground *newGradient = 0;
- QSharedPointer<KoGradientBackground> oldGradient = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
- if (oldGradient) {
- // just copy the gradient and set the new stops
- QGradient *g = KoFlake::cloneGradient(oldGradient->gradient());
- g->setStops(stops);
- newGradient = new KoGradientBackground(g);
- newGradient->setTransform(oldGradient->transform());
- }
- else {
- // No gradient yet, so create a new one.
- QLinearGradient *g = new QLinearGradient(QPointF(0, 0), QPointF(1, 1));
- g->setCoordinateMode(QGradient::ObjectBoundingMode);
- g->setStops(stops);
- newGradient = new KoGradientBackground(g);
- }
- return QSharedPointer<KoGradientBackground>(newGradient);
- }
-
- KoColorPopupButton *colorButton;
- QAction *noFillAction;
- KoColorPopupAction *colorAction;
- KoResourcePopupAction *gradientAction;
- KoResourcePopupAction *patternAction;
- QButtonGroup *group;
-
- QWidget *spacer;
- KoCanvasBase *canvas;
-};
-
-KoFillConfigWidget::KoFillConfigWidget(QWidget *parent)
-: QWidget(parent)
-, d(new Private())
-{
- setObjectName("Fill widget");
- QHBoxLayout *layout = new QHBoxLayout(this);
- layout->setMargin(0);
- layout->setSpacing(0);
-
- d->group = new QButtonGroup(this);
- d->group->setExclusive(true);
-
- // The button for no fill
- KoGroupButton *button = new KoGroupButton(KoGroupButton::GroupLeft, this);
- QPixmap noFillButtonIcon((const char **) buttonnone);
- noFillButtonIcon.setMask(QBitmap(noFillButtonIcon));
- button->setIcon(noFillButtonIcon);
- button->setToolTip(i18nc("No stroke or fill", "None"));
- button->setCheckable(true);
- d->group->addButton(button, None);
- layout->addWidget(button);
-
- // The button for solid fill
- button = new KoGroupButton(KoGroupButton::GroupCenter, this);
- button->setIcon(QPixmap((const char **) buttonsolid));
- button->setToolTip(i18nc("Solid color stroke or fill", "Solid"));
- button->setCheckable(true);
- d->group->addButton(button, Solid);
- layout->addWidget(button);
-
- // The button for gradient fill
- button = new KoGroupButton(KoGroupButton::GroupCenter, this);
- button->setIcon(QPixmap((const char **) buttongradient));
- button->setToolTip(i18n("Gradient"));
- button->setCheckable(true);
- d->group->addButton(button, Gradient);
- layout->addWidget(button);
-
- // The button for pattern fill
- button = new KoGroupButton(KoGroupButton::GroupRight, this);
- button->setIcon(QPixmap((const char **) buttonpattern));
- button->setToolTip(i18n("Pattern"));
- button->setCheckable(true);
- d->group->addButton(button, Pattern);
- layout->addWidget(button);
-
- connect(d->group, SIGNAL(buttonClicked(int)), this, SLOT(styleButtonPressed(int)));
-
- d->colorButton = new KoColorPopupButton(this);
- d->colorButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- layout->addWidget(d->colorButton);
-
- d->noFillAction = new QAction(0);
-
- d->colorAction = new KoColorPopupAction(d->colorButton);
- d->colorAction->setToolTip(i18n("Change the filling color"));
- d->colorAction->setCurrentColor(Qt::white);
- d->colorButton->setDefaultAction(d->colorAction);
- d->colorButton->setPopupMode(QToolButton::InstantPopup);
- connect(d->colorAction, SIGNAL(colorChanged(const KoColor &)), this, SLOT(colorChanged()));
- connect(d->colorButton, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon()));
-
- // Gradient selector
- KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance();
- QSharedPointer<KoAbstractResourceServerAdapter> gradientResourceAdapter(new KoResourceServerAdapter<KoAbstractGradient>(serverProvider->gradientServer()));
- d->gradientAction = new KoResourcePopupAction(gradientResourceAdapter, d->colorButton);
- d->gradientAction->setToolTip(i18n("Change the filling gradient"));
- connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer<KoShapeBackground> )), this, SLOT(gradientChanged(QSharedPointer<KoShapeBackground> )));
- connect(d->colorButton, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon()));
-
- // 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()));
-
- // Spacer
- d->spacer = new QWidget();
- d->spacer->setObjectName("SpecialSpacer");
- layout->addWidget(d->spacer);
-
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
- if (selection) {
- d->canvas = canvasController->canvas();
- connect(selection, SIGNAL(selectionChanged()), this, SLOT(shapeChanged()));
- }
-}
-
-KoFillConfigWidget::~KoFillConfigWidget()
-{
- delete d->noFillAction;
- delete d;
-}
-
-void KoFillConfigWidget::setCanvas( KoCanvasBase *canvas )
-{
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
-
- connect(selection, SIGNAL(selectionChanged()), this, SLOT(shapeChanged()));
-
- d->canvas = canvas;
-}
-
-KoCanvasBase* KoFillConfigWidget::canvas()
-{
- return d->canvas;
-}
-
-QList<KoShape*> KoFillConfigWidget::currentShapes()
-{
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
- return selection->selectedShapes();
-}
-
-KoShape *KoFillConfigWidget::currentShape()
-{
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- KoSelection *selection = canvasController->canvas()->shapeManager()->selection();
- return selection->firstSelectedShape();
-}
-
-
-void KoFillConfigWidget::styleButtonPressed(int buttonId)
-{
- d->colorButton->setEnabled(true);
- switch (buttonId) {
- case KoFillConfigWidget::None:
- // Direct manipulation
- d->colorButton->setDefaultAction(d->noFillAction);
- d->colorButton->setDisabled(true);
- noColorSelected();
- break;
- case KoFillConfigWidget::Solid:
- d->colorButton->setDefaultAction(d->colorAction);
- colorChanged();
- break;
- case KoFillConfigWidget::Gradient:
- // Only select mode in the widget, don't set actual gradient :/
- d->colorButton->setDefaultAction(d->gradientAction);
- gradientChanged(d->gradientAction->currentBackground());
- 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;
- }
- d->colorButton->setPopupMode(QToolButton::InstantPopup);
-}
-
-void KoFillConfigWidget::noColorSelected()
-{
- QList<KoShape*> selectedShapes = currentShapes();
- if (selectedShapes.isEmpty()) {
- return;
- }
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- canvasController->canvas()->addCommand(new KoShapeBackgroundCommand(selectedShapes, QSharedPointer<KoShapeBackground>(0)));
-}
-
-void KoFillConfigWidget::colorChanged()
-{
- QList<KoShape*> selectedShapes = currentShapes();
- if (selectedShapes.isEmpty()) {
- return;
- }
-
- QSharedPointer<KoShapeBackground> fill(new KoColorBackground(d->colorAction->currentColor()));
- KUndo2Command *firstCommand = 0;
- foreach (KoShape *shape, selectedShapes) {
- if (! firstCommand) {
- firstCommand = new KoShapeBackgroundCommand(shape, fill);
- } else {
- new KoShapeBackgroundCommand(shape, fill, firstCommand);
- }
- }
-
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- canvasController->canvas()->addCommand(firstCommand);
-}
-
-void KoFillConfigWidget::gradientChanged(QSharedPointer<KoShapeBackground> background)
-{
- QList<KoShape*> selectedShapes = currentShapes();
- if (selectedShapes.isEmpty()) {
- return;
- }
-
- QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(background);
- if (!gradientBackground) {
- return;
- }
-
- QGradientStops newStops = gradientBackground->gradient()->stops();
- gradientBackground.clear();
-
- KUndo2Command *firstCommand = 0;
- foreach (KoShape *shape, selectedShapes) {
- QSharedPointer<KoShapeBackground> fill = d->applyFillGradientStops(shape, newStops);
- if (! fill) {
- continue;
- }
- if (! firstCommand) {
- firstCommand = new KoShapeBackgroundCommand(shape, fill);
- } else {
- new KoShapeBackgroundCommand(shape, fill, firstCommand);
- }
- }
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- canvasController->canvas()->addCommand(firstCommand);
-}
-
-void KoFillConfigWidget::patternChanged(QSharedPointer<KoShapeBackground> background)
-{
- QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(background);
- if (! patternBackground) {
- return;
- }
-
- QList<KoShape*> selectedShapes = currentShapes();
- if (selectedShapes.isEmpty()) {
- return;
- }
-
- KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
- KoImageCollection *imageCollection = canvasController->canvas()->shapeController()->resourceManager()->imageCollection();
- if (imageCollection) {
- QSharedPointer<KoPatternBackground> fill(new KoPatternBackground(imageCollection));
- fill->setPattern(patternBackground->pattern());
- canvasController->canvas()->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill));
- }
-}
-
-void KoFillConfigWidget::shapeChanged()
-{
- KoShape *shape = currentShape();
- if (! shape) {
- d->group->button(KoFillConfigWidget::None)->setChecked(false);
- d->group->button(KoFillConfigWidget::Solid)->setChecked(false);
- d->group->button(KoFillConfigWidget::Gradient)->setChecked(false);
- d->group->button(KoFillConfigWidget::Pattern)->setChecked(false);
- d->colorButton->setDisabled(true);
- return;
- }
-
- d->colorAction->blockSignals(true);
- updateWidget(shape);
- d->colorAction->blockSignals(false);
-}
-
-
-void KoFillConfigWidget::updateWidget(KoShape *shape)
-{
- if (! shape) {
- return;
- }
-
- KoZoomHandler zoomHandler;
- const qreal realWidth = zoomHandler.resolutionX() * width();
- const qreal realHeight = zoomHandler.resolutionX() * height();
-
- const qreal zoom = (realWidth > realHeight) ? realHeight : realWidth;
- zoomHandler.setZoom(zoom);
-
- shape->waitUntilReady(zoomHandler, false);
-
- d->colorButton->setEnabled(true);
- QSharedPointer<KoShapeBackground> background = shape->background();
- if (! background) {
- // No Fill
- d->group->button(KoFillConfigWidget::None)->setChecked(true);
- d->colorButton->setDefaultAction(d->noFillAction);
- d->colorButton->setDisabled(true);
- d->colorButton->setPopupMode(QToolButton::InstantPopup);
- return;
- }
-
- QSharedPointer<KoColorBackground> colorBackground = qSharedPointerDynamicCast<KoColorBackground>(background);
- QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(background);
- QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(background);
-
- if (colorBackground) {
- d->colorAction->setCurrentColor(colorBackground->color());
- d->group->button(KoFillConfigWidget::Solid)->setChecked(true);
- d->colorButton->setDefaultAction(d->colorAction);
- } else if (gradientBackground) {
- d->gradientAction->setCurrentBackground(background);
- d->group->button(KoFillConfigWidget::Gradient)->setChecked(true);
- d->colorButton->setDefaultAction(d->gradientAction);
- } else if (patternBackground) {
- d->patternAction->setCurrentBackground(background);
- d->group->button(KoFillConfigWidget::Pattern)->setChecked(true);
- d->colorButton->setDefaultAction(d->patternAction);
- } else {
- // No Fill
- d->group->button(KoFillConfigWidget::None)->setChecked(true);
- d->colorButton->setDefaultAction(d->noFillAction);
- d->colorButton->setDisabled(true);
- }
- d->colorButton->setPopupMode(QToolButton::InstantPopup);
-}
diff --git a/libs/widgets/KoFillConfigWidget.h b/libs/widgets/KoFillConfigWidget.h
deleted file mode 100644
index ffd459ee0a..0000000000
--- a/libs/widgets/KoFillConfigWidget.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/* 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.
- */
-
-#ifndef FILLCONFIGWIDGET_H
-#define FILLCONFIGWIDGET_H
-
-#include "kritawidgets_export.h"
-
-#include <QWidget>
-#include <QSharedPointer>
-
-class KoCanvasBase;
-class KoShapeBackground;
-class KoShape;
-
-/// A widget for configuring the fill of a shape
-class KRITAWIDGETS_EXPORT KoFillConfigWidget : public QWidget
-{
- Q_OBJECT
- enum StyleButton {
- None,
- Solid,
- Gradient,
- Pattern
- };
-public:
- explicit KoFillConfigWidget(QWidget *parent);
- ~KoFillConfigWidget();
-
- void setCanvas(KoCanvasBase *canvas);
-
- KoCanvasBase *canvas();
-
- /// Returns the list of the selected shape
- /// If you need to use only one shape, call currentShape()
- virtual QList<KoShape*> currentShapes();
-
- /// Returns the first selected shape of the resource
- virtual KoShape *currentShape();
-
-private Q_SLOTS:
- void styleButtonPressed(int buttonId);
-
- void noColorSelected();
-
- /// apply color changes to the selected shape
- void colorChanged();
-
- /// the gradient of the fill changed, apply the changes
- void gradientChanged(QSharedPointer<KoShapeBackground> background);
-
- /// the pattern of the fill changed, apply the changes
- void patternChanged(QSharedPointer<KoShapeBackground> background);
-
- virtual void shapeChanged();
-private:
- /// update the widget with the KoShape background
- void updateWidget(KoShape *shape);
-
- class Private;
- Private * const d;
-};
-
-#endif // FILLCONFIGWIDGET_H
diff --git a/libs/widgets/KoLineStyleSelector.cpp b/libs/widgets/KoLineStyleSelector.cpp
index 54ecde1320..026314b2a6 100644
--- a/libs/widgets/KoLineStyleSelector.cpp
+++ b/libs/widgets/KoLineStyleSelector.cpp
@@ -1,91 +1,93 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoLineStyleSelector.h"
#include "KoLineStyleModel_p.h"
#include "KoLineStyleItemDelegate_p.h"
#include <QPen>
#include <QPainter>
class Q_DECL_HIDDEN KoLineStyleSelector::Private
{
public:
Private(QWidget *parent)
: model(new KoLineStyleModel(parent))
{
}
KoLineStyleModel *model;
};
KoLineStyleSelector::KoLineStyleSelector(QWidget *parent)
: QComboBox(parent), d(new Private(this))
{
setModel(d->model);
setItemDelegate(new KoLineStyleItemDelegate(this));
}
KoLineStyleSelector::~KoLineStyleSelector()
{
delete d;
}
void KoLineStyleSelector::paintEvent(QPaintEvent *pe)
{
QComboBox::paintEvent(pe);
QStyleOptionComboBox option;
option.initFrom(this);
option.frame = hasFrame();
QRect r = style()->subControlRect(QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxEditField, this);
if (!option.frame) // frameless combo boxes have smaller margins but styles do not take this into account
r.adjust(-14, 0, 14, 1);
QPen pen = itemData(currentIndex(), Qt::DecorationRole).value<QPen>();
- pen.setBrush(option.palette.text()); // use the view-specific palette; the model hardcodes this to black
QPainter painter(this);
painter.setPen(pen);
+ if (!(option.state & QStyle::State_Enabled)) {
+ painter.setOpacity(0.5);
+ }
painter.drawLine(r.left(), r.center().y(), r.right(), r.center().y());
}
bool KoLineStyleSelector::addCustomStyle(const QVector<qreal> &style)
{
return d->model->addCustomStyle(style);
}
void KoLineStyleSelector::setLineStyle(Qt::PenStyle style, const QVector<qreal> &dashes)
{
int index = d->model->setLineStyle(style, dashes);
if (index >= 0)
setCurrentIndex(index);
}
Qt::PenStyle KoLineStyleSelector::lineStyle() const
{
QPen pen = itemData(currentIndex(), Qt::DecorationRole).value<QPen>();
return pen.style();
}
QVector<qreal> KoLineStyleSelector::lineDashes() const
{
QPen pen = itemData(currentIndex(), Qt::DecorationRole).value<QPen>();
return pen.dashPattern();
}
diff --git a/libs/widgets/KoMarkerItemDelegate.cpp b/libs/widgets/KoMarkerItemDelegate.cpp
index acfa19db4a..37c4d6c3bb 100644
--- a/libs/widgets/KoMarkerItemDelegate.cpp
+++ b/libs/widgets/KoMarkerItemDelegate.cpp
@@ -1,76 +1,70 @@
/* This file is part of the KDE project
* Copyright (C) 2011 Thorsten Zachmann <zachmann@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 "KoMarkerItemDelegate.h"
#include <KoPathShape.h>
#include <KoMarker.h>
#include <QPainter>
#include <QPen>
-KoMarkerItemDelegate::KoMarkerItemDelegate(KoMarkerData::MarkerPosition position, QObject *parent)
+#include "kis_global.h"
+
+KoMarkerItemDelegate::KoMarkerItemDelegate(KoFlake::MarkerPosition position, QObject *parent)
: QAbstractItemDelegate(parent)
, m_position(position)
{
}
KoMarkerItemDelegate::~KoMarkerItemDelegate()
{
}
void KoMarkerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
- painter->save();
-
- if (option.state & QStyle::State_Selected)
+ if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
-
- bool antialiasing = painter->testRenderHint(QPainter::Antialiasing);
- if (!antialiasing) {
- painter->setRenderHint(QPainter::Antialiasing, true);
}
- KoPathShape pathShape;
- pathShape.moveTo(QPointF(option.rect.left(), option.rect.center().y()));
- pathShape.lineTo(QPointF(option.rect.right(), option.rect.center().y()));
- KoMarker *marker = index.data(Qt::DecorationRole).value<KoMarker*>();
- if (marker != 0) {
- pathShape.setMarker(marker, m_position);
- }
-
- // paint marker
QPen pen(option.palette.text(), 2);
- QPainterPath path = pathShape.pathStroke(pen);
- painter->fillPath(path, pen.brush());
-
- if (!antialiasing) {
- painter->setRenderHint(QPainter::Antialiasing, false);
- }
-
- painter->restore();
+ KoMarker *marker = index.data(Qt::DecorationRole).value<KoMarker*>();
+ drawMarkerPreview(painter, option.rect.adjusted(1, 0, -1, 0), pen, marker, m_position);
}
QSize KoMarkerItemDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex &index) const
{
Q_UNUSED(option)
Q_UNUSED(index)
return QSize(80,30);
}
+
+void KoMarkerItemDelegate::drawMarkerPreview(QPainter *painter, const QRect &rect, const QPen &pen, KoMarker *marker, KoFlake::MarkerPosition position)
+{
+ if (marker) {
+ marker->drawPreview(painter, rect, pen, position);
+ } else {
+ const qreal centerY = QRectF(rect).center().y();
+ QPen oldPen = painter->pen();
+ painter->setPen(pen);
+ painter->drawLine(rect.left(), centerY, rect.right(), centerY);
+ painter->setPen(oldPen);
+ }
+}
diff --git a/libs/widgets/KoMarkerItemDelegate.h b/libs/widgets/KoMarkerItemDelegate.h
index e8b847f5ef..717758097b 100644
--- a/libs/widgets/KoMarkerItemDelegate.h
+++ b/libs/widgets/KoMarkerItemDelegate.h
@@ -1,40 +1,44 @@
/* 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 KOMARKERITEMDELEGATE_H
#define KOMARKERITEMDELEGATE_H
// Calligra
-#include <KoMarkerData.h>
+#include <KoFlake.h>
// Qt
#include <QAbstractItemDelegate>
+class KoMarker;
+
class KoMarkerItemDelegate : public QAbstractItemDelegate
{
public:
- explicit KoMarkerItemDelegate(KoMarkerData::MarkerPosition position, QObject *parent = 0);
+ explicit KoMarkerItemDelegate(KoFlake::MarkerPosition position, QObject *parent = 0);
virtual ~KoMarkerItemDelegate();
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
virtual QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &index) const;
+
+ static void drawMarkerPreview(QPainter *painter, const QRect &rect, const QPen &pen, KoMarker *marker, KoFlake::MarkerPosition position);
private:
- KoMarkerData::MarkerPosition m_position;
+ KoFlake::MarkerPosition m_position;
};
#endif /* KOMARKERITEMDELEGATE_H */
diff --git a/libs/widgets/KoMarkerModel.cpp b/libs/widgets/KoMarkerModel.cpp
index a325f2a36b..36af229919 100644
--- a/libs/widgets/KoMarkerModel.cpp
+++ b/libs/widgets/KoMarkerModel.cpp
@@ -1,91 +1,129 @@
/* 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 "KoMarkerModel.h"
// Calligra
#include <KoMarker.h>
// Qt
#include <QSize>
-KoMarkerModel::KoMarkerModel(const QList<KoMarker*> markers, KoMarkerData::MarkerPosition position, QObject *parent)
-: QAbstractListModel(parent)
-, m_markers(markers)
-, m_markerPosition(position)
+KoMarkerModel::KoMarkerModel(const QList<KoMarker*> markers, KoFlake::MarkerPosition position, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_markerPosition(position)
+ , m_temporaryMarkerPosition(-1)
{
+ Q_FOREACH (KoMarker *marker, markers) {
+ m_markers.append(QExplicitlySharedDataPointer<KoMarker>(marker));
+ }
}
KoMarkerModel::~KoMarkerModel()
{
}
int KoMarkerModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_markers.count();
}
QVariant KoMarkerModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
switch(role) {
case Qt::DecorationRole:
if (index.row() < m_markers.size()) {
- return QVariant::fromValue<KoMarker*>(m_markers.at(index.row()));
+ return QVariant::fromValue<KoMarker*>(m_markers.at(index.row()).data());
}
return QVariant();
case Qt::SizeHintRole:
return QSize(80,30);
default:
return QVariant();
}
}
int KoMarkerModel::markerIndex(KoMarker *marker) const
{
- return m_markers.indexOf(marker);
+ for (int i = 0; i < m_markers.size(); i++) {
+ if (m_markers[i] == marker) return i;
+ if (m_markers[i] && marker && *m_markers[i] == *marker) return i;
+ }
+
+ return false;
+}
+
+int KoMarkerModel::addTemporaryMarker(KoMarker *marker)
+{
+ if (m_temporaryMarkerPosition >= 0) {
+ removeTemporaryMarker();
+ }
+
+ m_temporaryMarkerPosition = m_markers.size() > 0 ? 1 : 0;
+ beginInsertRows(QModelIndex(), m_temporaryMarkerPosition, m_temporaryMarkerPosition);
+ m_markers.prepend(QExplicitlySharedDataPointer<KoMarker>(marker));
+
+ endInsertRows();
+
+ return m_temporaryMarkerPosition;
+}
+
+void KoMarkerModel::removeTemporaryMarker()
+{
+ if (m_temporaryMarkerPosition >= 0) {
+ beginRemoveRows(QModelIndex(), m_temporaryMarkerPosition, m_temporaryMarkerPosition);
+ m_markers.removeAt(m_temporaryMarkerPosition);
+ m_temporaryMarkerPosition = -1;
+ endRemoveRows();
+ }
+}
+
+int KoMarkerModel::temporaryMarkerPosition() const
+{
+ return m_temporaryMarkerPosition;
}
QVariant KoMarkerModel::marker(int index, int role) const
{
if (index < 0){
return QVariant();
}
switch(role) {
case Qt::DecorationRole:
if (index< m_markers.size()) {
- return QVariant::fromValue<KoMarker*>(m_markers.at(index));
+ return QVariant::fromValue<KoMarker*>(m_markers.at(index).data());
}
return QVariant();
case Qt::SizeHintRole:
return QSize(80, 30);
default:
return QVariant();
}
}
-KoMarkerData::MarkerPosition KoMarkerModel::position() const
+KoFlake::MarkerPosition KoMarkerModel::position() const
{
return m_markerPosition;
}
diff --git a/libs/widgets/KoMarkerModel.h b/libs/widgets/KoMarkerModel.h
index 7f7402b043..46982e2608 100644
--- a/libs/widgets/KoMarkerModel.h
+++ b/libs/widgets/KoMarkerModel.h
@@ -1,46 +1,57 @@
/* 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 KOMARKERMODEL_H
#define KOMARKERMODEL_H
-#include <KoMarkerData.h>
+#include <KoFlake.h>
#include <QAbstractListModel>
+#include <QExplicitlySharedDataPointer>
+
class KoMarker;
class KoMarkerModel : public QAbstractListModel
{
public:
- KoMarkerModel(const QList<KoMarker*> markers, KoMarkerData::MarkerPosition position, QObject *parent = 0);
+ KoMarkerModel(const QList<KoMarker*> markers, KoFlake::MarkerPosition position, QObject *parent = 0);
virtual ~KoMarkerModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
int markerIndex(KoMarker *marker) const;
+
+ // returns index of the newly added temporary marker
+ int addTemporaryMarker(KoMarker *marker);
+ // removes a temporary marker added by \ref addTemporaryMarker
+ void removeTemporaryMarker();
+
+ int temporaryMarkerPosition() const;
+
QVariant marker(int index, int role = Qt::UserRole) const;
- KoMarkerData::MarkerPosition position() const;
+ KoFlake::MarkerPosition position() const;
private:
- QList<KoMarker*> m_markers;
- KoMarkerData::MarkerPosition m_markerPosition;
+ QList<QExplicitlySharedDataPointer<KoMarker>> m_markers;
+ KoFlake::MarkerPosition m_markerPosition;
+ int m_temporaryMarkerPosition;
};
#endif /* KOMARKERMODEL_H */
diff --git a/libs/widgets/KoMarkerSelector.cpp b/libs/widgets/KoMarkerSelector.cpp
index cd631792bb..10fdbd641f 100644
--- a/libs/widgets/KoMarkerSelector.cpp
+++ b/libs/widgets/KoMarkerSelector.cpp
@@ -1,114 +1,112 @@
/* 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 "KoMarkerSelector.h"
#include "KoMarker.h"
#include "KoMarkerModel.h"
#include "KoMarkerItemDelegate.h"
#include "KoPathShape.h"
#include <QPainter>
#include <QPainterPath>
class KoMarkerSelector::Private
{
public:
- Private(KoMarkerData::MarkerPosition position, QWidget *parent)
+ Private(KoFlake::MarkerPosition position, QWidget *parent)
: model(new KoMarkerModel(QList<KoMarker*>(), position, parent))
{}
KoMarkerModel *model;
};
-KoMarkerSelector::KoMarkerSelector(KoMarkerData::MarkerPosition position, QWidget *parent)
+KoMarkerSelector::KoMarkerSelector(KoFlake::MarkerPosition position, QWidget *parent)
: QComboBox(parent)
, d(new Private(position, this))
{
setModel(d->model);
setItemDelegate(new KoMarkerItemDelegate(position, this));
}
KoMarkerSelector::~KoMarkerSelector()
{
delete d;
}
void KoMarkerSelector::paintEvent(QPaintEvent *pe)
{
QComboBox::paintEvent(pe);
QStyleOptionComboBox option;
option.initFrom(this);
option.frame = hasFrame();
QRect rect = style()->subControlRect(QStyle::CC_ComboBox, &option, QStyle::SC_ComboBoxEditField, this);
if (!option.frame) { // frameless combo boxes have smaller margins but styles do not take this into account
rect.adjust(-14, 0, 14, 1);
}
QPainter painter(this);
bool antialiasing = painter.testRenderHint(QPainter::Antialiasing);
if (!antialiasing) {
painter.setRenderHint(QPainter::Antialiasing, true);
}
- KoPathShape pathShape;
- pathShape.moveTo(QPointF(rect.left(), rect.center().y()));
- pathShape.lineTo(QPointF(rect.right(), rect.center().y()));
-
- KoMarker *marker = itemData(currentIndex(), Qt::DecorationRole).value<KoMarker*>();
- if (marker != 0) {
- pathShape.setMarker(marker, d->model->position());
+ if (!(option.state & QStyle::State_Enabled)) {
+ painter.setOpacity(0.5);
}
-
- // paint marker
- QPen pen(option.palette.text(), 2);
- QPainterPath path = pathShape.pathStroke(pen);
- painter.fillPath(path, pen.brush());
+ QPen pen(Qt::black, 2);
+ KoMarker *marker = itemData(currentIndex(), Qt::DecorationRole).value<KoMarker*>();
+ KoMarkerItemDelegate::drawMarkerPreview(&painter, rect, pen, marker, d->model->position());
if (!antialiasing) {
painter.setRenderHint(QPainter::Antialiasing, false);
}
}
void KoMarkerSelector::setMarker(KoMarker *marker)
{
int index = d->model->markerIndex(marker);
if (index >= 0) {
setCurrentIndex(index);
+ if (index != d->model->temporaryMarkerPosition()) {
+ d->model->removeTemporaryMarker();
+ }
+ } else {
+ setCurrentIndex(d->model->addTemporaryMarker(marker));
}
}
KoMarker *KoMarkerSelector::marker() const
{
return itemData(currentIndex(), Qt::DecorationRole).value<KoMarker*>();
}
void KoMarkerSelector::updateMarkers(const QList<KoMarker*> markers)
{
- KoMarkerModel *model = new KoMarkerModel(markers,d->model->position(), this);
+ KoMarkerModel *model = new KoMarkerModel(markers, d->model->position(), this);
d->model = model;
// this deletes the old model
setModel(model);
}
QVariant KoMarkerSelector::itemData(int index, int role) const
{
return d->model->marker(index, role);
}
diff --git a/libs/widgets/KoMarkerSelector.h b/libs/widgets/KoMarkerSelector.h
index cfe18960e9..b10127e986 100644
--- a/libs/widgets/KoMarkerSelector.h
+++ b/libs/widgets/KoMarkerSelector.h
@@ -1,57 +1,59 @@
/* 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 KOMARKERSELECTOR_H
#define KOMARKERSELECTOR_H
+#include "kritawidgets_export.h"
+
+#include <KoFlake.h>
#include <QComboBox>
-#include <KoMarkerData.h>
class KoMarker;
-class KoMarkerSelector : public QComboBox
+class KRITAWIDGETS_EXPORT KoMarkerSelector : public QComboBox
{
Q_OBJECT
public:
- explicit KoMarkerSelector(KoMarkerData::MarkerPosition position, QWidget *parent = 0);
+ explicit KoMarkerSelector(KoFlake::MarkerPosition position, QWidget *parent = 0);
virtual ~KoMarkerSelector();
// set the current marker style
void setMarker(KoMarker *marker);
// return the current marker style
KoMarker *marker() const;
/// reimplement
QVariant itemData(int index, int role = Qt::UserRole) const;
/**
* Set the available markers in the document.
*/
void updateMarkers(const QList<KoMarker*> markers);
protected:
void paintEvent(QPaintEvent *pe);
private:
class Private;
Private * const d;
};
#endif /* KOMARKERSELECTOR_H */
diff --git a/libs/widgets/KoPositionSelector.cpp b/libs/widgets/KoPositionSelector.cpp
deleted file mode 100644
index 679b9f2670..0000000000
--- a/libs/widgets/KoPositionSelector.cpp
+++ /dev/null
@@ -1,233 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007 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 "KoPositionSelector.h"
-
-#include <QRadioButton>
-#include <QGridLayout>
-#include <QButtonGroup>
-#include <QPainter>
-#include <WidgetsDebug.h>
-#include <QStyleOption>
-
-#define GAP 0
-
-class Q_DECL_HIDDEN KoPositionSelector::Private
-{
-public:
- Private()
- : position(KoFlake::TopLeftCorner)
- {
- topLeft = createButton(KoFlake::TopLeftCorner);
- topLeft->setChecked(true);
- topRight = createButton(KoFlake::TopRightCorner);
- center = createButton(KoFlake::CenteredPosition);
- bottomRight = createButton(KoFlake::BottomRightCorner);
- bottomLeft = createButton(KoFlake::BottomLeftCorner);
- }
-
- QRadioButton *createButton(int id) {
- QRadioButton *b = new QRadioButton();
- buttonGroup.addButton(b, id);
- return b;
- }
-
- QRadioButton *topLeft, *topRight, *center, *bottomRight, *bottomLeft;
- QButtonGroup buttonGroup;
- KoFlake::Position position;
-};
-
-class RadioLayout : public QLayout {
-public:
- RadioLayout(QWidget *parent)
- : QLayout(parent)
- {
- }
-
- ~RadioLayout() override
- {
- Q_FOREACH ( const Item & item, items )
- delete item.child;
- items.clear();
- }
-
- void setGeometry (const QRect &geom) override {
- QSize prefSize = calcSizes();
-
- qreal columnWidth, rowHeight;
- if (geom.width() <= minimum.width())
- columnWidth = geom.width() / (qreal) maxCol;
- else
- columnWidth = prefSize.width() + GAP;
- if (geom.height() <= minimum.height())
- rowHeight = geom.height() / (qreal) maxRow;
- else
- rowHeight = prefSize.height() + GAP;
- // padding inside row and column so that radio button is centered
- QPoint padding( qRound(0.5 * (columnWidth - prefSize.width())), qRound(0.5 * (rowHeight - prefSize.height())));
- // offset so that all the radio button are centered within the widget
- qreal offsetX = 0.5 * (geom.width()- static_cast<qreal>(maxCol) * columnWidth);
- qreal offsetY = 0.5 * (geom.height() - static_cast<qreal>(maxRow) * rowHeight);
- QPoint offset( qRound(offsetX), qRound(offsetY));
- Q_FOREACH (const Item & item, items) {
- QPoint point( qRound(item.column * columnWidth), qRound(item.row * rowHeight) );
- QRect rect(point + offset + padding + geom.topLeft(), prefSize);
- item.child->setGeometry(rect);
- }
- }
-
- QSize calcSizes() {
- QSize prefSize;
- maxRow = 0;
- maxCol = 0;
- Q_FOREACH (const Item & item, items) {
- if(prefSize.isEmpty()) {
- QAbstractButton *but = dynamic_cast<QAbstractButton*> (item.child->widget());
- Q_ASSERT(but);
- QStyleOptionButton opt;
- opt.initFrom(but);
- prefSize = QSize(but->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth, &opt, but),
- but->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorHeight, &opt, but));
- }
- maxRow = qMax(maxRow, item.row);
- maxCol = qMax(maxCol, item.column);
- }
- maxCol++; maxRow++; // due to being zero-based.
- preferred = QSize(maxCol * prefSize.width() + (maxCol-1) * GAP, maxRow * prefSize.height() + (maxRow-1) * GAP);
- minimum = QSize(maxCol * prefSize.width(), maxRow * prefSize.height());
- return prefSize;
- }
-
- QLayoutItem *itemAt (int index) const override {
- if( index < count() )
- return items.at(index).child;
- else
- return 0;
- }
-
- QLayoutItem *takeAt (int index) override {
- Q_ASSERT(index < count());
- Item item = items.takeAt(index);
- return item.child;
- }
-
- int count () const override {
- return items.count();
- }
-
- void addItem(QLayoutItem *) override {
- Q_ASSERT(0);
- }
-
- QSize sizeHint() const override {
- if(preferred.isEmpty())
- const_cast<RadioLayout*> (this)->calcSizes();
- return preferred;
- }
-
- QSize minimumSize() const override {
- if(minimum.isEmpty())
- const_cast<RadioLayout*> (this)->calcSizes();
- return minimum;
- }
-
- void addWidget(QRadioButton *widget, int row, int column) {
- addChildWidget(widget);
- Item newItem;
- newItem.child = new QWidgetItem(widget);
- newItem.row = row;
- newItem.column = column;
- items.append(newItem);
- }
-
-private:
- struct Item {
- QLayoutItem *child;
- int column;
- int row;
- };
- QList<Item> items;
- QSize preferred, minimum;
- int maxCol, maxRow;
-};
-
-KoPositionSelector::KoPositionSelector(QWidget *parent)
- : QWidget(parent),
- d(new Private())
-{
- setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
- RadioLayout *lay = new RadioLayout(this);
- lay->addWidget(d->topLeft, 0, 0);
- lay->addWidget(d->topRight, 0, 2);
- lay->addWidget(d->center, 1, 1);
- lay->addWidget(d->bottomRight, 2, 2);
- lay->addWidget(d->bottomLeft, 2, 0);
- setLayout(lay);
-
- connect(&d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(positionChanged(int)));
-}
-
-KoPositionSelector::~KoPositionSelector() {
- delete d;
-}
-
-KoFlake::Position KoPositionSelector::position() const {
- return d->position;
-}
-
-void KoPositionSelector::setPosition(KoFlake::Position position) {
- d->position = position;
- switch(d->position) {
- case KoFlake::TopLeftCorner:
- d->topLeft->setChecked(true);
- break;
- case KoFlake::TopRightCorner:
- d->topRight->setChecked(true);
- break;
- case KoFlake::CenteredPosition:
- d->center->setChecked(true);
- break;
- case KoFlake::BottomLeftCorner:
- d->bottomLeft->setChecked(true);
- break;
- case KoFlake::BottomRightCorner:
- d->bottomRight->setChecked(true);
- break;
- }
-}
-
-void KoPositionSelector::positionChanged(int position) {
- d->position = static_cast<KoFlake::Position> (position);
- emit positionSelected(d->position);
-}
-
-void KoPositionSelector::paintEvent (QPaintEvent *) {
- QPainter painter( this );
- QPen pen(Qt::black);
- int width;
- if (d->topLeft->width() %2 == 0)
- width = 2;
- else
- width = 3;
- pen.setWidth(width);
- painter.setPen(pen);
- painter.drawRect( QRect( d->topLeft->geometry().center(), d->bottomRight->geometry().center() ) );
- painter.end();
-}
diff --git a/libs/widgets/KoPositionSelector.h b/libs/widgets/KoPositionSelector.h
deleted file mode 100644
index 48625e1a92..0000000000
--- a/libs/widgets/KoPositionSelector.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* This file is part of the KDE project
- * 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 KOPOSITIONSELECTOR_H
-#define KOPOSITIONSELECTOR_H
-
-#include <QWidget>
-#include <KoFlake.h>
-#include "kritawidgets_export.h"
-
-/**
- * Widget to show a set of radio buttons so the user can select a position.
- */
-class KRITAWIDGETS_EXPORT KoPositionSelector : public QWidget
-{
- Q_OBJECT
-public:
- explicit KoPositionSelector(QWidget *parent);
- ~KoPositionSelector();
-
- KoFlake::Position position() const;
- void setPosition(KoFlake::Position position);
-
-Q_SIGNALS:
- void positionSelected(KoFlake::Position position);
-
-protected:
- /// reimplemented
- virtual void paintEvent (QPaintEvent *event);
-
-private Q_SLOTS:
- void positionChanged(int position);
-
-private:
- class Private;
- Private * const d;
-};
-
-#endif
diff --git a/libs/widgets/KoResourceItemChooser.cpp b/libs/widgets/KoResourceItemChooser.cpp
index a4b01627ec..0894cc021f 100644
--- a/libs/widgets/KoResourceItemChooser.cpp
+++ b/libs/widgets/KoResourceItemChooser.cpp
@@ -1,553 +1,554 @@
/* This file is part of the KDE project
Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
Copyright (c) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
Copyright (c) 2013 Sascha Suelzer <s.suelzer@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoResourceItemChooser.h"
#include <math.h>
#include <QGridLayout>
#include <QButtonGroup>
#include <QPushButton>
#include <QHeaderView>
#include <QAbstractProxyModel>
#include <QLabel>
#include <QScrollArea>
#include <QImage>
#include <QPixmap>
#include <QPainter>
#include <QSplitter>
#include <QToolButton>
#include <QWheelEvent>
#include <klocalizedstring.h>
#include <KoIcon.h>
#include <KoFileDialog.h>
#include <KisMimeDatabase.h>
#include "KoResourceServerAdapter.h"
#include "KoResourceItemView.h"
#include "KoResourceItemDelegate.h"
#include "KoResourceModel.h"
#include <resources/KoResource.h>
#include "KoResourceTaggingManager.h"
#include "KoTagFilterWidget.h"
#include "KoTagChooserWidget.h"
#include "KoResourceItemChooserSync.h"
class Q_DECL_HIDDEN KoResourceItemChooser::Private
{
public:
Private()
: model(0)
, view(0)
, buttonGroup(0)
, viewModeButton(0)
, usePreview(false)
, previewScroller(0)
, previewLabel(0)
, splitter(0)
, tiledPreview(false)
, grayscalePreview(false)
, synced(false)
, updatesBlocked(false)
, savedResourceWhileReset(0)
{}
KoResourceModel *model;
KoResourceTaggingManager *tagManager;
KoResourceItemView *view;
QButtonGroup *buttonGroup;
QToolButton *viewModeButton;
bool usePreview;
QScrollArea *previewScroller;
QLabel *previewLabel;
QSplitter *splitter;
QGridLayout *buttonLayout;
bool tiledPreview;
bool grayscalePreview;
bool synced;
bool updatesBlocked;
KoResource *savedResourceWhileReset;
QList<QAbstractButton*> customButtons;
};
KoResourceItemChooser::KoResourceItemChooser(QSharedPointer<KoAbstractResourceServerAdapter> resourceAdapter, QWidget *parent, bool usePreview)
: QWidget(parent)
, d(new Private())
{
Q_ASSERT(resourceAdapter);
d->splitter = new QSplitter(this);
d->model = new KoResourceModel(resourceAdapter, this);
connect(d->model, SIGNAL(beforeResourcesLayoutReset(KoResource *)), SLOT(slotBeforeResourcesLayoutReset(KoResource *)));
connect(d->model, SIGNAL(afterResourcesLayoutReset()), SLOT(slotAfterResourcesLayoutReset()));
d->view = new KoResourceItemView(this);
+ d->view->setObjectName("ResourceItemview");
d->view->setModel(d->model);
d->view->setItemDelegate(new KoResourceItemDelegate(this));
d->view->setSelectionMode(QAbstractItemView::SingleSelection);
d->view->viewport()->installEventFilter(this);
connect(d->view, SIGNAL(currentResourceChanged(QModelIndex)), this, SLOT(activated(QModelIndex)));
connect(d->view, SIGNAL(currentResourceClicked(QModelIndex)), this, SLOT(clicked(QModelIndex)));
connect(d->view, SIGNAL(contextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint)));
connect(d->view, SIGNAL(sigSizeChanged()), this, SLOT(updateView()));
d->splitter->addWidget(d->view);
d->splitter->setStretchFactor(0, 2);
d->usePreview = usePreview;
if (d->usePreview) {
d->previewScroller = new QScrollArea(this);
d->previewScroller->setWidgetResizable(true);
d->previewScroller->setBackgroundRole(QPalette::Dark);
d->previewScroller->setVisible(true);
d->previewScroller->setAlignment(Qt::AlignCenter);
d->previewLabel = new QLabel(this);
d->previewScroller->setWidget(d->previewLabel);
d->splitter->addWidget(d->previewScroller);
if (d->splitter->count() == 2) {
d->splitter->setSizes(QList<int>() << 280 << 160);
}
}
d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
connect(d->splitter, SIGNAL(splitterMoved(int, int)), SIGNAL(splitterMoved()));
d->buttonGroup = new QButtonGroup(this);
d->buttonGroup->setExclusive(false);
QGridLayout *layout = new QGridLayout(this);
d->buttonLayout = new QGridLayout();
QPushButton *button = new QPushButton(this);
button->setIcon(koIcon("document-open"));
button->setToolTip(i18nc("@info:tooltip", "Import resource"));
button->setEnabled(true);
d->buttonGroup->addButton(button, Button_Import);
d->buttonLayout->addWidget(button, 0, 0);
button = new QPushButton(this);
button->setIcon(koIcon("trash-empty"));
button->setToolTip(i18nc("@info:tooltip", "Delete resource"));
button->setEnabled(false);
d->buttonGroup->addButton(button, Button_Remove);
d->buttonLayout->addWidget(button, 0, 1);
connect(d->buttonGroup, SIGNAL(buttonClicked(int)), this, SLOT(slotButtonClicked(int)));
d->buttonLayout->setColumnStretch(0, 1);
d->buttonLayout->setColumnStretch(1, 1);
d->buttonLayout->setColumnStretch(2, 2);
d->buttonLayout->setSpacing(0);
d->buttonLayout->setMargin(0);
d->viewModeButton = new QToolButton(this);
d->viewModeButton->setIcon(koIcon("view-choose"));
d->viewModeButton->setPopupMode(QToolButton::InstantPopup);
d->viewModeButton->setVisible(false);
d->tagManager = new KoResourceTaggingManager(d->model, this);
connect(d->tagManager, SIGNAL(updateView()), this, SLOT(updateView()));
layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0);
layout->addWidget(d->viewModeButton, 0, 1);
layout->addWidget(d->splitter, 1, 0, 1, 2);
layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 2);
layout->addLayout(d->buttonLayout, 3, 0, 1, 2);
layout->setMargin(0);
layout->setSpacing(0);
updateButtonState();
showTaggingBar(false);
activated(d->model->index(0, 0));
}
KoResourceItemChooser::~KoResourceItemChooser()
{
disconnect();
delete d;
}
void KoResourceItemChooser::slotButtonClicked(int button)
{
if (button == Button_Import) {
QString extensions = d->model->extensions();
QStringList mimeTypes;
Q_FOREACH(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->model->importResourceFile(filename);
} else if (button == Button_Remove) {
QModelIndex index = d->view->currentIndex();
int row = index.row();
int column = index.column();
if (index.isValid()) {
KoResource *resource = resourceFromModelIndex(index);
if (resource) {
d->model->removeResource(resource);
}
}
if (column == 0) {
int rowMin = --row;
row = qBound(0, rowMin, row);
}
int columnMin = --column;
column = qBound(0, columnMin, column);
setCurrentItem(row, column);
activated(d->model->index(row, column));
}
updateButtonState();
}
void KoResourceItemChooser::showButtons(bool show)
{
foreach (QAbstractButton * button, d->buttonGroup->buttons()) {
show ? button->show() : button->hide();
}
Q_FOREACH (QAbstractButton *button, d->customButtons) {
show ? button->show() : button->hide();
}
}
void KoResourceItemChooser::addCustomButton(QAbstractButton *button, int cell)
{
d->buttonLayout->addWidget(button, 0, cell);
d->buttonLayout->setColumnStretch(2, 1);
d->buttonLayout->setColumnStretch(3, 1);
}
void KoResourceItemChooser::showTaggingBar(bool show)
{
d->tagManager->showTaggingBar(show);
}
void KoResourceItemChooser::setRowCount(int rowCount)
{
int resourceCount = d->model->resourcesCount();
d->model->setColumnCount(static_cast<qreal>(resourceCount) / rowCount);
//Force an update to get the right row height (in theory)
QRect geometry = d->view->geometry();
d->view->setViewMode(KoResourceItemView::FIXED_ROWS);
d->view->setGeometry(geometry.adjusted(0, 0, 0, 1));
d->view->setGeometry(geometry);
}
void KoResourceItemChooser::setColumnCount(int columnCount)
{
d->model->setColumnCount(columnCount);
}
void KoResourceItemChooser::setRowHeight(int rowHeight)
{
d->view->verticalHeader()->setDefaultSectionSize(rowHeight);
}
void KoResourceItemChooser::setColumnWidth(int columnWidth)
{
d->view->horizontalHeader()->setDefaultSectionSize(columnWidth);
}
void KoResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate)
{
d->view->setItemDelegate(delegate);
}
KoResource *KoResourceItemChooser::currentResource() const
{
QModelIndex index = d->view->currentIndex();
if (index.isValid()) {
return resourceFromModelIndex(index);
}
return 0;
}
void KoResourceItemChooser::setCurrentResource(KoResource *resource)
{
// don't update if the change came from the same chooser
if (d->updatesBlocked) {
return;
}
QModelIndex index = d->model->indexFromResource(resource);
d->view->setCurrentIndex(index);
updatePreview(index.isValid() ? resource : 0);
}
void KoResourceItemChooser::slotBeforeResourcesLayoutReset(KoResource *activateAfterReset)
{
d->savedResourceWhileReset = activateAfterReset ? activateAfterReset : currentResource();
}
void KoResourceItemChooser::slotAfterResourcesLayoutReset()
{
if (d->savedResourceWhileReset) {
this->blockSignals(true);
setCurrentResource(d->savedResourceWhileReset);
this->blockSignals(false);
}
}
void KoResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation)
{
d->splitter->setOrientation(orientation);
}
void KoResourceItemChooser::setPreviewTiled(bool tiled)
{
d->tiledPreview = tiled;
}
void KoResourceItemChooser::setGrayscalePreview(bool grayscale)
{
d->grayscalePreview = grayscale;
}
void KoResourceItemChooser::setCurrentItem(int row, int column)
{
QModelIndex index = d->model->index(row, column);
if (!index.isValid())
return;
d->view->setCurrentIndex(index);
if (index.isValid()) {
updatePreview(resourceFromModelIndex(index));
}
}
void KoResourceItemChooser::setProxyModel(QAbstractProxyModel *proxyModel)
{
proxyModel->setSourceModel(d->model);
d->view->setModel(proxyModel);
}
void KoResourceItemChooser::activated(const QModelIndex &/*index*/)
{
KoResource *resource = currentResource();
if (resource) {
d->updatesBlocked = true;
emit resourceSelected(resource);
d->updatesBlocked = false;
updatePreview(resource);
updateButtonState();
}
}
void KoResourceItemChooser::clicked(const QModelIndex &index)
{
Q_UNUSED(index);
KoResource *resource = currentResource();
if (resource) {
emit resourceClicked(resource);
}
}
void KoResourceItemChooser::updateButtonState()
{
QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove);
if (! removeButton)
return;
KoResource *resource = currentResource();
if (resource) {
removeButton->setEnabled(!resource->permanent());
return;
}
removeButton->setEnabled(false);
}
void KoResourceItemChooser::updatePreview(KoResource *resource)
{
if (!d->usePreview) return;
if (!resource) {
d->previewLabel->setPixmap(QPixmap());
return;
}
QImage image = resource->image();
if (image.format() != QImage::Format_RGB32 ||
image.format() != QImage::Format_ARGB32 ||
image.format() != QImage::Format_ARGB32_Premultiplied) {
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
if (d->tiledPreview) {
int width = d->previewScroller->width() * 4;
int height = d->previewScroller->height() * 4;
QImage img(width, height, image.format());
QPainter gc(&img);
gc.fillRect(img.rect(), Qt::white);
gc.setPen(Qt::NoPen);
gc.setBrush(QBrush(image));
gc.drawRect(img.rect());
image = img;
}
// Only convert to grayscale if it is rgb. Otherwise, it's gray already.
if (d->grayscalePreview && !image.isGrayscale()) {
QRgb *pixel = reinterpret_cast<QRgb *>(image.bits());
for (int row = 0; row < image.height(); ++row) {
for (int col = 0; col < image.width(); ++col) {
const QRgb currentPixel = pixel[row * image.width() + col];
const int red = qRed(currentPixel);
const int green = qGreen(currentPixel);
const int blue = qBlue(currentPixel);
const int grayValue = (red * 11 + green * 16 + blue * 5) / 32;
pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue);
}
}
}
d->previewLabel->setPixmap(QPixmap::fromImage(image));
}
KoResource *KoResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
const QAbstractProxyModel *proxyModel = dynamic_cast<const QAbstractProxyModel *>(index.model());
if (proxyModel) {
//Get original model index, because proxy models destroy the internalPointer
QModelIndex originalIndex = proxyModel->mapToSource(index);
return static_cast<KoResource *>(originalIndex.internalPointer());
}
return static_cast<KoResource *>(index.internalPointer());
}
QSize KoResourceItemChooser::viewSize() const
{
return d->view->size();
}
KoResourceItemView *KoResourceItemChooser::itemView() const
{
return d->view;
}
void KoResourceItemChooser::contextMenuRequested(const QPoint &pos)
{
d->tagManager->contextMenuRequested(currentResource(), pos);
}
void KoResourceItemChooser::setViewModeButtonVisible(bool visible)
{
d->viewModeButton->setVisible(visible);
}
QToolButton *KoResourceItemChooser::viewModeButton() const
{
return d->viewModeButton;
}
void KoResourceItemChooser::setSynced(bool sync)
{
if (d->synced == sync)
return;
d->synced = sync;
KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
if (sync) {
connect(chooserSync, SIGNAL(baseLenghtChanged(int)), SLOT(baseLengthChanged(int)));
baseLengthChanged(chooserSync->baseLength());
} else {
chooserSync->disconnect(this);
}
}
void KoResourceItemChooser::baseLengthChanged(int length)
{
if (d->synced) {
int resourceCount = d->model->resourcesCount();
int width = d->view->width();
int maxColums = width / length;
int cols = width / (2 * length) + 1;
while (cols <= maxColums) {
int size = width / cols;
int rows = ceil(resourceCount / (double)cols);
if (rows * size < (d->view->height() - 5)) {
break;
}
cols++;
}
setColumnCount(cols);
}
d->view->updateView();
}
bool KoResourceItemChooser::eventFilter(QObject *object, QEvent *event)
{
if (d->synced && event->type() == QEvent::Wheel) {
KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
QWheelEvent *qwheel = static_cast<QWheelEvent *>(event);
if (qwheel->modifiers() & Qt::ControlModifier) {
int degrees = qwheel->delta() / 8;
int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10;
chooserSync->setBaseLength(newBaseLength);
return true;
}
}
return QObject::eventFilter(object, event);
}
void KoResourceItemChooser::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
updateView();
}
void KoResourceItemChooser::showEvent(QShowEvent *event)
{
QWidget::showEvent(event);
updateView();
}
void KoResourceItemChooser::updateView()
{
if (d->synced) {
KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
baseLengthChanged(chooserSync->baseLength());
}
}
diff --git a/libs/widgets/KoResourceItemChooser.h b/libs/widgets/KoResourceItemChooser.h
index 943a220ba3..c9f3151edd 100644
--- a/libs/widgets/KoResourceItemChooser.h
+++ b/libs/widgets/KoResourceItemChooser.h
@@ -1,153 +1,153 @@
/* This file is part of the KDE project
Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
Copyright (c) 2007 Jan Hambrecht <jaham@gmx.net>
Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
Copyright (c) 2010 Boudewijn Rempt <boud@valdyas.org>
Copyright (C) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
Copyright (c) 2013 Sascha Suelzer <s.suelzer@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KO_RESOURCE_ITEM_CHOOSER
#define KO_RESOURCE_ITEM_CHOOSER
#include <QWidget>
#include "kritawidgets_export.h"
class QModelIndex;
class QAbstractProxyModel;
class QAbstractItemDelegate;
class QAbstractButton;
class QToolButton;
class KoAbstractResourceServerAdapter;
class KoResourceItemView;
class KoResource;
/**
* A widget that contains a KoResourceChooser as well
* as an import/export button
*/
class KRITAWIDGETS_EXPORT KoResourceItemChooser : public QWidget
{
Q_OBJECT
public:
enum Buttons { Button_Import, Button_Remove };
/// \p usePreview shows the aside preview with the resource's image
explicit KoResourceItemChooser(QSharedPointer<KoAbstractResourceServerAdapter> resourceAdapter, QWidget *parent = 0, bool usePreview = false);
~KoResourceItemChooser();
/// Sets number of columns in the view and causes the number of rows to be calculated accordingly
void setColumnCount(int columnCount);
/// Sets number of rows in the view and causes the number of columns to be calculated accordingly
void setRowCount(int rowCount);
/// Sets the height of the view rows
void setRowHeight(int rowHeight);
/// Sets the width of the view columns
void setColumnWidth(int columnWidth);
/// Sets a custom delegate for the view
void setItemDelegate(QAbstractItemDelegate *delegate);
/// Gets the currently selected resource
/// @returns the selected resource, 0 is no resource is selected
KoResource *currentResource() const;
/// Sets the item representing the resource as selected
void setCurrentResource(KoResource *resource);
/**
- * Sets the sected resource, does nothing if there is no valid item
+ * Sets the selected resource, does nothing if there is no valid item
* @param row row of the item
* @param column column of the item
*/
void setCurrentItem(int row, int column);
void showButtons(bool show);
void addCustomButton(QAbstractButton *button, int cell);
/// determines whether the preview right or below the splitter
void setPreviewOrientation(Qt::Orientation orientation);
/// determines whether the preview should tile the resource's image or not
void setPreviewTiled(bool tiled);
/// shows the preview converted to grayscale
void setGrayscalePreview(bool grayscale);
/// sets the visibilty of tagging KlineEdits.
void showTaggingBar(bool show);
///Set a proxy model with will be used to filter the resources
void setProxyModel(QAbstractProxyModel *proxyModel);
QSize viewSize() const;
KoResourceItemView *itemView() const;
void setViewModeButtonVisible(bool visible);
QToolButton *viewModeButton() const;
void setSynced(bool sync);
virtual bool eventFilter(QObject *object, QEvent *event);
Q_SIGNALS:
/// Emitted when a resource was selected
void resourceSelected(KoResource *resource);
/// Emitted when an *already selected* resource is clicked
/// again
void resourceClicked(KoResource *resource);
void splitterMoved();
public Q_SLOTS:
void slotButtonClicked(int button);
private Q_SLOTS:
void activated(const QModelIndex &index);
void clicked(const QModelIndex &index);
void contextMenuRequested(const QPoint &pos);
void baseLengthChanged(int length);
void slotBeforeResourcesLayoutReset(KoResource *activateAfterReset);
void slotAfterResourcesLayoutReset();
void updateView();
protected:
virtual void showEvent(QShowEvent *event);
private:
void updateButtonState();
void updatePreview(KoResource *resource);
virtual void resizeEvent(QResizeEvent *event);
/// Resource for a given model index
/// @returns the resource pointer, 0 is index not valid
KoResource *resourceFromModelIndex(const QModelIndex &index) const;
class Private;
Private *const d;
};
#endif // KO_RESOURCE_ITEM_CHOOSER
diff --git a/libs/widgets/KoResourcePopupAction.cpp b/libs/widgets/KoResourcePopupAction.cpp
index 2d39c7639a..182eb7739b 100644
--- a/libs/widgets/KoResourcePopupAction.cpp
+++ b/libs/widgets/KoResourcePopupAction.cpp
@@ -1,201 +1,210 @@
/* 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 "KoResourcePopupAction.h"
#include "KoResourceServerAdapter.h"
#include "KoResourceItemView.h"
#include "KoResourceModel.h"
#include "KoResourceItemDelegate.h"
#include <resources/KoResource.h>
#include "KoCheckerBoardPainter.h"
#include "KoShapeBackground.h"
#include <resources/KoAbstractGradient.h>
#include <resources/KoPattern.h>
#include <KoGradientBackground.h>
#include <KoPatternBackground.h>
#include <KoImageCollection.h>
#include <QMenu>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QPainter>
#include <QGradient>
#include <QToolButton>
#include <QRect>
#include <QWidgetAction>
class KoResourcePopupAction::Private
{
public:
- QMenu *menu {0};
- KoResourceItemView *resourceList {0};
+ QMenu *menu = 0;
+ KoResourceModel *model = 0;
+ KoResourceItemView *resourceList = 0;
QSharedPointer<KoShapeBackground> background;
- KoImageCollection *imageCollection {0};
+ KoImageCollection *imageCollection = 0;
KoCheckerBoardPainter checkerPainter {4};
};
KoResourcePopupAction::KoResourcePopupAction(QSharedPointer<KoAbstractResourceServerAdapter>resourceAdapter, QObject *parent)
: QAction(parent)
, d(new Private())
{
Q_ASSERT(resourceAdapter);
d->menu = new QMenu();
QWidget *widget = new QWidget();
QWidgetAction *wdgAction = new QWidgetAction(this);
d->resourceList = new KoResourceItemView(widget);
- d->resourceList->setModel(new KoResourceModel(resourceAdapter, widget));
+
+ d->model = new KoResourceModel(resourceAdapter, widget);
+ d->resourceList->setModel(d->model);
d->resourceList->setItemDelegate(new KoResourceItemDelegate(widget));
KoResourceModel * resourceModel = qobject_cast<KoResourceModel*>(d->resourceList->model());
if (resourceModel) {
resourceModel->setColumnCount(1);
}
KoResource *resource = 0;
- if (resourceAdapter->resources().count() > 0) {
- resource = resourceAdapter->resources().at(0);
- }
-
- KoAbstractGradient *gradient = dynamic_cast<KoAbstractGradient*>(resource);
- KoPattern *pattern = dynamic_cast<KoPattern*>(resource);
- if (gradient) {
- QGradient *qg = gradient->toQGradient();
- qg->setCoordinateMode(QGradient::ObjectBoundingMode);
- d->background = QSharedPointer<KoShapeBackground>(new KoGradientBackground(qg));
- }
- else if (pattern) {
- d->imageCollection = new KoImageCollection();
- d->background = QSharedPointer<KoShapeBackground>(new KoPatternBackground(d->imageCollection));
- static_cast<KoPatternBackground*>(d->background.data())->setPattern(pattern->pattern());
+ QList<KoResource*> resources = resourceAdapter->resources();
+ if (resources.count() > 0) {
+ resource = resources.at(0);
+ d->resourceList->setCurrentIndex(d->model->indexFromResource(resource));
+ indexChanged(d->resourceList->currentIndex());
}
QHBoxLayout *layout = new QHBoxLayout(widget);
layout->addWidget(d->resourceList);
widget->setLayout(layout);
wdgAction->setDefaultWidget(widget);
d->menu->addAction(wdgAction);
setMenu(d->menu);
new QHBoxLayout(d->menu);
d->menu->layout()->addWidget(widget);
d->menu->layout()->setMargin(0);
connect(d->resourceList, SIGNAL(clicked(QModelIndex)), this, SLOT(indexChanged(QModelIndex)));
updateIcon();
}
KoResourcePopupAction::~KoResourcePopupAction()
{
/* Removing the actions here make them be deleted together with their default widget.
* This happens only if the actions are QWidgetAction, and we know they are since
* the only ones added are in KoResourcePopupAction constructor. */
int i = 0;
while(d->menu->actions().size() > 0) {
d->menu->removeAction(d->menu->actions()[i]);
++i;
}
delete d->menu;
delete d->imageCollection;
delete d;
}
QSharedPointer<KoShapeBackground> KoResourcePopupAction::currentBackground() const
{
return d->background;
}
void KoResourcePopupAction::setCurrentBackground(QSharedPointer<KoShapeBackground> background)
{
d->background = background;
updateIcon();
}
+void KoResourcePopupAction::setCurrentResource(KoResource *resource)
+{
+ QModelIndex index = d->model->indexFromResource(resource);
+ if (index.isValid()) {
+ d->resourceList->setCurrentIndex(index);
+ indexChanged(index);
+ }
+}
+
+KoResource* KoResourcePopupAction::currentResource() const
+{
+ QModelIndex index = d->resourceList->currentIndex();
+ if (!index.isValid()) return 0;
+
+ return static_cast<KoResource*>(index.internalPointer());
+}
void KoResourcePopupAction::indexChanged(const QModelIndex &modelIndex)
{
if (! modelIndex.isValid()) {
return;
}
d->menu->hide();
KoResource *resource = static_cast<KoResource*>(modelIndex.internalPointer());
if(resource) {
KoAbstractGradient *gradient = dynamic_cast<KoAbstractGradient*>(resource);
KoPattern *pattern = dynamic_cast<KoPattern*>(resource);
if (gradient) {
QGradient *qg = gradient->toQGradient();
qg->setCoordinateMode(QGradient::ObjectBoundingMode);
d->background = QSharedPointer<KoShapeBackground>(new KoGradientBackground(qg));
} else if (pattern) {
KoImageCollection *collection = new KoImageCollection();
d->background = QSharedPointer<KoShapeBackground>(new KoPatternBackground(collection));
qSharedPointerDynamicCast<KoPatternBackground>(d->background)->setPattern(pattern->pattern());
}
emit resourceSelected(d->background);
updateIcon();
}
}
void KoResourcePopupAction::updateIcon()
{
QSize iconSize;
QToolButton *toolButton = dynamic_cast<QToolButton*>(parentWidget());
if (toolButton) {
iconSize = QSize(toolButton->iconSize());
} else {
iconSize = QSize(16, 16);
}
// This must be a QImage, as drawing to a QPixmap outside the
// UI thread will cause sporadic crashes.
QImage pm = QImage(iconSize, QImage::Format_ARGB32_Premultiplied);
pm.fill(Qt::transparent);
QPainter p(&pm);
QSharedPointer<KoGradientBackground> gradientBackground = qSharedPointerDynamicCast<KoGradientBackground>(d->background);
QSharedPointer<KoPatternBackground> patternBackground = qSharedPointerDynamicCast<KoPatternBackground>(d->background);
if (gradientBackground) {
QRect innerRect(0, 0, iconSize.width(), iconSize.height());
QLinearGradient paintGradient;
paintGradient.setStops(gradientBackground->gradient()->stops());
paintGradient.setStart(innerRect.topLeft());
paintGradient.setFinalStop(innerRect.topRight());
d->checkerPainter.paint(p, innerRect);
p.fillRect(innerRect, QBrush(paintGradient));
} else if (patternBackground) {
d->checkerPainter.paint(p, QRect(QPoint(),iconSize));
p.fillRect(0, 0, iconSize.width(), iconSize.height(), patternBackground->pattern());
}
p.end();
setIcon(QIcon(QPixmap::fromImage(pm)));
}
diff --git a/libs/widgets/KoResourcePopupAction.h b/libs/widgets/KoResourcePopupAction.h
index b69a232041..271f917b97 100644
--- a/libs/widgets/KoResourcePopupAction.h
+++ b/libs/widgets/KoResourcePopupAction.h
@@ -1,67 +1,73 @@
/* 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.
*/
#ifndef KORESOURCEPOPUPACTION_H
#define KORESOURCEPOPUPACTION_H
#include <QAction>
#include <QSharedPointer>
+#include "kritawidgets_export.h"
+
class KoShapeBackground;
class KoAbstractResourceServerAdapter;
class QModelIndex;
+class KoResource;
-class KoResourcePopupAction : public QAction
+class KRITAWIDGETS_EXPORT KoResourcePopupAction : public QAction
{
Q_OBJECT
public:
/**
* Constructs a KoResourcePopupAction (gradient or pattern) with the specified parent.
*
* @param parent The parent for this action.
*/
explicit KoResourcePopupAction(QSharedPointer<KoAbstractResourceServerAdapter>gradientResourceAdapter, QObject *parent = 0);
/**
* Destructor
*/
virtual ~KoResourcePopupAction();
QSharedPointer<KoShapeBackground> currentBackground() const;
void setCurrentBackground(QSharedPointer<KoShapeBackground> background);
+ void setCurrentResource(KoResource *resource);
+ KoResource *currentResource() const;
+
Q_SIGNALS:
/// Emitted when a resource was selected
void resourceSelected(QSharedPointer<KoShapeBackground> background);
public Q_SLOTS:
void updateIcon();
private Q_SLOTS:
void indexChanged(const QModelIndex &modelIndex);
private:
class Private;
Private * const d;
};
#endif /* KORESOURCEPOPUPACTION_H */
diff --git a/libs/widgets/KoShadowConfigWidget.cpp b/libs/widgets/KoShadowConfigWidget.cpp
index 4807d08105..6e4a44d163 100644
--- a/libs/widgets/KoShadowConfigWidget.cpp
+++ b/libs/widgets/KoShadowConfigWidget.cpp
@@ -1,231 +1,241 @@
/* This file is part of the KDE project
* Copyright (C) 2012 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 "KoShadowConfigWidget.h"
#include "ui_KoShadowConfigWidget.h"
#include <KoIcon.h>
#include <KoUnit.h>
#include <KoColorPopupAction.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceManager.h>
#include <KoSelection.h>
#include <KoShapeShadow.h>
#include <KoShapeShadowCommand.h>
-#include <KoShapeManager.h>
+#include <KoSelectedShapesProxy.h>
#include <klocalizedstring.h>
#include <QCheckBox>
#include <math.h>
class Q_DECL_HIDDEN KoShadowConfigWidget::Private
{
public:
Private()
{
}
Ui_KoShadowConfigWidget widget;
KoColorPopupAction *actionShadowColor;
KoCanvasBase *canvas;
};
KoShadowConfigWidget::KoShadowConfigWidget(QWidget *parent)
: QWidget(parent)
, d(new Private())
{
d->widget.setupUi(this);
d->widget.shadowOffset->setValue(8.0);
d->widget.shadowBlur->setValue(8.0);
d->widget.shadowBlur->setMinimum(0.0);
d->widget.shadowAngle->setValue(315.0);
d->widget.shadowAngle->setMinimum(0.0);
d->widget.shadowAngle->setMaximum(360.0);
d->widget.shadowVisible->setChecked(false);
visibilityChanged();
d->actionShadowColor = new KoColorPopupAction(this);
d->actionShadowColor->setCurrentColor(QColor(0, 0, 0, 192)); // some reasonable default for shadow
d->actionShadowColor->setIcon(koIcon("format-stroke-color"));
d->actionShadowColor->setToolTip(i18n("Change the color of the shadow"));
d->widget.shadowColor->setDefaultAction(d->actionShadowColor);
connect(d->widget.shadowVisible, SIGNAL(toggled(bool)), this, SLOT(applyChanges()));
connect(d->widget.shadowVisible, SIGNAL(toggled(bool)), this, SLOT(visibilityChanged()));
connect(d->actionShadowColor, SIGNAL(colorChanged(const KoColor&)), this, SLOT(applyChanges()));
connect(d->widget.shadowAngle, SIGNAL(valueChanged(int)), this, SLOT(applyChanges()));
connect(d->widget.shadowOffset, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges()));
connect(d->widget.shadowBlur, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyChanges()));
}
KoShadowConfigWidget::~KoShadowConfigWidget()
{
delete d;
}
void KoShadowConfigWidget::setShadowColor(const QColor &color)
{
d->widget.shadowColor->blockSignals(true);
d->actionShadowColor->blockSignals(true);
d->actionShadowColor->setCurrentColor( color );
d->actionShadowColor->blockSignals(false);
d->widget.shadowColor->blockSignals(false);
}
QColor KoShadowConfigWidget::shadowColor() const
{
return d->actionShadowColor->currentColor();
}
void KoShadowConfigWidget::setShadowOffset(const QPointF &offset)
{
qreal length = sqrt(offset.x()*offset.x() + offset.y()*offset.y());
qreal angle = atan2(-offset.y(), offset.x());
if (angle < 0.0) {
angle += 2*M_PI;
}
d->widget.shadowAngle->blockSignals(true);
d->widget.shadowAngle->setValue(-90 - angle * 180.0 / M_PI);
d->widget.shadowAngle->blockSignals(false);
d->widget.shadowOffset->blockSignals(true);
d->widget.shadowOffset->changeValue(length);
d->widget.shadowOffset->blockSignals(false);
}
QPointF KoShadowConfigWidget::shadowOffset() const
{
QPointF offset(d->widget.shadowOffset->value(), 0);
QTransform m;
m.rotate(d->widget.shadowAngle->value() + 90);
return m.map(offset);
}
void KoShadowConfigWidget::setShadowBlur(const qreal &blur)
{
d->widget.shadowBlur->blockSignals(true);
d->widget.shadowBlur->changeValue(blur);
d->widget.shadowBlur->blockSignals(false);
}
qreal KoShadowConfigWidget::shadowBlur() const
{
return d->widget.shadowBlur->value();
}
void KoShadowConfigWidget::setShadowVisible(bool visible)
{
d->widget.shadowVisible->blockSignals(true);
d->widget.shadowVisible->setChecked(visible);
d->widget.shadowVisible->blockSignals(false);
visibilityChanged();
}
bool KoShadowConfigWidget::shadowVisible() const
{
return d->widget.shadowVisible->isChecked();
}
void KoShadowConfigWidget::visibilityChanged()
{
d->widget.shadowAngle->setEnabled( d->widget.shadowVisible->isChecked() );
d->widget.shadowBlur->setEnabled( d->widget.shadowVisible->isChecked() );
d->widget.shadowColor->setEnabled( d->widget.shadowVisible->isChecked() );
d->widget.shadowOffset->setEnabled( d->widget.shadowVisible->isChecked() );
}
void KoShadowConfigWidget::applyChanges()
{
if (d->canvas) {
- KoSelection *selection = d->canvas->shapeManager()->selection();
- KoShape * shape = selection->firstSelectedShape(KoFlake::TopLevelSelection);
+ KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
+ KoShape * shape = selection->firstSelectedShape();
if (! shape) {
return;
}
KoShapeShadow *newShadow = new KoShapeShadow();
newShadow->setVisible(shadowVisible());
newShadow->setColor(shadowColor());
newShadow->setOffset(shadowOffset());
newShadow->setBlur(shadowBlur());
- d->canvas->addCommand(new KoShapeShadowCommand(selection->selectedShapes(KoFlake::TopLevelSelection), newShadow));
+ d->canvas->addCommand(new KoShapeShadowCommand(selection->selectedShapes(), newShadow));
}
}
void KoShadowConfigWidget::selectionChanged()
{
if (! d->canvas) {
return;
}
- KoSelection *selection = d->canvas->shapeManager()->selection();
- KoShape * shape = selection->firstSelectedShape(KoFlake::TopLevelSelection);
+ KoSelection *selection = d->canvas->selectedShapesProxy()->selection();
+ KoShape * shape = selection->firstSelectedShape();
setEnabled(shape != 0);
if (! shape) {
setShadowVisible(false);
return;
}
KoShapeShadow * shadow = shape->shadow();
if (! shadow) {
setShadowVisible(false);
return;
}
setShadowVisible(shadow->isVisible());
setShadowOffset(shadow->offset());
setShadowColor(shadow->color());
setShadowBlur(shadow->blur());
}
void KoShadowConfigWidget::setCanvas(KoCanvasBase *canvas)
{
d->canvas = canvas;
- connect(canvas->shapeManager(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
- connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged()));
+ connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
+ connect(canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), this, SLOT(selectionChanged()));
setUnit(canvas->unit());
connect( d->canvas->resourceManager(), SIGNAL( canvasResourceChanged( int, const QVariant& ) ),
this, SLOT( resourceChanged( int, const QVariant& ) ) );
}
+void KoShadowConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerBlur, KisSpinBoxUnitManager *managerOffset)
+{
+ d->widget.shadowOffset->blockSignals(true);
+ d->widget.shadowBlur->blockSignals(true);
+ d->widget.shadowOffset->setUnitManager(managerOffset);
+ d->widget.shadowBlur->setUnitManager(managerBlur);
+ d->widget.shadowOffset->blockSignals(false);
+ d->widget.shadowBlur->blockSignals(false);
+}
+
void KoShadowConfigWidget::setUnit(const KoUnit &unit)
{
d->widget.shadowOffset->blockSignals(true);
d->widget.shadowBlur->blockSignals(true);
d->widget.shadowOffset->setUnit(unit);
d->widget.shadowBlur->setUnit(unit);
d->widget.shadowOffset->blockSignals(false);
d->widget.shadowBlur->blockSignals(false);
}
void KoShadowConfigWidget::resourceChanged( int key, const QVariant & res )
{
if( key == KoCanvasResourceManager::Unit ) {
setUnit(res.value<KoUnit>());
}
}
diff --git a/libs/widgets/KoShadowConfigWidget.h b/libs/widgets/KoShadowConfigWidget.h
index ba6e7a5aa5..3786268ecf 100644
--- a/libs/widgets/KoShadowConfigWidget.h
+++ b/libs/widgets/KoShadowConfigWidget.h
@@ -1,77 +1,80 @@
/* This file is part of the KDE project
* Copyright (C) 2012 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.
*/
#ifndef KOSHADOWCONFIGWIDGET_H
#define KOSHADOWCONFIGWIDGET_H
#include "kritawidgets_export.h"
#include <QWidget>
class KoUnit;
class KoCanvasBase;
+class KisSpinBoxUnitManager;
/// A widget for configuring the shadow of a shape
class KRITAWIDGETS_EXPORT KoShadowConfigWidget : public QWidget
{
Q_OBJECT
public:
explicit KoShadowConfigWidget(QWidget *parent);
~KoShadowConfigWidget();
/// Sets the shadow color
void setShadowColor(const QColor &color);
/// Returns the shadow color
QColor shadowColor() const;
/// Sets the shadow offset
void setShadowOffset(const QPointF &offset);
/// Returns the shadow offset
QPointF shadowOffset() const;
/// Sets the shadow blur radius
void setShadowBlur(const qreal &blur);
/// Returns the shadow blur radius
qreal shadowBlur() const;
/// Sets if the shadow is visible
void setShadowVisible(bool visible);
/// Returns if shadow is visible
bool shadowVisible() const;
public Q_SLOTS:
+
+ void setUnitManagers(KisSpinBoxUnitManager* managerBlur, KisSpinBoxUnitManager* managerOffset);
void setUnit( const KoUnit &unit );
void setCanvas(KoCanvasBase *canvas);
private Q_SLOTS:
void visibilityChanged();
void applyChanges();
void selectionChanged();
void resourceChanged( int key, const QVariant & res );
private:
class Private;
Private *const d;
};
#endif // KOSHADOWCONFIGWIDGET_H
diff --git a/libs/widgets/KoStrokeConfigWidget.cpp b/libs/widgets/KoStrokeConfigWidget.cpp
index 8f9088fc54..22c3135849 100644
--- a/libs/widgets/KoStrokeConfigWidget.cpp
+++ b/libs/widgets/KoStrokeConfigWidget.cpp
@@ -1,552 +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 <KoCanvasResourceManager.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
+ // 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"));
+ 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)
+ 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->setUnit(KoUnit(KoUnit::Point));
+ 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(const 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 completly transfered 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));
}
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));
+ 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, const QVariant&)),
this, SLOT(canvasResourceChanged(int, const 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 KoCanvasResourceManager::Unit:
setUnit(value.value<KoUnit>());
break;
}
}
diff --git a/libs/widgets/KoStrokeConfigWidget.h b/libs/widgets/KoStrokeConfigWidget.h
deleted file mode 100644
index 129a074c19..0000000000
--- a/libs/widgets/KoStrokeConfigWidget.h
+++ /dev/null
@@ -1,104 +0,0 @@
-/* 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 Rob Buis <buis@kde.org>
- * Copyright (C) 2004 Laurent Montel <montel@kde.org>
- * Copyright (C) 2005-2006 Tim Beaulen <tbscope@gmail.com>
- * Copyright (C) 2005 Inge Wallin <inge@lysator.liu.se>
- * Copyright (C) 2005, 2011 Thomas Zander <zander@kde.org>
- * Copyright (C) 2005-2008 Jan Hambrecht <jaham@gmx.net>
- * Copyright (C) 2006 C. Boemann <cbo@boemann.dk>
- * Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
- * Copyright (C) 2011 Thorsten Zachmann <zachmann@kde.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 STROKECONFIGWIDGET_H
-#define STROKECONFIGWIDGET_H
-
-#include "kritawidgets_export.h"
-
-#include <QWidget>
-#include <KoMarkerData.h>
-
-class KoUnit;
-class KoShapeStrokeModel;
-class KoMarker;
-class KoCanvasBase;
-class KoShapeStroke;
-
-/// A widget for configuring the stroke of a shape
-class KRITAWIDGETS_EXPORT KoStrokeConfigWidget : public QWidget
-{
- Q_OBJECT
-public:
- explicit KoStrokeConfigWidget(QWidget *parent);
- ~KoStrokeConfigWidget();
-
- // Getters
- Qt::PenStyle lineStyle() const;
- QVector<qreal> lineDashes() const;
- qreal lineWidth() const;
- QColor color() const;
- qreal miterLimit() const;
- KoMarker *startMarker() const;
- KoMarker *endMarker() const;
- Qt::PenCapStyle capStyle() const;
- Qt::PenJoinStyle joinStyle() const;
-
- /**
- * Creates KoShapeStroke object filled with the options
- * configured by the widget. The caller is in charge of
- * deletion of the returned object
- */
- KoShapeStroke* createShapeStroke() const;
-
- void setCanvas(KoCanvasBase *canvas);
- void setActive(bool active);
-
-private Q_SLOTS:
- void updateControls(KoShapeStrokeModel *stroke, KoMarker *startMarker, KoMarker *endMarker);
-
- void updateMarkers(const QList<KoMarker*> &markers);
-
- /// start marker has changed
- void startMarkerChanged();
- /// end marker has changed
- void endMarkerChanged();
-
- void canvasResourceChanged(int key, const QVariant &value);
-
- /// selection has changed
- void selectionChanged();
-
- /// apply line changes to the selected shape
- void applyChanges();
-
-private:
- void setUnit(const KoUnit &unit);
-
- /// apply marker changes to the selected shape
- void applyMarkerChanges(KoMarkerData::MarkerPosition position);
-
- void blockChildSignals(bool block);
-
-private:
- class Private;
- Private * const d;
-};
-
-#endif // SHADOWCONFIGWIDGET_H
diff --git a/libs/widgets/KoTitledTabWidget.cpp b/libs/widgets/KoTitledTabWidget.cpp
new file mode 100644
index 0000000000..e57d3903fa
--- /dev/null
+++ b/libs/widgets/KoTitledTabWidget.cpp
@@ -0,0 +1,43 @@
+/*
+ * 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 "KoTitledTabWidget.h"
+
+#include <QLabel>
+
+KoTitledTabWidget::KoTitledTabWidget(QWidget *parent)
+ : QTabWidget(parent)
+{
+ m_titleLabel = new QLabel(this);
+ setCornerWidget(m_titleLabel);
+
+ connect(this, SIGNAL(currentChanged(int)), SLOT(slotUpdateTitle()));
+ slotUpdateTitle();
+}
+
+void KoTitledTabWidget::slotUpdateTitle()
+{
+ QWidget *widget = this->widget(currentIndex());
+
+ if (widget) {
+ const QString title = widget->windowTitle();
+
+ m_titleLabel->setVisible(!title.isEmpty());
+ m_titleLabel->setText(title);
+ }
+}
diff --git a/libs/widgets/KoTitledTabWidget.h b/libs/widgets/KoTitledTabWidget.h
new file mode 100644
index 0000000000..e92b75f781
--- /dev/null
+++ b/libs/widgets/KoTitledTabWidget.h
@@ -0,0 +1,42 @@
+/*
+ * 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 KOTITLEDTABWIDGET_H
+#define KOTITLEDTABWIDGET_H
+
+#include <QTabWidget>
+
+#include "kritawidgets_export.h"
+
+class QLabel;
+
+
+class KRITAWIDGETS_EXPORT KoTitledTabWidget : public QTabWidget
+{
+ Q_OBJECT
+public:
+ KoTitledTabWidget(QWidget *parent);
+
+private Q_SLOTS:
+ void slotUpdateTitle();
+
+private:
+ QLabel *m_titleLabel;
+};
+
+#endif // KOTITLEDTABWIDGET_H
diff --git a/libs/widgets/KoToolDocker.cpp b/libs/widgets/KoToolDocker.cpp
index 5477c96568..a1ccb32903 100644
--- a/libs/widgets/KoToolDocker.cpp
+++ b/libs/widgets/KoToolDocker.cpp
@@ -1,281 +1,281 @@
/* This file is part of the KDE project
*
* Copyright (c) 2010-2011 C. Boemann <cbo@boemann.dk>
* Copyright (c) 2005-2006 Boudewijn Rempt <boud@valdyas.org>
* 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.
*/
#include "KoToolDocker.h"
#include <KoDockWidgetTitleBarButton.h>
#include <KoDockWidgetTitleBar.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <kconfiggroup.h>
#include <ksharedconfig.h>
#include <QIcon>
#include <QApplication>
#include <QPointer>
#include <QGridLayout>
#include <QScrollArea>
#include <QScrollBar>
#include <QLabel>
#include <QSet>
#include <QAction>
#include <QStyleOptionFrame>
#include <QToolButton>
#include <QTabWidget>
#include <WidgetsDebug.h>
+#include <kis_debug.h>
class Q_DECL_HIDDEN KoToolDocker::Private
{
public:
Private(KoToolDocker *dock)
: q(dock)
, tabbed(false)
, tabIcon(koIcon("tab-new"))
, unTabIcon(koIcon("tab-close"))
{
}
QList<QPointer<QWidget> > currentWidgetList;
QSet<QWidget *> currentAuxWidgets;
QScrollArea *scrollArea;
QWidget *hiderWidget; // non current widgets are hidden by being children of this
QWidget *housekeeperWidget;
QGridLayout *housekeeperLayout;
KoToolDocker *q;
Qt::DockWidgetArea dockingArea;
bool tabbed;
QIcon tabIcon;
QIcon unTabIcon;
QToolButton *tabButton;
void resetWidgets()
{
currentWidgetList.clear();
qDeleteAll(currentAuxWidgets);
currentAuxWidgets.clear();
}
void recreateLayout(const QList<QPointer<QWidget> > &optionWidgetList)
{
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (!widget.isNull() && widget && hiderWidget) {
widget->setParent(hiderWidget);
}
}
qDeleteAll(currentAuxWidgets);
currentAuxWidgets.clear();
currentWidgetList = optionWidgetList;
// need to unstretch row that have previously been stretched
housekeeperLayout->setRowStretch(housekeeperLayout->rowCount()-1, 0);
if (tabbed && currentWidgetList.size() > 1) {
QTabWidget *t;
housekeeperLayout->addWidget(t = new QTabWidget(), 0, 0);
t->setDocumentMode(true);
currentAuxWidgets.insert(t);
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (widget.isNull() || widget->objectName().isEmpty()) {
Q_ASSERT(!(widget->objectName().isEmpty()));
continue; // skip this docker in release build when assert don't crash
}
t->addTab(widget, widget->windowTitle());
}
} else {
int cnt = 0;
QFrame *s;
QLabel *l;
switch(dockingArea) {
case Qt::TopDockWidgetArea:
case Qt::BottomDockWidgetArea:
housekeeperLayout->setHorizontalSpacing(2);
housekeeperLayout->setVerticalSpacing(0);
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (widget.isNull() || widget->objectName().isEmpty()) {
- continue; // skip this docker in release build when assert don't crash
+ KIS_SAFE_ASSERT_RECOVER(!(widget->objectName().isEmpty())) { continue; }
}
if (!widget->windowTitle().isEmpty()) {
housekeeperLayout->addWidget(l = new QLabel(widget->windowTitle()), 0, 2*cnt);
currentAuxWidgets.insert(l);
}
housekeeperLayout->addWidget(widget, 1, 2*cnt);
widget->show();
if (widget != currentWidgetList.last()) {
housekeeperLayout->addWidget(s = new QFrame(), 0, 2*cnt+1, 2, 1);
s->setFrameShape(QFrame::VLine);
currentAuxWidgets.insert(s);
}
cnt++;
}
break;
case Qt::LeftDockWidgetArea:
case Qt::RightDockWidgetArea: {
housekeeperLayout->setHorizontalSpacing(0);
housekeeperLayout->setVerticalSpacing(2);
int specialCount = 0;
Q_FOREACH (QPointer<QWidget> widget, currentWidgetList) {
if (widget.isNull() || widget->objectName().isEmpty()) {
- Q_ASSERT(!(widget->objectName().isEmpty()));
- continue; // skip this docker in release build when assert don't crash
+ KIS_SAFE_ASSERT_RECOVER(!(widget->objectName().isEmpty())) { continue; }
}
if (!widget->windowTitle().isEmpty()) {
housekeeperLayout->addWidget(l = new QLabel(widget->windowTitle()), cnt++, 0);
currentAuxWidgets.insert(l);
}
housekeeperLayout->addWidget(widget, cnt++, 0);
QLayout *subLayout = widget->layout();
if (subLayout) {
for (int i = 0; i < subLayout->count(); ++i) {
QWidget *spacerWidget = subLayout->itemAt(i)->widget();
if (spacerWidget && spacerWidget->objectName().contains("SpecialSpacer")) {
specialCount++;
}
}
}
widget->show();
if (widget != currentWidgetList.last()) {
housekeeperLayout->addWidget(s = new QFrame(), cnt++, 0);
s->setFrameShape(QFrame::HLine);
currentAuxWidgets.insert(s);
}
}
if (specialCount == currentWidgetList.count() || qApp->applicationName().contains("krita")) {
housekeeperLayout->setRowStretch(cnt, 10000);
}
break;
}
default:
break;
}
}
housekeeperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
housekeeperLayout->invalidate();
}
void locationChanged(Qt::DockWidgetArea area)
{
dockingArea = area;
recreateLayout(currentWidgetList);
}
void toggleTab()
{
if (!tabbed) {
tabbed = true;
tabButton->setIcon(unTabIcon);
} else {
tabbed = false;
tabButton->setIcon(tabIcon);
}
recreateLayout(currentWidgetList);
}
};
KoToolDocker::KoToolDocker(QWidget *parent)
: QDockWidget(i18n("Tool Options"), parent),
d(new Private(this))
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("DockWidget sharedtooldocker");
d->tabbed = cfg.readEntry("TabbedMode", false);
setFeatures(DockWidgetMovable|DockWidgetFloatable);
setTitleBarWidget(new KoDockWidgetTitleBar(this));
connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea )), this, SLOT(locationChanged(Qt::DockWidgetArea)));
d->housekeeperWidget = new QWidget();
d->housekeeperLayout = new QGridLayout();
d->housekeeperLayout->setContentsMargins(4,4,4,0);
d->housekeeperWidget->setLayout(d->housekeeperLayout);
d->housekeeperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
d->hiderWidget = new QWidget(d->housekeeperWidget);
d->hiderWidget->setVisible(false);
d->scrollArea = new QScrollArea();
d->scrollArea->setWidget(d->housekeeperWidget);
d->scrollArea->setFrameShape(QFrame::NoFrame);
d->scrollArea->setWidgetResizable(true);
d->scrollArea->setFocusPolicy(Qt::NoFocus);
setWidget(d->scrollArea);
d->tabButton = new QToolButton(this); // parent hack in toggleLock to keep it clickable
d->tabButton->setIcon(d->tabIcon);
d->tabButton->setToolTip(i18n("Toggles organizing the options in tabs or not"));
d->tabButton->setAutoRaise(true);
connect(d->tabButton, SIGNAL(clicked()), SLOT(toggleTab()));
d->tabButton->resize(d->tabButton->sizeHint());
}
KoToolDocker::~KoToolDocker()
{
KConfigGroup cfg = KSharedConfig::openConfig()->group("DockWidget sharedtooldocker");
cfg.writeEntry("TabbedMode", d->tabbed);
cfg.sync();
delete d;
}
bool KoToolDocker::hasOptionWidget()
{
return !d->currentWidgetList.isEmpty();
}
void KoToolDocker::setTabEnabled(bool enabled)
{
d->tabButton->setVisible(enabled);
}
void KoToolDocker::setOptionWidgets(const QList<QPointer<QWidget> > &optionWidgetList)
{
d->recreateLayout(optionWidgetList);
}
void KoToolDocker::resizeEvent(QResizeEvent*)
{
int fw = isFloating() ? style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, this) : 0;
d->tabButton->move(width() - d->tabButton->width() - d->scrollArea->verticalScrollBar()->sizeHint().width(), fw);
}
void KoToolDocker::resetWidgets()
{
d->resetWidgets();
}
void KoToolDocker::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
}
void KoToolDocker::unsetCanvas()
{
setEnabled(false);
}
//have to include this because of Q_PRIVATE_SLOT
#include <moc_KoToolDocker.cpp>
diff --git a/libs/widgets/kis_double_parse_spin_box.cpp b/libs/widgets/kis_double_parse_spin_box.cpp
index 801d9cca2f..4131d701bd 100644
--- a/libs/widgets/kis_double_parse_spin_box.cpp
+++ b/libs/widgets/kis_double_parse_spin_box.cpp
@@ -1,235 +1,240 @@
/*
* 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"))
{
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();
- double v = KisNumericParser::parseSimpleMathExpr(cleanText());
+ double v = KisNumericParser::parseSimpleMathExpr(veryCleanText());
v = QString("%1").arg(v, 0, 'f', decimals()).toDouble();
- if (hasFocus() && (v == value() || (v >= maximum() && value() == maximum()) || (v <= minimum() && value() == minimum())) ) { //solve a very annoying bug where the formula can collapse while editing. With this trick the formula is not lost until focus is lost.
- return cleanText();
+ if (hasFocus() && (v == value() || (v > maximum() && value() == maximum()) || (v < minimum() && value() == minimum())) ) { //solve a very annoying bug where the formula can collapse while editing. With this trick the formula is not lost until focus is lost.
+ return veryCleanText();
}
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.
return;
}
+ QDoubleSpinBox::setValue(value);
+
if (!hasFocus()) {
clearError();
}
-
- QDoubleSpinBox::setValue(value);
}
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/widgets/kis_double_parse_spin_box.h b/libs/widgets/kis_double_parse_spin_box.h
index 29085af972..bc3310f5e6 100644
--- a/libs/widgets/kis_double_parse_spin_box.h
+++ b/libs/widgets/kis_double_parse_spin_box.h
@@ -1,82 +1,85 @@
/*
* 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.
*/
#ifndef KISDOUBLEPARSESPINBOX_H
#define KISDOUBLEPARSESPINBOX_H
#include <QDoubleSpinBox>
#include "kritawidgets_export.h"
class QLabel;
/*!
* \brief The KisDoubleParseSpinBox class is a cleverer doubleSpinBox, able to parse arithmetic expressions.
*
* Use this spinbox instead of the basic one from Qt if you want it to be able to parse arithmetic expressions.
*/
class KRITAWIDGETS_EXPORT KisDoubleParseSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
KisDoubleParseSpinBox(QWidget* parent = 0);
- ~KisDoubleParseSpinBox();
+ virtual ~KisDoubleParseSpinBox(); //KisDoubleParseSpinBox may be used polymorphycally as a QDoubleSpinBox.
virtual double valueFromText(const QString & text) const;
virtual QString textFromValue(double val) const;
virtual QValidator::State validate ( QString & input, int & pos ) const;
virtual void stepBy(int steps);
void setValue(double value); //polymorphism won't work directly, we use a signal/slot hack to do so but if signals are disabled this function will still be useful.
bool isLastValid() const{ return boolLastValid; }
+ //! \brief this virtual function is similar to cleanText(); for KisDoubleParseSpinBox. But child class may remove additional artifacts.
+ virtual QString veryCleanText() const;
+
Q_SIGNALS:
//! \brief signal emmitted when the last parsed expression create an error.
void errorWhileParsing(QString expr) const;
//! \brief signal emmitted when the last parsed expression is valid.
void noMoreParsingError() const;
public Q_SLOTS:
//! \brief useful to let the widget change it's stylesheet when an error occured in the last expression.
void setErrorStyle();
//! \brief useful to let the widget reset it's stylesheet when there's no more error.
void clearErrorStyle();
//! \brief say the widget to return to an error free state.
void clearError();
protected:
mutable bool boolLastValid;
mutable double oldValue;
mutable QString lastExprParsed;
QLabel* warningIcon;
QPalette oldPalette;
bool isOldPaletteSaved;
QMargins oldMargins;
bool areOldMarginsSaved;
};
#endif // KISDOUBLEPARSESPINBOX_H
diff --git a/libs/widgets/kis_double_parse_unit_spin_box.cpp b/libs/widgets/kis_double_parse_unit_spin_box.cpp
index 47c10515dc..21d05ad526 100644
--- a/libs/widgets/kis_double_parse_unit_spin_box.cpp
+++ b/libs/widgets/kis_double_parse_unit_spin_box.cpp
@@ -1,172 +1,422 @@
/*
* 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_unit_spin_box.h"
+#include "kis_spin_box_unit_manager.h"
+#include <QLineEdit>
class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private
{
public:
- Private(double low, double up, double step)
+ Private(double low, double up, double step, KisSpinBoxUnitManager* unitManager)
: lowerInPoints(low),
- upperInPoints(up),
- stepInPoints(step),
- unit(KoUnit(KoUnit::Point))
+ upperInPoints(up),
+ stepInPoints(step),
+ unit(KoUnit(KoUnit::Point)),
+ outPutSymbol(""),
+ unitManager(unitManager),
+ defaultUnitManager(unitManager),
+ isDeleting(false),
+ unitHasBeenChangedFromOutSideOnce(false),
+ letUnitBeChangedFromOutsideMoreThanOnce(true),
+ displayUnit(true)
{
}
double lowerInPoints; ///< lowest value in points
double upperInPoints; ///< highest value in points
double stepInPoints; ///< step in points
KoUnit unit;
+
+ double previousValueInPoint; ///< allow to store the previous value in point, usefull in some cases, even if, usually, we prefere to refer to the actual value (in selected unit) and convert it, since this is not alway updated.
+ QString previousSymbol;
+ QString outPutSymbol;
+
+ KisSpinBoxUnitManager* unitManager; //manage more units than permitted by KoUnit.
+ KisSpinBoxUnitManager* defaultUnitManager; //the default unit manager is the one the spinbox rely on and go back to if a connected unit manager is destroyed before the spinbox.
+
+ bool isDeleting;
+
+ bool unitHasBeenChangedFromOutSideOnce; //in some part of the code the unit is reset. We want to prevent this overriding the unit defined by the user. We use this switch to do so.
+ bool letUnitBeChangedFromOutsideMoreThanOnce;
+
+ bool displayUnit;
+
};
KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) :
KisDoubleParseSpinBox(parent),
- d(new Private(-9999, 9999, 1))
+ d(new Private(-9999, 9999, 1, KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(this)))
{
setUnit( KoUnit(KoUnit::Point) );
setAlignment( Qt::AlignRight );
- connect(this, SIGNAL(valueChanged( double )), SLOT(privateValueChanged()));
+ connect(this, SIGNAL(valueChanged( double )), this, SLOT(privateValueChanged()));
+ connect(lineEdit(), SIGNAL(textChanged(QString)),
+ this, SLOT(detectUnitChanges()) );
+
+ connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange);
+ connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
+
}
KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox()
{
+ d->isDeleting = true;
+ delete d->defaultUnitManager;
delete d;
}
+void KisDoubleParseUnitSpinBox::setUnitManager(KisSpinBoxUnitManager* unitManager)
+{
+ qreal oldVal = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value());
+ QString oldSymbol = d->unitManager->getApparentUnitSymbol();
+
+ qreal newVal;
+
+ double newMin;
+ double newMax;
+ double newStep;
+
+ if (oldSymbol == unitManager->getApparentUnitSymbol() &&
+ d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType())
+ {
+ d->unitManager = unitManager; //set the new unitmanager anyway, since it may be a subclass, so change the behavior anyway.
+ goto connect_signals;
+ }
+
+ if (d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) {
+ //dimension is the same, calculate the new value
+ newVal = unitManager->getApparentValue(oldVal);
+ } else {
+ newVal = unitManager->getApparentValue(d->lowerInPoints);
+ }
+
+ newMin = unitManager->getApparentValue(d->lowerInPoints);
+ newMax = unitManager->getApparentValue(d->upperInPoints);
+ newStep = unitManager->getApparentValue(d->stepInPoints);
+
+ if (unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) {
+ // limit the pixel step by 1.0
+ newStep = qMax(qreal(1.0), newStep);
+ }
+
+ KisDoubleParseSpinBox::setMinimum(newMin);
+ KisDoubleParseSpinBox::setMaximum(newMax);
+ KisDoubleParseSpinBox::setSingleStep(newStep);
+
+connect_signals:
+
+ if (d->unitManager != d->defaultUnitManager) {
+ disconnect(d->unitManager, &QObject::destroyed,
+ this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependance anymore.
+ }
+ disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange);
+ disconnect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
+
+ d->unitManager = unitManager;
+
+ connect(d->unitManager, &QObject::destroyed,
+ this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager);
+
+
+ connect(d->unitManager, (void (KisSpinBoxUnitManager::*)()) &KisSpinBoxUnitManager::unitAboutToChange, this, (void (KisDoubleParseUnitSpinBox::*)()) &KisDoubleParseUnitSpinBox::prepareUnitChange);
+ connect(d->unitManager, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, (void (KisDoubleParseUnitSpinBox::*)( QString const& )) &KisDoubleParseUnitSpinBox::internalUnitChange);
+
+ KisDoubleParseSpinBox::setValue(newVal);
+}
+
void KisDoubleParseUnitSpinBox::changeValue( double newValue )
{
- if (d->unit.toUserValue(newValue) == oldValue) {
+ double apparentValue;
+ double fact;
+ double cons;
+
+ if (d->outPutSymbol.isEmpty()) {
+ apparentValue = d->unitManager->getApparentValue(newValue);
+ } else {
+
+ fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
+ cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
+
+ apparentValue = fact*newValue + cons;
+ }
+
+ if (apparentValue == KisDoubleParseSpinBox::value()) {
return;
}
- KisDoubleParseSpinBox::setValue( d->unit.toUserValue(newValue) );
+ if (d->outPutSymbol.isEmpty()) {
+ KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue(newValue) );
+ } else {
+
+ KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue((newValue - cons)/fact) );
+ }
}
void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit)
{
- if( unit == d->unit) return;
- double oldValue = d->unit.fromUserValue( KisDoubleParseSpinBox::value() );
+ if (d->unitHasBeenChangedFromOutSideOnce && !d->letUnitBeChangedFromOutsideMoreThanOnce) {
+ return;
+ }
- KisDoubleParseSpinBox::setMinimum( unit.toUserValue( d->lowerInPoints ) );
- KisDoubleParseSpinBox::setMaximum( unit.toUserValue( d->upperInPoints ) );
+ if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) {
+ d->unitManager->setUnitDimension(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length.
+ }
- qreal step = unit.toUserValue( d->stepInPoints );
+ setUnit(unit.symbol());
+ d->unit = unit;
+}
+void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol)
+{
+ d->unitManager->setApparentUnitFromSymbol(symbol); //via signals and slots, the correct functions should be called.
+}
+void KisDoubleParseUnitSpinBox::setReturnUnit(const QString & symbol)
+{
+ d->outPutSymbol = symbol;
+}
+
+void KisDoubleParseUnitSpinBox::prepareUnitChange() {
+
+ d->previousValueInPoint = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value());
+ d->previousSymbol = d->unitManager->getApparentUnitSymbol();
+
+}
+
+void KisDoubleParseUnitSpinBox::internalUnitChange(const QString &symbol) {
- if (unit.type() == KoUnit::Pixel) {
+ //d->unitManager->setApparentUnitFromSymbol(symbol);
+
+ if (d->unitManager->getApparentUnitSymbol() == d->previousSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this.
+ return;
+ }
+
+ KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) );
+ KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) );
+
+ qreal step = d->unitManager->getApparentValue( d->stepInPoints );
+
+ if (symbol == KoUnit(KoUnit::Pixel).symbol()) {
// limit the pixel step by 1.0
step = qMax(qreal(1.0), step);
}
KisDoubleParseSpinBox::setSingleStep( step );
- d->unit = unit;
- KisDoubleParseSpinBox::setValue( KoUnit::ptToUnit( oldValue, unit ) );
- setSuffix( unit.symbol().prepend(QLatin1Char(' ')) );
+ KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( d->previousValueInPoint ) );
+
+ d->unitHasBeenChangedFromOutSideOnce = true;
+}
+
+void KisDoubleParseUnitSpinBox::setDimensionType(int dim)
+{
+ if (!KisSpinBoxUnitManager::isUnitId(dim)) {
+ return;
+ }
+
+ d->unitManager->setUnitDimension((KisSpinBoxUnitManager::UnitDimension) dim);
}
double KisDoubleParseUnitSpinBox::value( ) const
{
- return d->unit.fromUserValue( KisDoubleParseSpinBox::value() );
+ if (d->outPutSymbol.isEmpty()) {
+ return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
+ }
+
+ double ref = d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() );
+ double fact = d->unitManager->getConversionFactor(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
+ double cons = d->unitManager->getConversionConstant(d->unitManager->getUnitDimensionType(), d->outPutSymbol);
+
+ return fact*ref + cons;
}
void KisDoubleParseUnitSpinBox::setMinimum(double min)
{
d->lowerInPoints = min;
- KisDoubleParseSpinBox::setMinimum( d->unit.toUserValue( min ) );
+ KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) );
}
void KisDoubleParseUnitSpinBox::setMaximum(double max)
{
d->upperInPoints = max;
- KisDoubleParseSpinBox::setMaximum( d->unit.toUserValue( max ) );
+ KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) );
}
void KisDoubleParseUnitSpinBox::setLineStep(double step)
{
- d->stepInPoints = KoUnit(KoUnit::Point).toUserValue(step);
+ d->stepInPoints = d->unitManager->getReferenceValue(step);
KisDoubleParseSpinBox::setSingleStep( step );
}
void KisDoubleParseUnitSpinBox::setLineStepPt(double step)
{
d->stepInPoints = step;
- KisDoubleParseSpinBox::setSingleStep( d->unit.toUserValue( step ) );
+ KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) );
}
void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step )
{
setMinimum( min );
setMaximum( max );
setLineStepPt( step );
}
QValidator::State KisDoubleParseUnitSpinBox::validate(QString &input, int &pos) const
{
Q_UNUSED(pos);
QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end
const int res = input.indexOf( regexp );
- if ( res == -1 ) {
+ /*if ( res == -1 ) {
// Nothing like an unit? The user is probably editing the unit
return QValidator::Intermediate;
- }
+ }*/
- QString expr ( input.left( res ) );
- const QString unitName ( regexp.cap( 1 ).trimmed().toLower() );
+ QString expr ( (res > 0) ? input.left( res ) : input );
+ const QString unitName ( (res > 0) ? regexp.cap( 1 ).trimmed().toLower() : "" );
bool ok = true;
bool interm = false;
QValidator::State exprState = KisDoubleParseSpinBox::validate(expr, pos);
+ if (res < 0) {
+ return exprState;
+ }
+
if (exprState == QValidator::Invalid) {
return exprState;
} else if (exprState == QValidator::Intermediate) {
interm = true;
}
//check if we can parse the unit.
- KoUnit::fromSymbol(unitName, &ok);
+ QStringList listOfSymbol = d->unitManager->getsUnitSymbolList();
+ ok = listOfSymbol.contains(unitName);
if (!ok || interm) {
return QValidator::Intermediate;
}
return QValidator::Acceptable;
}
QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const
{
- return KisDoubleParseSpinBox::textFromValue(value);
+ QString txt = KisDoubleParseSpinBox::textFromValue(value);
+ if (d->displayUnit) {
+ if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) {
+ txt += " " + d->unitManager->getApparentUnitSymbol();
+ }
+ }
+ return txt;
+}
+
+QString KisDoubleParseUnitSpinBox::veryCleanText() const
+{
+
+ return makeTextClean(cleanText());
+
}
double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const
{
- //KisDoubleParseSpinBox is supposed to remove the suffix and prefix by itself.
- return KisDoubleParseSpinBox::valueFromText(str);
+
+ QString txt = makeTextClean(str);
+
+ return KisDoubleParseSpinBox::valueFromText(txt); //this function will take care of prefix (and don't mind if suffix has been removed.
+}
+
+void KisDoubleParseUnitSpinBox::setUnitChangeFromOutsideBehavior(bool toggle) {
+ d->letUnitBeChangedFromOutsideMoreThanOnce = toggle;
+}
+
+void KisDoubleParseUnitSpinBox::setDisplayUnit(bool toggle) {
+
+ d->displayUnit = toggle;
+
}
void KisDoubleParseUnitSpinBox::privateValueChanged()
{
emit valueChangedPt( value() );
}
+
+QString KisDoubleParseUnitSpinBox::detectUnit()
+{
+ QString str = veryCleanText().trimmed(); //text with the new unit but not the old one.
+
+ QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end
+ int res = str.indexOf( regexp );
+
+ if (res > -1) {
+ QString expr ( str.right( str.size() - res ) );
+ expr = expr.trimmed();
+ return expr;
+ }
+
+ return "";
+}
+
+void KisDoubleParseUnitSpinBox::detectUnitChanges()
+{
+ QString unitSymb = detectUnit();
+
+ if (unitSymb.isEmpty()) {
+ return;
+ }
+
+ QString oldUnitSymb = d->unitManager->getApparentUnitSymbol();
+
+ setUnit(unitSymb);
+ setValue(valueFromText(cleanText())); //change value keep the old value, but converted to new unit... which is different from the value the user entered in the new unit. So we need to set the new value.
+
+ if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) {
+ // the user has changed the unit, so we block changes from outside.
+ setUnitChangeFromOutsideBehavior(false);
+ }
+}
+
+QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const
+{
+ QString expr = txt;
+ QString symbol = d->unitManager->getApparentUnitSymbol();
+
+ if ( expr.endsWith(suffix()) ) {
+ expr.remove(expr.size()-suffix().size(), suffix().size());
+ }
+
+ expr = expr.trimmed();
+
+ if ( expr.endsWith(symbol) ) {
+ expr.remove(expr.size()-symbol.size(), symbol.size());
+ }
+
+ return expr.trimmed();
+}
+
+void KisDoubleParseUnitSpinBox::disconnectExternalUnitManager()
+{
+ if (!d->isDeleting)
+ {
+ setUnitManager(d->defaultUnitManager); //go back to default unit manager.
+ }
+}
diff --git a/libs/widgets/kis_double_parse_unit_spin_box.h b/libs/widgets/kis_double_parse_unit_spin_box.h
index 83094f8e85..a8efac06df 100644
--- a/libs/widgets/kis_double_parse_unit_spin_box.h
+++ b/libs/widgets/kis_double_parse_unit_spin_box.h
@@ -1,100 +1,137 @@
/*
* 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.
*/
#ifndef KIS_DOUBLEPARSEUNITSPINBOX_H
#define KIS_DOUBLEPARSEUNITSPINBOX_H
#include <KoUnit.h>
#include "kis_double_parse_spin_box.h"
#include "kritawidgets_export.h"
+class KisSpinBoxUnitManager;
+
/*!
* \brief The KisDoubleParseUnitSpinBox class is an evolution of the \see KoUnitDoubleSpinBox, but inherit from \see KisDoubleParseSpinBox to be able to parse math expressions.
*
* This class store the
*/
class KRITAWIDGETS_EXPORT KisDoubleParseUnitSpinBox : public KisDoubleParseSpinBox
{
Q_OBJECT
public:
KisDoubleParseUnitSpinBox(QWidget* parent = 0);
- ~KisDoubleParseUnitSpinBox();
+ virtual ~KisDoubleParseUnitSpinBox();
+
+ void setUnitManager(KisSpinBoxUnitManager* unitManager);
/**
- * Set the new value in points which will then be converted to the current unit for display
+ * Set the new value in points (or other reference unit) which will then be converted to the current unit for display
* @param newValue the new value
* @see value()
*/
virtual void changeValue( double newValue );
+
/**
* This spinbox shows the internal value after a conversion to the unit set here.
*/
virtual void setUnit(const KoUnit &unit);
+ virtual void setUnit(const QString & symbol);
+ /*!
+ * \brief setReturnUnit set a unit, such that the spinbox now return values in this unit instead of the reference unit for the current dimension.
+ * \param symbol the symbol of the new unit.
+ */
+ void setReturnUnit(const QString & symbol);
+
+ /**
+ * @brief setDimensionType set the dimension (for example length or angle) of the units the spinbox manage
+ * @param dim the dimension id. (if not an id in KisSpinBoxUnitManager::UnitDimension, then the function does nothing).
+ */
+ virtual void setDimensionType(int dim);
/// @return the current value, converted in points
double value( ) const;
/// Set minimum value in points.
void setMinimum(double min);
/// Set maximum value in points.
void setMaximum(double max);
/// Set step size in the current unit.
void setLineStep(double step);
/// Set step size in points.
void setLineStepPt(double step);
/// Set minimum, maximum value and the step size (all in points)
void setMinMaxStep( double min, double max, double step );
/// reimplemented from superclass, will forward to KoUnitDoubleValidator
virtual QValidator::State validate(QString &input, int &pos) const;
/**
* Transform the double in a nice text, using locale symbols
* @param value the number as double
* @return the resulting string
*/
virtual QString textFromValue( double value ) const;
+
+ //! \brief get the text in the spinbox without prefix or suffix, and remove unit symbol if present.
+ virtual QString veryCleanText() const;
+
/**
* Transfrom a string into a double, while taking care of locale specific symbols.
* @param str the string to transform into a number
* @return the value as double
*/
virtual double valueFromText( const QString& str ) const;
+ void setUnitChangeFromOutsideBehavior(bool toggle); //if set to false, setting the unit using KoUnit won't have any effect.
+
+ //! \brief display the unit symbol in the spinbox or not. For example if the unit is displayed in a combobox connected to the unit manager.
+ void setDisplayUnit(bool toggle);
+
Q_SIGNALS:
- /// emitted like valueChanged in the parent, but this one emits the point value
+ /// emitted like valueChanged in the parent, but this one emits the point value, or converted to another reference unit.
void valueChangedPt( qreal );
private:
class Private;
Private * const d;
+ QString detectUnit();
+ QString makeTextClean(QString const& txt) const;
+
+ //thoses functions are usefull to sync the spinbox with it's unitmanager.
+ //! \brief change the unit, reset the spin box everytime. From the outside it's alway set unit that should be called.
+ void internalUnitChange(QString const& symbol);
+ void prepareUnitChange();
+
private Q_SLOTS:
// exists to do emits for valueChangedPt
void privateValueChanged();
+ void detectUnitChanges();
+ void disconnectExternalUnitManager();
+
};
#endif // KIS_DOUBLEPARSEUNITSPINBOX_H
diff --git a/libs/widgets/tests/CMakeLists.txt b/libs/widgets/tests/CMakeLists.txt
index 1b9c7fb4e1..496beec7ff 100644
--- a/libs/widgets/tests/CMakeLists.txt
+++ b/libs/widgets/tests/CMakeLists.txt
@@ -1,25 +1,30 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/")
add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}")
#add_subdirectory(filedialogtester)
include(ECMAddTests)
include(KritaAddBrokenUnitTest)
ecm_add_tests(
zoomhandler_test.cpp
zoomcontroller_test.cpp
NAME_PREFIX "libs-widgets-"
LINK_LIBRARIES kritawidgets Qt5::Test)
krita_add_broken_unit_test(
KoResourceTaggingTest.cpp
TEST_NAME libs-widgets-KoResourceTaggingTest
LINK_LIBRARIES kritawidgets Qt5::Test)
ecm_add_tests(
kis_parse_spin_boxes_test.cpp
NAME_PREFIX "krita-ui-"
LINK_LIBRARIES kritaui Qt5::Test)
+
+ecm_add_tests(
+ KoAnchorSelectionWidgetTest.cpp
+ NAME_PREFIX "libs-widgets-"
+ LINK_LIBRARIES kritaui Qt5::Test)
diff --git a/libs/widgets/tests/KoAnchorSelectionWidgetTest.cpp b/libs/widgets/tests/KoAnchorSelectionWidgetTest.cpp
new file mode 100644
index 0000000000..49e0324912
--- /dev/null
+++ b/libs/widgets/tests/KoAnchorSelectionWidgetTest.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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 "KoAnchorSelectionWidgetTest.h"
+
+#include <QTest>
+#include <QDialog>
+
+#include <QVBoxLayout>
+
+#include "kis_debug.h"
+
+
+void KoAnchorSelectionWidgetTest::test()
+{
+ QDialog dlg;
+
+ KoAnchorSelectionWidget *widget = new KoAnchorSelectionWidget(&dlg);
+ connect(widget,
+ SIGNAL(valueChanged(KoFlake::AnchorPosition)),
+ SLOT(slotValueChanged(KoFlake::AnchorPosition)));
+
+ QVBoxLayout *layout = new QVBoxLayout(&dlg);
+ layout->addWidget(widget);
+ dlg.setLayout(layout);
+ dlg.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+
+ //dlg.exec();
+ qWarning() << "WARNING: showing of the dialogs in the unittest is disabled!";
+}
+
+void KoAnchorSelectionWidgetTest::slotValueChanged(KoFlake::AnchorPosition id)
+{
+ ENTER_FUNCTION() << ppVar(id);
+}
+
+QTEST_MAIN(KoAnchorSelectionWidgetTest)
diff --git a/libs/widgets/tests/KoAnchorSelectionWidgetTest.h b/libs/widgets/tests/KoAnchorSelectionWidgetTest.h
new file mode 100644
index 0000000000..a91a91dc8d
--- /dev/null
+++ b/libs/widgets/tests/KoAnchorSelectionWidgetTest.h
@@ -0,0 +1,36 @@
+/*
+ * 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 __KO_ANCHOR_SELECTION_WIDGET_TEST_H
+#define __KO_ANCHOR_SELECTION_WIDGET_TEST_H
+
+#include <QtTest/QtTest>
+
+#include <KoAnchorSelectionWidget.h>
+
+class KoAnchorSelectionWidgetTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void test();
+
+private Q_SLOTS:
+ void slotValueChanged(KoFlake::AnchorPosition id);
+};
+
+#endif /* __KO_ANCHOR_SELECTION_WIDGET_TEST_H */
diff --git a/libs/widgetutils/CMakeLists.txt b/libs/widgetutils/CMakeLists.txt
index ede32a1558..a8577f39f3 100644
--- a/libs/widgetutils/CMakeLists.txt
+++ b/libs/widgetutils/CMakeLists.txt
@@ -1,128 +1,130 @@
add_subdirectory(tests)
configure_file(xmlgui/config-xmlgui.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-xmlgui.h )
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/config)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/xmlgui)
set(kritawidgetutils_LIB_SRCS
WidgetUtilsDebug.cpp
kis_icon_utils.cpp
kis_action_registry.cpp
KisActionsSnapshot.cpp
KoGroupButton.cpp
KoProgressBar.cpp
KoProgressUpdater.cpp
KoUpdater.cpp
KoUpdaterPrivate_p.cpp
KoProperties.cpp
KoFileDialog.cpp
KoResourcePaths.cpp
kis_num_parser.cpp
+ kis_spin_box_unit_manager.cpp
config/kcolorscheme.cpp
config/kcolorschememanager.cpp
config/khelpclient.cpp
config/klanguagebutton.cpp
config/krecentfilesaction.cpp
config/kstandardaction.cpp
xmlgui/KisShortcutsEditorItem.cpp
xmlgui/KisShortcutEditWidget.cpp
xmlgui/KisShortcutsEditorDelegate.cpp
xmlgui/KisShortcutsDialog.cpp
xmlgui/KisShortcutsDialog_p.cpp
xmlgui/KisShortcutsEditor.cpp
xmlgui/KisShortcutsEditor_p.cpp
xmlgui/kshortcutschemeseditor.cpp
xmlgui/kshortcutschemeshelper.cpp
xmlgui/kaboutkdedialog_p.cpp
xmlgui/kactioncategory.cpp
xmlgui/kactioncollection.cpp
xmlgui/kactionconflictdetector.cpp
xmlgui/kbugreport.cpp
xmlgui/kcheckaccelerators.cpp
xmlgui/kedittoolbar.cpp
xmlgui/kgesture.cpp
xmlgui/kgesturemap.cpp
xmlgui/khelpmenu.cpp
xmlgui/kkeysequencewidget.cpp
xmlgui/kmainwindow.cpp
xmlgui/kmenumenuhandler_p.cpp
xmlgui/kshortcutwidget.cpp
xmlgui/kswitchlanguagedialog_p.cpp
xmlgui/ktoggletoolbaraction.cpp
xmlgui/ktoolbar.cpp
xmlgui/ktoolbarhandler.cpp
xmlgui/kundoactions.cpp
xmlgui/kxmlguibuilder.cpp
xmlgui/kxmlguiclient.cpp
xmlgui/kxmlguifactory.cpp
xmlgui/kxmlguifactory_p.cpp
xmlgui/kxmlguiversionhandler.cpp
xmlgui/kxmlguiwindow.cpp
)
if (HAVE_DBUS)
set(kritawidgetutils_LIB_SRCS ${kritawidgetutils_LIB_SRCS}
xmlgui/kmainwindowiface.cpp
)
endif()
ki18n_wrap_ui(kritawidgetutils_LIB_SRCS
xmlgui/KisShortcutsDialog.ui
xmlgui/kshortcutwidget.ui
)
qt5_add_resources(kritawidgetutils_LIB_SRCS xmlgui/kxmlgui.qrc)
add_library(kritawidgetutils SHARED ${kritawidgetutils_LIB_SRCS})
target_include_directories(kritawidgetutils
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/config>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/xmlgui>
)
generate_export_header(kritawidgetutils BASE_NAME kritawidgetutils)
if (HAVE_DBUS)
set (KRITA_WIDGET_UTILS_EXTRA_LIBS ${KRITA_WIDGET_UTILS_EXTRA_LIBS} Qt5::DBus)
endif ()
if (APPLE)
find_library(FOUNDATION_LIBRARY Foundation)
set(KRITA_WIDGET_UTILS_EXTRA_LIBS ${KRITA_WIDGET_UTILS_EXTRA_LIBS} ${FOUNDATION_LIBRARY})
endif ()
target_link_libraries(kritawidgetutils
PUBLIC
Qt5::Widgets
Qt5::Gui
Qt5::Xml
Qt5::Core
KF5::ItemViews
kritaglobal
PRIVATE
Qt5::PrintSupport
KF5::I18n
KF5::ConfigCore
KF5::CoreAddons
KF5::ConfigGui
KF5::GuiAddons
KF5::WidgetsAddons
KF5::WindowSystem
kritaplugin
+ kritaodf
${KRITA_WIDGET_UTILS_EXTRA_LIBS}
)
set_target_properties(kritawidgetutils
PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritawidgetutils ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/libs/widgetutils/kis_num_parser.cpp b/libs/widgetutils/kis_num_parser.cpp
index 262c4cf404..99576c9c4c 100644
--- a/libs/widgetutils/kis_num_parser.cpp
+++ b/libs/widgetutils/kis_num_parser.cpp
@@ -1,567 +1,574 @@
/*
* 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_num_parser.h"
#include <qnumeric.h> // for qIsNaN
#include <qmath.h>
#include <QVector>
#include <QRegExp>
#include <QStringList>
#include <QVariant>
+#include <QLocale>
#include <iostream>
using namespace std;
const QVector<char> opLevel1 = {'+', '-'};
const QVector<char> opLevel2 = {'*', '/'};
const QStringList supportedFuncs = {"", "cos", "sin", "tan", "acos", "asin", "atan", "exp", "ln", "log10", "abs"};
const QRegExp funcExpr("(-)?([a-zA-Z]*[0-9]*)?\\((.+)\\)");
const QRegExp numberExpr("(-)?([0-9]+\\.?[0-9]*(e[0-9]*)?)");
const QRegExp funcExprInteger("(-)?\\((.+)\\)");
const QRegExp integerExpr("(-)?([0-9]+)");
//double functions
double treatFuncs(QString const& expr, bool & noProblem);
double treatLevel1(QString const& expr, bool & noProblem);
double treatLevel2(QString const& expr, bool & noProblem);
double treatLevel3(QString const& expr, bool & noProblem);
//int functions
double treatLevel1Int(QString const& expr, bool & noProblem);
double treatLevel2Int(QString const& expr, bool & noProblem);
double treatFuncsInt(QString const& expr, bool & noProblem);
namespace KisNumericParser {
/*!
* \param expr the expression to parse
* \param noProblem if provided, the value pointed to will be se to true is no problem appeared, false otherwise.
* \return the numerical value the expression eval to (or 0 in case of error).
*/
double parseSimpleMathExpr(const QString &expr, bool *noProblem)
{
bool ok = true; //intermediate variable to pass by reference to the sublevel parser (if no pointer is provided).
//then go down each 3 levels of operation priority.
if (noProblem != nullptr) {
return treatLevel1(expr, *noProblem);
}
return treatLevel1(expr, ok);
}
/*!
* \param expr the expression to parse
* \param noProblem if provided, the value pointed to will be se to true is no problem appeared, false otherwise.
* \return the numerical value the expression eval to (or 0 in case of error).
*/
int parseIntegerMathExpr(QString const& expr, bool* noProblem)
{
bool ok = true; //intermediate variable to pass by reference to the sublevel parser (if no pointer is provided).
if (noProblem != nullptr) {
return qRound(treatLevel1Int(expr, *noProblem));
}
return qRound(treatLevel1Int(expr, ok));
}
} //namespace KisNumericParser.
//intermediate functions
/*!
* \brief extractSubExprLevel1 extract from an expression the part of an expression that need to be treated recursivly before computing level 1 operations (+, -).
* \param expr The expression to treat, the part returned will be removed.
* \param nextOp This reference, in case of sucess, will hold the first level operation identified as separator ('+' or '-')
* \param noProblem A reference to a bool, set to true if there was no problem, false otherwise.
* \return The first part of the expression that doesn't contain first level operations not nested within parenthesis.
*/
inline QString extractSubExprLevel1(QString & expr, char & nextOp, bool & noProblem){
QString ret;
int subCount = 0;
bool lastMetIsNumber = false;
for(int i = 0; i < expr.size(); i++){
if (expr.at(i) == '(') {
subCount++;
}
if (expr.at(i) == ')') {
subCount--;
}
if (subCount < 0) {
noProblem = false;
return ret;
}
if(i == expr.size()-1 && subCount == 0){
ret = expr;
expr.clear();
break;
}
if( (expr.at(i) == '+' || expr.at(i) == '-') &&
subCount == 0) {
if (expr.at(i) == '-' &&
i < expr.size()-1) {
bool cond = !expr.at(i+1).isSpace();
if (cond && !lastMetIsNumber) {
continue;
}
}
ret = expr.mid(0, i).trimmed();
nextOp = expr.at(i).toLatin1();
expr = expr.mid(i+1);
break;
}
if (expr.at(i).isDigit()) {
lastMetIsNumber = true;
} else if (expr.at(i) != '.' &&
!expr.at(i).isSpace()) {
lastMetIsNumber = false;
}
}
noProblem = true;
return ret;
}
/*!
* \brief extractSubExprLevel2 extract from an expression the part of an expression that need to be treated recursivly before computing level 2 operations (*, /).
* \param expr The expression to treat, the part returned will be removed.
* \param nextOp This reference, in case of sucess, will hold the first level operation identified as separator ('*' or '/')
* \param noProblem A reference to a bool, set to true if there was no problem, false otherwise.
* \return The first part of the expression that doesn't contain second level operations not nested within parenthesis.
*/
inline QString extractSubExprLevel2(QString & expr, char & nextOp, bool & noProblem){
QString ret;
int subCount = 0;
for(int i = 0; i < expr.size(); i++){
if (expr.at(i) == '(') {
subCount++;
}
if (expr.at(i) == ')') {
subCount--;
}
if (subCount < 0) {
noProblem = false;
return ret;
}
if(i == expr.size()-1 && subCount == 0){
ret = expr;
expr.clear();
break;
}
if( (expr.at(i) == '*' || expr.at(i) == '/') &&
subCount == 0) {
ret = expr.mid(0, i).trimmed();
nextOp = expr.at(i).toLatin1();
expr = expr.mid(i+1);
break;
}
}
noProblem = true;
return ret;
}
/*!
* \brief treatLevel1 treat an expression at the first level of recursion.
* \param expr The expression to treat.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed expression or subexpression or 0 in case of error.
*/
double treatLevel1(const QString &expr, bool & noProblem)
{
noProblem = true;
QString exprDestructable = expr;
char nextOp = '+';
double result = 0.0;
while (!exprDestructable.isEmpty()) {
double sign = (nextOp == '-') ? -1 : 1;
QString part = extractSubExprLevel1(exprDestructable, nextOp, noProblem);
if (!noProblem) {
return 0.0;
}
if (sign > 0) {
result += treatLevel2(part, noProblem);
} else {
result -= treatLevel2(part, noProblem);
}
if(!noProblem){
return 0.0;
}
}
return result;
}
/*!
* \brief treatLevel2 treat a subexpression at the second level of recursion.
* \param expr The subexpression to treat.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed subexpression or 0 in case of error.
*
* The expression should not contain first level operations not nested in parenthesis.
*/
double treatLevel2(QString const& expr, bool & noProblem)
{
noProblem = true;
QString exprDestructable = expr;
char nextOp = '*';
QString part = extractSubExprLevel2(exprDestructable, nextOp, noProblem);
double result = treatLevel3(part, noProblem);
while (!exprDestructable.isEmpty()) {
if (!noProblem) {
return 0.0;
}
bool needToMultiply = (nextOp == '*');
part = extractSubExprLevel2(exprDestructable, nextOp, noProblem);
if (!noProblem) {
return 0.0;
}
if (needToMultiply) {
result *= treatLevel3(part, noProblem);
} else {
result /= treatLevel3(part, noProblem);
}
}
return result;
}
/*!
* \brief treatLevel3 treat a subexpression at the third level of recursion.
* \param expr The subexpression to treat.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed subexpression or 0 in case of error.
*
* The expression should not contain first or second level operations not nested in parenthesis.
*/
double treatLevel3(const QString &expr, bool & noProblem)
{
noProblem = true;
int indexPower = -1;
int indexCount = 0;
int subLevels = 0;
for (int i = 0; i < expr.size(); i++) {
if (expr.at(i) == '(') {
subLevels++;
} else if(expr.at(i) == ')') {
subLevels--;
if (subLevels < 0) {
noProblem = false;
return 0.0;
}
} else if (expr.at(i) == '^') {
if (subLevels == 0) {
indexPower = i;
indexCount++;
}
}
}
if (indexCount > 1 || indexPower + 1 >= expr.size()) {
noProblem = false;
return 0.0;
}
if (indexPower > -1) {
QStringList subExprs;
subExprs << expr.mid(0,indexPower);
subExprs << expr.mid(indexPower+1);
bool noProb1 = true;
bool noProb2 = true;
double base = treatFuncs(subExprs[0], noProb1);
double power = treatFuncs(subExprs[1], noProb2);
return qPow(base, power);
} else {
return treatFuncs(expr, noProblem);
}
noProblem = false;
return 0.0;
}
/*!
* \brief treatFuncs treat the last level of recursion: parenthesis and functions.
* \param expr The expression to parse.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed subexpression or 0 in case of error.
*
* The expression should not contain operators not nested anymore. The subexpressions within parenthesis will be treated by recalling the level 1 function.
*/
double treatFuncs(QString const& expr, bool & noProblem)
{
noProblem = true;
QRegExp funcExp = funcExpr; //copy the expression in the current execution stack, to avoid errors for example when multiple thread call this function.
QRegExp numExp = numberExpr;
if (funcExp.exactMatch(expr.trimmed())) {
int sign = funcExp.capturedTexts()[1].isEmpty() ? 1 : -1;
QString func = funcExp.capturedTexts()[2].toLower();
QString subExpr = funcExp.capturedTexts()[3];
double val = treatLevel1(subExpr, noProblem);
if (!noProblem) {
return 0.0;
}
if (func.isEmpty()) {
return sign*val;
}
if (!supportedFuncs.contains(func)) {
noProblem = false;
return 0.0;
}
//trigonometry is done in degree
if (func == "cos") {
val = qCos(val/180*qAcos(-1));
} else if (func == "sin") {
val = qSin(val/180*qAcos(-1));
} else if (func == "tan") {
val = qTan(val/180*qAcos(-1));
} else if(func == "acos") {
val = qAcos(val)*180/qAcos(-1);
} else if (func == "asin") {
val = qAsin(val)*180/qAcos(-1);
} else if (func == "atan") {
val = qAtan(val)*180/qAcos(-1);
} else if (func == "exp") {
val = qExp(val);
} else if (func == "ln") {
val = qLn(val);
} else if (func == "log10") {
val = qLn(val)/qLn(10.0);
} else if (func == "abs") {
val = qAbs(val);
}
return sign*val;
} else if(numExp.exactMatch(expr.trimmed())) {
return expr.toDouble(&noProblem);
}
+ double val = QLocale().toDouble(expr, &noProblem);
+
+ if(noProblem) {
+ return val;
+ }
+
noProblem = false;
return 0.0;
}
//int functions
/*!
* \brief treatLevel1 treat an expression at the first level of recursion.
* \param expr The expression to treat.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed expression or subexpression or 0 in case of error.
*/
double treatLevel1Int(QString const& expr, bool & noProblem)
{
noProblem = true;
QString exprDestructable = expr;
char nextOp = '+';
double result = 0.0;
while (!exprDestructable.isEmpty()) {
double sign = (nextOp == '-') ? -1 : 1;
QString part = extractSubExprLevel1(exprDestructable, nextOp, noProblem);
if( !noProblem) {
return 0.0;
}
if (sign > 0) {
result += treatLevel2Int(part, noProblem);
} else {
result -= treatLevel2Int(part, noProblem);
}
if(!noProblem){
return 0.0;
}
}
return result;
}
/*!
* \brief treatLevel2 treat a subexpression at the second level of recursion.
* \param expr The subexpression to treat.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed subexpression or 0 in case of error.
*
* The expression should not contain first level operations not nested in parenthesis.
*/
double treatLevel2Int(const QString &expr, bool &noProblem)
{
noProblem = true;
QString exprDestructable = expr;
char nextOp = '*';
QString part = extractSubExprLevel2(exprDestructable, nextOp, noProblem);
double result = treatFuncsInt(part, noProblem);
while (!exprDestructable.isEmpty()) {
if (!noProblem) {
return 0.0;
}
bool needToMultiply = (nextOp == '*');
part = extractSubExprLevel2(exprDestructable, nextOp, noProblem);
if (!noProblem) {
return 0.0;
}
if (needToMultiply) {
result *= treatFuncsInt(part, noProblem);
} else {
double val = treatFuncsInt(part, noProblem);
if(std::isinf(result/val) || qIsNaN(result/val)){
noProblem = false;
return 0.0;
}
result /= val;
}
}
return result;
}
/*!
* \brief treatFuncs treat the last level of recursion: parenthesis
* \param expr The expression to parse.
* \param noProblem A reference to a bool set to true if no problem happened, false otherwise.
* \return The value of the parsed subexpression or 0 in case of error.
*
* The expression should not contain operators not nested anymore. The subexpressions within parenthesis will be treated by recalling the level 1 function.
*/
double treatFuncsInt(QString const& expr, bool & noProblem)
{
noProblem = true;
QRegExp funcExpInteger = funcExprInteger;
QRegExp integerExp = integerExpr;
QRegExp numberExp = numberExpr;
if (funcExpInteger.exactMatch(expr.trimmed())) {
int sign = funcExpInteger.capturedTexts()[1].isEmpty() ? 1 : -1;
QString subExpr = funcExpInteger.capturedTexts()[2];
double val = treatLevel1Int(subExpr, noProblem);
if (!noProblem) {
return 0;
}
return sign*val;
} else if(numberExp.exactMatch(expr.trimmed())) {
double value = QVariant(expr).toDouble(&noProblem);
return value;
}
noProblem = false;
return 0;
}
diff --git a/libs/widgetutils/kis_spin_box_unit_manager.cpp b/libs/widgetutils/kis_spin_box_unit_manager.cpp
new file mode 100644
index 0000000000..acae8ac490
--- /dev/null
+++ b/libs/widgetutils/kis_spin_box_unit_manager.cpp
@@ -0,0 +1,524 @@
+/*
+ * Copyright (c) 2017 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_spin_box_unit_manager.h"
+
+#include "KoUnit.h"
+#include <klocalizedstring.h>
+
+#include <QtMath>
+
+
+KisSpinBoxUnitManagerBuilder* KisSpinBoxUnitManagerFactory::builder = nullptr;
+
+KisSpinBoxUnitManager* KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(QObject* parent)
+{
+ if (builder == nullptr) {
+ return new KisSpinBoxUnitManager(parent);
+ }
+
+ return builder->buildUnitManager(parent);
+}
+
+void KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder)
+{
+ if (builder != nullptr) {
+ delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced.
+ }
+
+ builder = pBuilder;
+}
+
+void KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder()
+{
+ if (builder != nullptr) {
+ delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced.
+ }
+
+ builder = nullptr;
+}
+
+const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "px", "°", "frame"};
+
+const QStringList KisSpinBoxUnitManager::documentRelativeLengthUnitSymbols = {"px", "vw", "vh"}; //px are relative to the resolution, vw and vh to the width and height.
+const QStringList KisSpinBoxUnitManager::documentRelativeTimeUnitSymbols = {"s", "%"}; //secondes are relative to the framerate, % to the sequence length.
+
+class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private
+{
+public:
+ Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH,
+ QString pUnitSymbol = "pt",
+ double pConv = 1.0):
+ dim(pDim),
+ unitSymbol(pUnitSymbol),
+ conversionFactor(pConv),
+ conversionFactorIsFixed(true),
+ conversionConstant(0),
+ conversionConstantIsFixed(true),
+ constrains(0),
+ unitListCached(false),
+ hasHundredPercent(false),
+ canAccessDocument(false)
+ {
+
+ }
+
+ KisSpinBoxUnitManager::UnitDimension dim;
+
+ QString unitSymbol;
+ mutable double conversionFactor;
+ bool conversionFactorIsFixed; //tell if it's possible to trust the conversion factor stored or if it's needed to recompute it.
+ mutable double conversionConstant;
+ bool conversionConstantIsFixed; //tell if it's possible to trust the conversion constant stored or if it's needed to recompute it.
+
+ KisSpinBoxUnitManager::Constrains constrains;
+
+ mutable QStringList unitList;
+ mutable bool unitListCached;
+
+ mutable QStringList unitListWithName;
+ mutable bool unitListWithNameCached;
+
+ //it's possible to store a reference for the % unit, for lenght.
+ bool hasHundredPercent;
+ qreal hundredPercent;
+
+ bool canAccessDocument;
+};
+
+KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QAbstractListModel(parent)
+{
+ d = new Private();
+
+ connect(this, (void (KisSpinBoxUnitManager::*)( QString )) &KisSpinBoxUnitManager::unitChanged, this, &KisSpinBoxUnitManager::newUnitSymbolToUnitIndex);
+}
+KisSpinBoxUnitManager::~KisSpinBoxUnitManager()
+{
+ delete d;
+}
+
+int KisSpinBoxUnitManager::getUnitDimensionType() const
+{
+ return d->dim;
+}
+
+QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const
+{
+ return referenceUnitSymbols[d->dim];
+}
+
+QString KisSpinBoxUnitManager::getApparentUnitSymbol() const
+{
+ return d->unitSymbol;
+}
+
+int KisSpinBoxUnitManager::getApparentUnitId() const
+{
+ QStringList list = getsUnitSymbolList();
+ return list.indexOf(d->unitSymbol);
+}
+
+QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{
+
+ QStringList list;
+
+ if (withName) {
+ if (d->unitListWithNameCached) {
+ return d->unitListWithName;
+ }
+ } else {
+ if (d->unitListCached) {
+ return d->unitList;
+ }
+ }
+
+ switch (d->dim) {
+
+ case LENGTH:
+
+ for (int i = 0; i < KoUnit::TypeCount; i++) {
+
+ if (KoUnit::Type(i) == KoUnit::Pixel) {
+ continue; //skip pixel, which is a document relative unit, in the base classe.
+ }
+
+ if (withName) {
+ list << KoUnit::unitDescription(KoUnit::Type(i));
+ } else {
+ list << KoUnit(KoUnit::Type(i)).symbol();
+ }
+ }
+
+ if (d->canAccessDocument) {
+ // ad document relative units
+ if (withName) {
+ list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("view width (vw)") << i18n("view height (vh)");
+ } else {
+ list << documentRelativeLengthUnitSymbols;
+ }
+ }
+
+ break;
+
+ case IMLENGTH:
+
+ if (withName) {
+ list << KoUnit::unitDescription(KoUnit::Pixel);
+ } else {
+ list << "px";
+ }
+
+ if (d->canAccessDocument) {
+ // ad document relative units
+ if (withName) {
+ list << i18n("view width (vw)") << i18n("view height (vh)");
+ } else {
+ list << "vw" << "vh";
+ }
+ }
+ break;
+
+ case ANGLE:
+
+ if (withName) {
+ list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)");
+ } else {
+ list << "°" << "rad" << "gon" << "%";
+ }
+ break;
+
+ case TIME:
+
+ if (withName) {
+ list << i18n("frames (f)");
+ } else {
+ list << "f";
+ }
+
+ if (d->canAccessDocument) {
+ if (withName) {
+ list << i18n("seconds (s)") << i18n("percent of animation (%)");
+ } else {
+ list << documentRelativeTimeUnitSymbols;
+ }
+ }
+
+ break;
+
+ }
+
+ if (withName) {
+ d->unitListWithName = list;
+ d->unitListWithNameCached = true;
+ } else {
+ d->unitList = list;
+ d->unitListCached = true;
+ }
+
+ return list;
+
+}
+
+qreal KisSpinBoxUnitManager::getConversionConstant(int dim, QString symbol) const
+{
+ Q_UNUSED(dim);
+ Q_UNUSED(symbol);
+
+ return 0; // all units managed here are transform via a linear function, so this wll alway be 0 in this class.
+}
+
+qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const
+{
+ if (!d->conversionFactorIsFixed) {
+ recomputeConversionFactor();
+ }
+
+ if(!d->conversionConstantIsFixed) {
+ recomputeConvesrionConstant();
+ }
+
+ qreal v = (apparentValue - d->conversionConstant)/d->conversionFactor;
+
+ if (d->constrains &= REFISINT) {
+ v = qFloor(v);
+ }
+
+ return v;
+
+}
+
+int KisSpinBoxUnitManager::rowCount(const QModelIndex &parent) const {
+ if (parent == QModelIndex()) {
+ return getsUnitSymbolList().size();
+ }
+ return 0;
+}
+
+QVariant KisSpinBoxUnitManager::data(const QModelIndex &index, int role) const {
+
+ if (role == Qt::DisplayRole) {
+ return getsUnitSymbolList(false).at(index.row());
+ }
+
+ return QVariant();
+}
+
+qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const
+{
+ if (!d->conversionFactorIsFixed) {
+ recomputeConversionFactor();
+ }
+
+ if(!d->conversionConstantIsFixed) {
+ recomputeConvesrionConstant();
+ }
+
+ qreal v = refValue*d->conversionFactor + d->conversionConstant;
+
+ if (d->constrains &= VALISINT) {
+ v = qFloor(v);
+ }
+
+ return v;
+}
+
+qreal KisSpinBoxUnitManager::getConversionFactor(int dim, QString symbol) const
+{
+
+ qreal factor = -1;
+
+ switch (dim) {
+
+ case LENGTH:
+ do {
+ if (symbol == "px") {
+ break;
+ }
+
+ bool ok;
+ KoUnit unit = KoUnit::fromSymbol(symbol, &ok);
+ if (! ok) {
+ break;
+ }
+ factor = unit.toUserValue(1.0);
+ } while (0) ;
+ break;
+
+ case IMLENGTH:
+ if (symbol == "px") {
+ factor = 1;
+ }
+ break;
+
+ case ANGLE:
+ if (symbol == "°") {
+ factor = 1.0;
+ break;
+ }
+ if (symbol == "rad") {
+ factor = acos(-1)/90.0;
+ break;
+ }
+ if (symbol == "gon") {
+ factor = 10.0/9.0;
+ break;
+ }
+ if (symbol == "%") {
+ factor = 2.5/9.0; //(25% of circle is 90°)
+ break;
+ }
+ break;
+
+ case TIME:
+
+ if (symbol != "f") { //we have only frames for the moment.
+ break;
+ }
+ factor = 1.0;
+ break;
+
+ default:
+ break;
+ }
+
+ return factor;
+}
+
+
+void KisSpinBoxUnitManager::setUnitDimension(UnitDimension dimension)
+{
+ if (dimension == d->dim) {
+ return;
+ }
+
+ d->dim = dimension;
+ d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed.
+ d->conversionFactor = 1.0;
+
+ emit unitDimensionChanged(d->dim);
+
+}
+
+void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol)
+{
+
+ QString symbol = pSymbol.trimmed();
+
+ if (symbol == d->unitSymbol) {
+ return;
+ }
+
+ emit unitAboutToChange();
+
+ QString newSymb = "";
+
+ switch (d->dim) {
+
+ case ANGLE:
+ if (symbol.toLower() == "deg") {
+ newSymb = "°";
+ break;
+ }
+ goto default_indentifier; //alway do default after handling possible special cases.
+
+default_indentifier:
+ default:
+ QStringList list = getsUnitSymbolList();
+ if (list.contains(symbol, Qt::CaseInsensitive)) {
+ for (QString str : list) {
+ if (str.toLower() == symbol.toLower()) {
+ newSymb = str; //official symbol may contain capitals letters, so better take the official version.
+ break;
+ }
+ }
+ break;
+ }
+
+ }
+
+ if(newSymb.isEmpty()) {
+ return; //abort if it was impossible to locate the correct symbol.
+ }
+
+ if (d->canAccessDocument) {
+ //manage document relative units.
+
+ QStringList speUnits;
+
+ switch (d->dim) {
+
+ case LENGTH:
+ speUnits = documentRelativeLengthUnitSymbols;
+ goto default_identifier_conv_fact;
+
+ case IMLENGTH:
+ speUnits << "vw" << "vh";
+ goto default_identifier_conv_fact;
+
+ case TIME:
+ speUnits = documentRelativeTimeUnitSymbols;
+ goto default_identifier_conv_fact;
+
+default_identifier_conv_fact:
+ default:
+
+ if (speUnits.isEmpty()) {
+ d->conversionFactorIsFixed = true;
+ break;
+ }
+
+ if (speUnits.contains(newSymb)) {
+ d->conversionFactorIsFixed = false;
+ break;
+ }
+
+ d->conversionFactorIsFixed = true;
+ break;
+ }
+
+ if (d->dim == TIME) {
+ if (newSymb == "%") {
+ d->conversionConstantIsFixed = false;
+ }
+ } else {
+ d->conversionConstantIsFixed = true;
+ }
+
+ }
+
+ qreal conversFact = getConversionFactor(d->dim, newSymb);
+ qreal oldConversFact = d->conversionFactor;
+
+ d->conversionFactor = conversFact;
+ emit conversionFactorChanged(d->conversionFactor, oldConversFact);
+
+ d->unitSymbol = newSymb;
+ emit unitChanged(newSymb);
+
+}
+
+void KisSpinBoxUnitManager::selectApparentUnitFromIndex(int index) {
+
+ if (index >= 0 && index < rowCount()) {
+ setApparentUnitFromSymbol(getsUnitSymbolList().at(index));
+ }
+
+}
+
+void KisSpinBoxUnitManager::newUnitSymbolToUnitIndex(QString symbol) {
+ int id = getsUnitSymbolList().indexOf(symbol);
+
+ if (id >= 0) {
+ emit unitChanged(id);
+ }
+}
+
+void KisSpinBoxUnitManager::recomputeConversionFactor() const
+{
+ if (d->conversionFactorIsFixed) {
+ return;
+ }
+
+ qreal oldConversionFactor = d->conversionFactor;
+
+ d->conversionFactor = getConversionFactor(d->dim, d->unitSymbol);
+
+ if (oldConversionFactor != d->conversionFactor) {
+ emit conversionFactorChanged(d->conversionFactor, oldConversionFactor);
+ }
+}
+
+void KisSpinBoxUnitManager::recomputeConvesrionConstant() const
+{
+ if (d->conversionConstantIsFixed) {
+ return;
+ }
+
+ qreal oldConversionConstant = d->conversionConstant;
+
+ d->conversionConstant = getConversionConstant(d->dim, d->unitSymbol);
+
+ if (oldConversionConstant != d->conversionConstant) {
+ emit conversionConstantChanged(d->conversionConstant, oldConversionConstant);
+ }
+}
+
+void KisSpinBoxUnitManager::grantDocumentRelativeUnits()
+{
+ d->canAccessDocument = true;
+}
diff --git a/libs/widgetutils/kis_spin_box_unit_manager.h b/libs/widgetutils/kis_spin_box_unit_manager.h
new file mode 100644
index 0000000000..c0d44ffeb1
--- /dev/null
+++ b/libs/widgetutils/kis_spin_box_unit_manager.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#ifndef KISSPINBOXUNITMANAGER_H
+#define KISSPINBOXUNITMANAGER_H
+
+#include <QObject>
+#include <QStringList>
+#include <QAbstractListModel>
+
+#include "kritawidgetutils_export.h"
+
+class KisSpinBoxUnitManager;
+class KisSpinBoxUnitManagerBuilder;
+class KisSpinBoxUnitManagerFactory;
+
+/*!
+ * \brief The KisSpinBoxUnitManagerFactory class is a factory that is used to build a default KisSpinBoxUnitManager.
+ * \see KisSpinBoxUnitManagerBuilder
+ */
+class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManagerFactory
+{
+public:
+
+ static KisSpinBoxUnitManager* buildDefaultUnitManager(QObject* parent);
+ //! \brief set a builder the factory can use. The factory should take on the lifecycle of the builder, so to delete it call clearUnitManagerBuilder();
+ static void setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder);
+ static void clearUnitManagerBuilder();
+
+private:
+
+ static KisSpinBoxUnitManagerBuilder* builder;
+
+};
+
+/*!
+ * \brief The KisSpinBoxUnitManagerBuilder class is the base class, used in the strategy pattern of KisSpinBoxUnitManagerFactory.
+ * \see KisSpinBoxUnitManagerFactory.
+ */
+class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManagerBuilder
+{
+
+public:
+
+ virtual ~KisSpinBoxUnitManagerBuilder() {}
+
+ virtual KisSpinBoxUnitManager* buildUnitManager(QObject* parent) = 0; //this pure virtual function is used to build a unitmanager, it will be used by the unitManagerFactory.
+};
+
+/**
+ * @brief The KisSpinBoxUnitManager class is an abstract interface for the unitspinboxes classes to manage different type of units.
+ *
+ * The class make a difference between unit dimension (distance, angle, time).
+ *
+ * The class allow to convert values between reference unit and apparent unit, but also to get other informations like possible units symbols.
+ *
+ * This class don't allow to use relative units (units which conversion factor is dependant of the context), even if its private data are prepared to manage it.
+ * The reason for this is that from the library of this class it is very hard to acess easily the informations needed. So all will be managed by subclasses in other libs.
+ *
+ * The class is a subclass of QAbstractListModel, so that available list of units is easily acessed by other Qt standard components, like QComboBoxes.
+ *
+ */
+class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManager : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+
+ enum UnitDimension{
+ LENGTH = 0, //length, print size, reference is point
+ IMLENGTH = 1, //length, image size, reference is pixel. This dimension is used when the printing units must be avoided
+ ANGLE = 2,
+ TIME = 3
+ };
+
+ static inline bool isUnitId(int code) { return (code == LENGTH || code == ANGLE || code == TIME); }
+
+ //! \brief this list hold the symbols of the referenc unit per dimension. The index is equal to the value in UnitDimension so that the dimension name can be used to index the list.
+ static const QStringList referenceUnitSymbols;
+
+ enum Constrain{
+ NOCONSTR = 0,
+ REFISINT = 1,
+ VALISINT = 2
+
+ };
+
+ Q_DECLARE_FLAGS(Constrains, Constrain)
+
+ explicit KisSpinBoxUnitManager(QObject *parent = 0);
+ virtual ~KisSpinBoxUnitManager();
+
+ int getUnitDimensionType() const;
+ QString getReferenceUnitSymbol() const;
+ QString getApparentUnitSymbol() const;
+
+ //! \brief get the position of the apparent unit in the list of units. It is usefull if we want to build a model for combo-box based unit management.
+ int getApparentUnitId() const;
+
+ virtual QStringList getsUnitSymbolList(bool withName = false) const;
+
+ qreal getReferenceValue(double apparentValue) const;
+ qreal getApparentValue(double refValue) const;
+
+ //! \brief gets the conversion factor of a managed unit, or -1 in case of error. This method is the one that need to be overridden to extend the ability of the KisSpinBoxUnitManager.
+ virtual qreal getConversionFactor(int dim, QString symbol) const;
+ //! \brief some units conversions are done via an affine transform, not just a linear transform. This function gives the constant of this affine transform (usually 0).
+ virtual qreal getConversionConstant(int dim, QString symbol) const;
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+Q_SIGNALS:
+
+ void unitDimensionChanged(int dimCode);
+ void unitAboutToChange();
+ void unitChanged(QString symbol);
+ void unitChanged(int index);
+ void conversionFactorChanged(qreal newConversionFactor, qreal oldConversionFactor) const;
+ void conversionConstantChanged(qreal newConversionFactor, qreal oldConversionFactor) const;
+ void unitListChanged();
+
+public Q_SLOTS:
+
+ void setUnitDimension(UnitDimension dimension);
+ void setApparentUnitFromSymbol(QString pSymbol);
+ void selectApparentUnitFromIndex(int index);
+
+protected:
+
+ class Private;
+ Private * d;
+
+ //! \brief convert a unitChanged signal with a QString to one with an index.
+ void newUnitSymbolToUnitIndex(QString symbol);
+
+ //unit's that may be used only if acess to the document informations exists.
+ static const QStringList documentRelativeLengthUnitSymbols;
+ static const QStringList documentRelativeTimeUnitSymbols;
+
+ void recomputeConversionFactor() const;
+ void recomputeConvesrionConstant() const;
+
+ //! \brief calling this method give acess to document relative units. Only subclasses that manage thoses units should call it.
+ void grantDocumentRelativeUnits();
+
+};
+
+#endif // KISSPINBOXUNITMANAGER_H
diff --git a/libs/widgetutils/xmlgui/kxmlguifactory.cpp b/libs/widgetutils/xmlgui/kxmlguifactory.cpp
index 8ebdd89071..9bef875d0c 100644
--- a/libs/widgetutils/xmlgui/kxmlguifactory.cpp
+++ b/libs/widgetutils/xmlgui/kxmlguifactory.cpp
@@ -1,694 +1,695 @@
/* This file is part of the KDE libraries
Copyright (C) 1999,2000 Simon Hausmann <hausmann@kde.org>
Copyright (C) 2000 Kurt Granroth <granroth@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 "kxmlguifactory.h"
#include "config-xmlgui.h"
#include "kxmlguifactory_p.h"
#include "kxmlguiclient.h"
#include "kxmlguibuilder.h"
#include "KisShortcutsDialog.h"
#include "kactioncollection.h"
#include <QAction>
#include <QtCore/QDir>
#include <QtXml/QDomDocument>
#include <QtCore/QFile>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
#include <QWidget>
#include <QtCore/QDate>
#include <QtCore/QVariant>
#include <QTextCodec>
#include <QStandardPaths>
#include <QDebug>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <kis_icon_utils.h>
+#include <WidgetUtilsDebug.h>
Q_DECLARE_METATYPE(QList<QKeySequence>)
using namespace KXMLGUI;
class KXMLGUIFactoryPrivate : public BuildState
{
public:
enum ShortcutOption { SetActiveShortcut = 1, SetDefaultShortcut = 2};
KXMLGUIFactoryPrivate()
{
m_rootNode = new ContainerNode(0L, QString(), QString());
m_defaultMergingName = QStringLiteral("<default>");
tagActionList = QStringLiteral("actionlist");
attrName = QStringLiteral("name");
}
~KXMLGUIFactoryPrivate()
{
delete m_rootNode;
}
void pushState()
{
m_stateStack.push(*this);
}
void popState()
{
BuildState::operator=(m_stateStack.pop());
}
bool emptyState() const
{
return m_stateStack.isEmpty();
}
QWidget *findRecursive(KXMLGUI::ContainerNode *node, bool tag);
QList<QWidget *> findRecursive(KXMLGUI::ContainerNode *node, const QString &tagName);
void applyActionProperties(const QDomElement &element,
ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
void configureAction(QAction *action, const QDomNamedNodeMap &attributes,
ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
void configureAction(QAction *action, const QDomAttr &attribute,
ShortcutOption shortcutOption = KXMLGUIFactoryPrivate::SetActiveShortcut);
void refreshActionProperties(KXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc);
void saveDefaultActionProperties(const QList<QAction *> &actions);
ContainerNode *m_rootNode;
QString m_defaultMergingName;
/*
* Contains the container which is searched for in ::container .
*/
QString m_containerName;
/*
* List of all clients
*/
QList<KXMLGUIClient *> m_clients;
QString tagActionList;
QString attrName;
BuildStateStack m_stateStack;
};
QString KXMLGUIFactory::readConfigFile(const QString &filename, const QString &_componentName)
{
QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
QString xml_file;
if (!QDir::isRelativePath(filename)) {
xml_file = filename;
} else {
// KF >= 5.1 (KXMLGUI_INSTALL_DIR)
xml_file = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kxmlgui5/") + componentName + QLatin1Char('/') + filename);
if (!QFile::exists(xml_file)) {
// KF >= 5.4 (resource file)
xml_file = QStringLiteral(":/kxmlgui5/") + componentName + QLatin1Char('/') + filename;
}
bool warn = false;
if (!QFile::exists(xml_file)) {
// kdelibs4 / KF 5.0 solution
xml_file = QStandardPaths::locate(QStandardPaths::AppDataLocation, componentName + QLatin1Char('/') + filename);
warn = true;
}
if (!QFile::exists(xml_file)) {
// kdelibs4 / KF 5.0 solution, and the caller includes the component name
// This was broken (lead to component/component/ in kdehome) and unnecessary
// (they can specify it with setComponentName instead)
xml_file = QStandardPaths::locate(QStandardPaths::AppDataLocation, filename);
warn = true;
}
if (warn) {
qWarning() << "KXMLGUI file found at deprecated location" << xml_file << "-- please use ${KXMLGUI_INSTALL_DIR} to install these files instead.";
}
}
QFile file(xml_file);
if (xml_file.isEmpty() || !file.open(QIODevice::ReadOnly)) {
qCritical() << "No such XML file" << filename;
return QString();
}
QByteArray buffer(file.readAll());
return QString::fromUtf8(buffer.constData(), buffer.size());
}
bool KXMLGUIFactory::saveConfigFile(const QDomDocument &doc,
const QString &filename, const QString &_componentName)
{
QString componentName = _componentName.isEmpty() ? QCoreApplication::applicationName() : _componentName;
QString xml_file(filename);
if (QDir::isRelativePath(xml_file))
xml_file = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) +
QStringLiteral("/kxmlgui5/") + componentName + QLatin1Char('/') + filename;
QFileInfo fileInfo(xml_file);
QDir().mkpath(fileInfo.absolutePath());
QFile file(xml_file);
if (xml_file.isEmpty() || !file.open(QIODevice::WriteOnly)) {
qCritical() << "Could not write to" << filename;
return false;
}
// write out our document
QTextStream ts(&file);
ts.setCodec(QTextCodec::codecForName("UTF-8"));
ts << doc;
file.close();
return true;
}
/**
* Removes all QDomComment objects from the specified node and all its children.
*/
/*
static void removeDOMComments(QDomNode &node)
{
QDomNode n = node.firstChild();
while (!n.isNull()) {
if (n.nodeType() == QDomNode::CommentNode) {
QDomNode tmp = n;
n = n.nextSibling();
node.removeChild(tmp);
} else {
QDomNode tmp = n;
n = n.nextSibling();
removeDOMComments(tmp);
}
}
}*/
KXMLGUIFactory::KXMLGUIFactory(KXMLGUIBuilder *builder, QObject *parent)
: QObject(parent), d(new KXMLGUIFactoryPrivate)
{
d->builder = builder;
d->guiClient = 0;
if (d->builder) {
d->builderContainerTags = d->builder->containerTags();
d->builderCustomTags = d->builder->customTags();
}
}
KXMLGUIFactory::~KXMLGUIFactory()
{
Q_FOREACH (KXMLGUIClient *client, d->m_clients) {
client->setFactory(0L);
}
delete d;
}
void KXMLGUIFactory::addClient(KXMLGUIClient *client)
{
- //qDebug(260) << client;
+ debugWidgetUtils << client;
if (client->factory()) {
if (client->factory() == this) {
return;
} else {
client->factory()->removeClient(client); //just in case someone does stupid things ;-)
}
}
if (d->emptyState()) {
emit makingChanges(true);
}
d->pushState();
// QTime dt; dt.start();
d->guiClient = client;
// add this client to our client list
if (!d->m_clients.contains(client)) {
d->m_clients.append(client);
}
- //else
- //qDebug(260) << "XMLGUI client already added " << client;
-
+ else {
+ debugWidgetUtils << "XMLGUI client already added " << client;
+ }
// Tell the client that plugging in is process and
// let it know what builder widget its mainwindow shortcuts
// should be attached to.
client->beginXMLPlug(d->builder->widget());
// try to use the build document for building the client's GUI, as the build document
// contains the correct container state information (like toolbar positions, sizes, etc.) .
// if there is non available, then use the "real" document.
QDomDocument doc = client->xmlguiBuildDocument();
if (doc.documentElement().isNull()) {
doc = client->domDocument();
}
QDomElement docElement = doc.documentElement();
d->m_rootNode->index = -1;
// cache some variables
d->clientName = docElement.attribute(d->attrName);
d->clientBuilder = client->clientBuilder();
if (d->clientBuilder) {
d->clientBuilderContainerTags = d->clientBuilder->containerTags();
d->clientBuilderCustomTags = d->clientBuilder->customTags();
} else {
d->clientBuilderContainerTags.clear();
d->clientBuilderCustomTags.clear();
}
// load user-defined shortcuts and other action properties
d->saveDefaultActionProperties(client->actionCollection()->actions());
if (!doc.isNull()) {
d->refreshActionProperties(client, client->actionCollection()->actions(), doc);
}
BuildHelper(*d, d->m_rootNode).build(docElement);
// let the client know that we built its GUI.
client->setFactory(this);
// call the finalizeGUI method, to fix up the positions of toolbars for example.
// ### FIXME : obey client builder
// --- Well, toolbars have a bool "positioned", so it doesn't really matter,
// if we call positionYourself on all of them each time. (David)
d->builder->finalizeGUI(d->guiClient);
// reset some variables, for safety
d->BuildState::reset();
client->endXMLPlug();
d->popState();
emit clientAdded(client);
// build child clients
Q_FOREACH (KXMLGUIClient *child, client->childClients()) {
addClient(child);
}
if (d->emptyState()) {
emit makingChanges(false);
}
/*
QString unaddedActions;
Q_FOREACH (KActionCollection* ac, KActionCollection::allCollections())
Q_FOREACH (QAction* action, ac->actions())
if (action->associatedWidgets().isEmpty())
unaddedActions += action->objectName() + ' ';
if (!unaddedActions.isEmpty())
qWarning() << "The following actions are not plugged into the gui (shortcuts will not work): " << unaddedActions;
*/
// qDebug() << "addClient took " << dt.elapsed();
}
// Find the right ActionProperties element, otherwise return null element
static QDomElement findActionPropertiesElement(const QDomDocument &doc)
{
const QLatin1String tagActionProp("ActionProperties");
QDomElement e = doc.documentElement().firstChildElement();
for (; !e.isNull(); e = e.nextSiblingElement()) {
if (QString::compare(e.tagName(), tagActionProp, Qt::CaseInsensitive) == 0) {
return e;
}
}
return QDomElement();
}
void KXMLGUIFactoryPrivate::refreshActionProperties(KXMLGUIClient *client, const QList<QAction *> &actions, const QDomDocument &doc)
{
// These were used for applyShortcutScheme() but not for applyActionProperties()??
Q_UNUSED(client);
Q_UNUSED(actions);
// try to find and apply user-defined shortcuts
const QDomElement actionPropElement = findActionPropertiesElement(doc);
if (!actionPropElement.isNull()) {
applyActionProperties(actionPropElement);
}
}
void KXMLGUIFactoryPrivate::saveDefaultActionProperties(const QList<QAction *> &actions)
{
// This method is called every time the user activated a new
// kxmlguiclient. We only want to execute the following code only once in
// the lifetime of an action.
Q_FOREACH (QAction *action, actions) {
// Skip 0 actions or those we have seen already.
if (!action || action->property("_k_DefaultShortcut").isValid()) {
continue;
}
// Check if the default shortcut is set
QList<QKeySequence> defaultShortcut = action->property("defaultShortcuts").value<QList<QKeySequence> >();
QList<QKeySequence> activeShortcut = action->shortcuts();
//qDebug() << action->objectName() << "default=" << defaultShortcut.toString() << "active=" << activeShortcut.toString();
// Check if we have an empty default shortcut and an non empty
// custom shortcut. Print out a warning and correct the mistake.
if ((!activeShortcut.isEmpty()) && defaultShortcut.isEmpty()) {
qCritical() << "Shortcut for action " << action->objectName() << action->text() << "set with QAction::setShortcut()! Use KActionCollection::setDefaultShortcut(s) instead.";
action->setProperty("_k_DefaultShortcut", QVariant::fromValue(activeShortcut));
} else {
action->setProperty("_k_DefaultShortcut", QVariant::fromValue(defaultShortcut));
}
}
}
void KXMLGUIFactory::forgetClient(KXMLGUIClient *client)
{
d->m_clients.removeAll(client);
}
void KXMLGUIFactory::removeClient(KXMLGUIClient *client)
{
//qDebug(260) << client;
// don't try to remove the client's GUI if we didn't build it
if (!client || client->factory() != this) {
return;
}
if (d->emptyState()) {
emit makingChanges(true);
}
// remove this client from our client list
d->m_clients.removeAll(client);
// remove child clients first (create a copy of the list just in case the
// original list is modified directly or indirectly in removeClient())
const QList<KXMLGUIClient *> childClients(client->childClients());
Q_FOREACH (KXMLGUIClient *child, childClients) {
removeClient(child);
}
//qDebug(260) << "calling removeRecursive";
d->pushState();
// cache some variables
d->guiClient = client;
d->clientName = client->domDocument().documentElement().attribute(d->attrName);
d->clientBuilder = client->clientBuilder();
client->setFactory(0L);
// if we don't have a build document for that client, yet, then create one by
// cloning the original document, so that saving container information in the
// DOM tree does not touch the original document.
QDomDocument doc = client->xmlguiBuildDocument();
if (doc.documentElement().isNull()) {
doc = client->domDocument().cloneNode(true).toDocument();
client->setXMLGUIBuildDocument(doc);
}
d->m_rootNode->destruct(doc.documentElement(), *d);
// reset some variables
d->BuildState::reset();
// This will destruct the KAccel object built around the given widget.
client->prepareXMLUnplug(d->builder->widget());
d->popState();
if (d->emptyState()) {
emit makingChanges(false);
}
emit clientRemoved(client);
}
QList<KXMLGUIClient *> KXMLGUIFactory::clients() const
{
return d->m_clients;
}
QWidget *KXMLGUIFactory::container(const QString &containerName, KXMLGUIClient *client,
bool useTagName)
{
d->pushState();
d->m_containerName = containerName;
d->guiClient = client;
QWidget *result = d->findRecursive(d->m_rootNode, useTagName);
d->guiClient = 0L;
d->m_containerName.clear();
d->popState();
return result;
}
QList<QWidget *> KXMLGUIFactory::containers(const QString &tagName)
{
return d->findRecursive(d->m_rootNode, tagName);
}
void KXMLGUIFactory::reset()
{
d->m_rootNode->reset();
d->m_rootNode->clearChildren();
}
void KXMLGUIFactory::resetContainer(const QString &containerName, bool useTagName)
{
if (containerName.isEmpty()) {
return;
}
ContainerNode *container = d->m_rootNode->findContainer(containerName, useTagName);
if (!container) {
return;
}
ContainerNode *parent = container->parent;
if (!parent) {
return;
}
// resetInternal( container );
parent->removeChild(container);
}
QWidget *KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node, bool tag)
{
if (((!tag && node->name == m_containerName) ||
(tag && node->tagName == m_containerName)) &&
(!guiClient || node->client == guiClient)) {
return node->container;
}
Q_FOREACH (ContainerNode *child, node->children) {
QWidget *cont = findRecursive(child, tag);
if (cont) {
return cont;
}
}
return 0L;
}
// Case insensitive equality without calling toLower which allocates a new string
static inline bool equals(const QString &str1, const char *str2)
{
return str1.compare(QLatin1String(str2), Qt::CaseInsensitive) == 0;
}
static inline bool equals(const QString &str1, const QString &str2)
{
return str1.compare(str2, Qt::CaseInsensitive) == 0;
}
QList<QWidget *> KXMLGUIFactoryPrivate::findRecursive(KXMLGUI::ContainerNode *node,
const QString &tagName)
{
QList<QWidget *> res;
if (equals(node->tagName, tagName)) {
res.append(node->container);
}
Q_FOREACH (KXMLGUI::ContainerNode *child, node->children) {
res << findRecursive(child, tagName);
}
return res;
}
void KXMLGUIFactory::plugActionList(KXMLGUIClient *client, const QString &name,
const QList<QAction *> &actionList)
{
d->pushState();
d->guiClient = client;
d->actionListName = name;
d->actionList = actionList;
d->clientName = client->domDocument().documentElement().attribute(d->attrName);
d->m_rootNode->plugActionList(*d);
// Load shortcuts for these new actions
d->saveDefaultActionProperties(actionList);
d->refreshActionProperties(client, actionList, client->domDocument());
d->BuildState::reset();
d->popState();
}
void KXMLGUIFactory::unplugActionList(KXMLGUIClient *client, const QString &name)
{
d->pushState();
d->guiClient = client;
d->actionListName = name;
d->clientName = client->domDocument().documentElement().attribute(d->attrName);
d->m_rootNode->unplugActionList(*d);
d->BuildState::reset();
d->popState();
}
void KXMLGUIFactoryPrivate::applyActionProperties(const QDomElement &actionPropElement,
ShortcutOption shortcutOption)
{
for (QDomElement e = actionPropElement.firstChildElement();
!e.isNull(); e = e.nextSiblingElement()) {
if (!equals(e.tagName(), "action")) {
continue;
}
QAction *action = guiClient->action(e);
if (!action) {
continue;
}
configureAction(action, e.attributes(), shortcutOption);
}
}
void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomNamedNodeMap &attributes,
ShortcutOption shortcutOption)
{
for (int i = 0; i < attributes.length(); i++) {
QDomAttr attr = attributes.item(i).toAttr();
if (attr.isNull()) {
continue;
}
configureAction(action, attr, shortcutOption);
}
}
void KXMLGUIFactoryPrivate::configureAction(QAction *action, const QDomAttr &attribute,
ShortcutOption shortcutOption)
{
QString attrName = attribute.name();
// If the attribute is a deprecated "accel", change to "shortcut".
if (equals(attrName, "accel")) {
attrName = QStringLiteral("shortcut");
}
// No need to re-set name, particularly since it's "objectName" in Qt4
if (equals(attrName, "name")) {
return;
}
if (equals(attrName, "icon")) {
action->setIcon(KisIconUtils::loadIcon(attribute.value()));
return;
}
QVariant propertyValue;
QVariant::Type propertyType = action->property(attrName.toLatin1().constData()).type();
bool isShortcut = (propertyType == QVariant::KeySequence);
if (propertyType == QVariant::Int) {
propertyValue = QVariant(attribute.value().toInt());
} else if (propertyType == QVariant::UInt) {
propertyValue = QVariant(attribute.value().toUInt());
} else if (isShortcut) {
// Setting the shortcut by property also sets the default shortcut
// (which is incorrect), so we have to do it directly
action->setShortcuts(QKeySequence::listFromString(attribute.value()));
if (shortcutOption & KXMLGUIFactoryPrivate::SetDefaultShortcut) {
action->setProperty("defaultShortcuts",
QVariant::fromValue(QKeySequence::listFromString(attribute.value())));
}
} else {
propertyValue = QVariant(attribute.value());
}
if (!isShortcut && !action->setProperty(attrName.toLatin1().constData(), propertyValue)) {
qWarning() << "Error: Unknown action property " << attrName << " will be ignored!";
}
}
// Find or create
QDomElement KXMLGUIFactory::actionPropertiesElement(QDomDocument &doc)
{
// first, lets see if we have existing properties
QDomElement elem = findActionPropertiesElement(doc);
// if there was none, create one
if (elem.isNull()) {
elem = doc.createElement(QStringLiteral("ActionProperties"));
doc.documentElement().appendChild(elem);
}
return elem;
}
QDomElement KXMLGUIFactory::findActionByName(QDomElement &elem, const QString &sName, bool create)
{
const QLatin1String attrName("name");
for (QDomNode it = elem.firstChild(); !it.isNull(); it = it.nextSibling()) {
QDomElement e = it.toElement();
if (e.attribute(attrName) == sName) {
return e;
}
}
if (create) {
QDomElement act_elem = elem.ownerDocument().createElement(QStringLiteral("Action"));
act_elem.setAttribute(attrName, sName);
elem.appendChild(act_elem);
return act_elem;
}
return QDomElement();
}
diff --git a/packaging/linux/appimage/build-deps.sh b/packaging/linux/appimage/build-deps.sh
index a106d5ed9b..4e4cc28bd7 100644
--- a/packaging/linux/appimage/build-deps.sh
+++ b/packaging/linux/appimage/build-deps.sh
@@ -1,123 +1,122 @@
#!/bin/bash
# Enter a CentOS 6 chroot (you could use other methods)
# git clone https://github.com/probonopd/AppImageKit.git
# ./AppImageKit/build.sh
# sudo ./AppImageKit/AppImageAssistant.AppDir/testappimage /isodevice/boot/iso/CentOS-6.5-x86_64-LiveCD.iso bash
# Halt on errors
set -e
# Be verbose
set -x
# Now we are inside CentOS 6
grep -r "CentOS release 6" /etc/redhat-release || exit 1
# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. That's
# not always set correctly in CentOS 6.7
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# Determine which architecture should be built
if [[ "$(arch)" = "i686" || "$(arch)" = "x86_64" ]] ; then
ARCH=$(arch)
else
echo "Architecture could not be determined"
exit 1
fi
# if the library path doesn't point to our usr/lib, linking will be broken and we won't find all deps either
export LD_LIBRARY_PATH=/usr/lib64/:/usr/lib:/krita.appdir/usr/lib
git_pull_rebase_helper()
{
git reset --hard HEAD
git pull
}
yum -y install epel-release
# we need to be up to date in order to install the xcb-keysyms dependency
yum -y update
# base dependencies and Qt5.
-yum -y install wget tar bzip2 git libtool which fuse fuse-devel libpng-devel automake libtool mesa-libEGL cppunit-devel cmake3 glibc-headers libstdc++-devel gcc-c++ freetype-devel fontconfig-devel libxml2-devel libstdc++-devel libXrender-devel patch xcb-util-keysyms-devel libXi-devel mesa-libGL-devel mesa-libGLU-devel libxcb libxcb-devel xcb-util xcb-util-devel glibc-devel xkeyboard-config
+yum -y install wget tar bzip2 git libtool which fuse fuse-devel libpng-devel automake libtool mesa-libEGL cppunit-devel cmake3 glibc-headers libstdc++-devel gcc-c++ freetype-devel fontconfig-devel libxml2-devel libstdc++-devel libXrender-devel patch xcb-util-keysyms-devel libXi-devel mesa-libGL-devel libxcb libxcb-devel xcb-util xcb-util-devel
# Newer compiler than what comes with CentOS 6
yum -y install centos-release-scl-rh
yum -y install devtoolset-3-gcc devtoolset-3-gcc-c++
. /opt/rh/devtoolset-3/enable
# Make sure we build from the /, parts of this script depends on that. We also need to run as root...
cd /
# Build AppImageKit
if [ ! -d AppImageKit ] ; then
git clone --depth 1 https://github.com/probonopd/AppImageKit.git /AppImageKit
fi
cd /AppImageKit/
git_pull_rebase_helper
-git checkout stable/v1.0
./build.sh
cd /
# Workaround for: On CentOS 6, .pc files in /usr/lib/pkgconfig are not recognized
# However, this is where .pc files get installed when bulding libraries... (FIXME)
# I found this by comparing the output of librevenge's "make install" command
# between Ubuntu and CentOS 6
ln -sf /usr/share/pkgconfig /usr/lib/pkgconfig
# A krita build layout looks like this:
# krita/ -- the source directory
# krita/3rdparty -- the cmake3 definitions for the dependencies
# d -- downloads of the dependencies from files.kde.org
# b -- build directory for the dependencies
# krita_build -- build directory for krita itself
# krita.appdir -- install directory for krita and the dependencies
# Get Krita
if [ ! -d /krita ] ; then
git clone --depth 1 https://github.com/KDE/krita.git /krita
fi
cd /krita/
git_pull_rebase_helper
# Create the build dir for the 3rdparty deps
if [ ! -d /b ] ; then
mkdir /b
fi
if [ ! -d /d ] ; then
mkdir /d
fi
# start building the deps
cd /b
rm -rf /b/* || true
cmake3 /krita/3rdparty \
-DCMAKE_INSTALL_PREFIX:PATH=/usr \
-DINSTALL_ROOT=/usr \
-DEXTERNALS_DOWNLOAD_DIR=/d
cmake3 --build . --config RelWithDebInfo --target ext_qt
-cmake3 --build . --config RelWithDebInfo --target ext_boost
-cmake3 --build . --config RelWithDebInfo --target ext_eigen3
-cmake3 --build . --config RelWithDebInfo --target ext_exiv2
-cmake3 --build . --config RelWithDebInfo --target ext_fftw3
-cmake3 --build . --config RelWithDebInfo --target ext_lcms2
-cmake3 --build . --config RelWithDebInfo --target ext_ocio
-cmake3 --build . --config RelWithDebInfo --target ext_openexr
-cmake3 --build . --config RelWithDebInfo --target ext_vc
+#cmake3 --build . --config RelWithDebInfo --target ext_boost
+#cmake3 --build . --config RelWithDebInfo --target ext_eigen3
+#cmake3 --build . --config RelWithDebInfo --target ext_exiv2
+#cmake3 --build . --config RelWithDebInfo --target ext_fftw3
+#cmake3 --build . --config RelWithDebInfo --target ext_lcms2
+#cmake3 --build . --config RelWithDebInfo --target ext_ocio
+#cmake3 --build . --config RelWithDebInfo --target ext_openexr
+#cmake3 --build . --config RelWithDebInfo --target ext_vc
#cmake3 --build . --config RelWithDebInfo --target ext_png
-cmake3 --build . --config RelWithDebInfo --target ext_tiff
-cmake3 --build . --config RelWithDebInfo --target ext_jpeg
-cmake3 --build . --config RelWithDebInfo --target ext_libraw
-cmake3 --build . --config RelWithDebInfo --target ext_kcrash
-cmake3 --build . --config RelWithDebInfo --target ext_poppler
-cmake3 --build . --config RelWithDebInfo --target ext_gsl
+#cmake3 --build . --config RelWithDebInfo --target ext_tiff
+#cmake3 --build . --config RelWithDebInfo --target ext_jpeg
+#cmake3 --build . --config RelWithDebInfo --target ext_libraw
+#cmake3 --build . --config RelWithDebInfo --target ext_kcrash
+#cmake3 --build . --config RelWithDebInfo --target ext_poppler
+#cmake3 --build . --config RelWithDebInfo --target ext_gsl
diff --git a/packaging/linux/appimage/build-krita.sh b/packaging/linux/appimage/build-krita.sh
index 00a79f3913..fbde58da0e 100644
--- a/packaging/linux/appimage/build-krita.sh
+++ b/packaging/linux/appimage/build-krita.sh
@@ -1,87 +1,88 @@
#!/bin/bash
# Enter a CentOS 6 chroot (you could use other methods)
# git clone https://github.com/probonopd/AppImageKit.git
# ./AppImageKit/build.sh
# sudo ./AppImageKit/AppImageAssistant.AppDir/testappimage /isodevice/boot/iso/CentOS-6.5-x86_64-LiveCD.iso bash
# Halt on errors
set -e
# Be verbose
set -x
# Now we are inside CentOS 6
grep -r "CentOS release 6" /etc/redhat-release || exit 1
# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. That's
# not always set correctly in CentOS 6.7
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# Determine which architecture should be built
if [[ "$(arch)" = "i686" || "$(arch)" = "x86_64" ]] ; then
ARCH=$(arch)
else
echo "Architecture could not be determined"
exit 1
fi
# if the library path doesn't point to our usr/lib, linking will be broken and we won't find all deps either
export LD_LIBRARY_PATH=/usr/lib64/:/usr/lib:/krita.appdir/usr/lib
git_pull_rebase_helper()
{
git reset --hard HEAD
git pull
}
# Use the new compiler
. /opt/rh/devtoolset-3/enable
# Workaround for: On CentOS 6, .pc files in /usr/lib/pkgconfig are not recognized
# However, this is where .pc files get installed when bulding libraries... (FIXME)
# I found this by comparing the output of librevenge's "make install" command
# between Ubuntu and CentOS 6
ln -sf /usr/share/pkgconfig /usr/lib/pkgconfig
# A krita build layout looks like this:
# krita/ -- the source directory
# krita/3rdparty -- the cmake3 definitions for the dependencies
# d -- downloads of the dependencies from files.kde.org
# b -- build directory for the dependencies
# krita_build -- build directory for krita itself
# krita.appdir -- install directory for krita and the dependencies
# Get Krita
if [ ! -d /krita ] ; then
git clone --depth 1 https://github.com/KDE/krita.git /krita
fi
cd /krita/
git_pull_rebase_helper
cd /
# If the environment variable DO_NOT_BUILD_KRITA is set to something,
# then stop here. This is for docker hub which has a timeout that
# prevents us from building in one go.
# if [ ! -z "$DO_NOT_BUILD_KRITA" ] ; then
# exit 0
# fi
mkdir -p /krita_build
cd /krita_build
cmake3 ../krita \
-DCMAKE_INSTALL_PREFIX:PATH=/krita.appdir/usr \
-DDEFINE_NO_DEPRECATED=1 \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DPACKAGERS_BUILD=1 \
-DBUILD_TESTING=FALSE \
-DKDE4_BUILD_TESTS=FALSE \
-DHAVE_MEMORY_LEAK_TRACKER=FALSE
# build
make -j4
diff --git a/packaging/linux/appimage/build-release.sh b/packaging/linux/appimage/build-release.sh
index 1bf69d31b6..a8e8b8e159 100644
--- a/packaging/linux/appimage/build-release.sh
+++ b/packaging/linux/appimage/build-release.sh
@@ -1,258 +1,250 @@
#!/bin/bash
-RELEASE=krita-3.0.99.90
+RELEASE=3.1.2.0
# Enter a CentOS 6 chroot (you could use other methods)
# git clone https://github.com/probonopd/AppImageKit.git
# ./AppImageKit/build.sh
# sudo ./AppImageKit/AppImageAssistant.AppDir/testappimage /isodevice/boot/iso/CentOS-6.5-x86_64-LiveCD.iso bash
# Halt on errors
set -e
# Be verbose
set -x
# Now we are inside CentOS 6
grep -r "CentOS release 6" /etc/redhat-release || exit 1
# If we are running inside Travis CI, then we want to build Krita
# and we remove the $DO_NOT_BUILD_KRITA environment variable
# that was used in the process of generating the Docker image.
# Also we do not want to download and build the dependencies every
# time we build Krita on travis in the docker image. In order to
# use newer dependencies, we re-build the docker image instead.
#unset DO_NOT_BUILD_KRITA
#NO_DOWNLOAD=1
# clean up
rm -rf /out/*
rm -rf /krita.appdir
+rm -rf /krita_build
+mkdir /krita_build
# qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. That's
# not always set correctly in CentOS 6.7
export LC_ALL=en_US.UTF-8
export LANG=en_us.UTF-8
# Determine which architecture should be built
if [[ "$(arch)" = "i686" || "$(arch)" = "x86_64" ]] ; then
ARCH=$(arch)
else
echo "Architecture could not be determined"
exit 1
fi
# if the library path doesn't point to our usr/lib, linking will be broken and we won't find all deps either
export LD_LIBRARY_PATH=/usr/lib64/:/usr/lib:/krita.appdir/usr/lib
cd /
# Prepare the install location
rm -rf /krita.appdir/ || true
mkdir -p /krita.appdir/usr/bin
# make sure lib and lib64 are the same thing
mkdir -p /krita.appdir/usr/lib
cd /krita.appdir/usr
ln -s lib lib64
# Use the new compiler
. /opt/rh/devtoolset-3/enable
# fetch and build krita
cd /
-wget http://files.kde.org/krita/3/source/$RELEASE.tar.xz
-tar -xf $RELEASE.tar.xz
+#wget http://files.kde.org/krita/krita-$RELEASE.tar.gz
+#wget http://www.valdyas.org/~boud/krita-$RELEASE.tar.gz
+wget http://download.kde.org/unstable/krita/$RELEASE/krita-$RELEASE.tar.gz
+tar -xf krita-$RELEASE.tar.gz
cd /krita_build
-rm -rf *
-cmake3 ../$RELEASE \
+cmake3 ../krita-$RELEASE \
-DCMAKE_INSTALL_PREFIX:PATH=/krita.appdir/usr \
-DDEFINE_NO_DEPRECATED=1 \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DPACKAGERS_BUILD=1 \
-DBUILD_TESTING=FALSE \
-DKDE4_BUILD_TESTS=FALSE \
-DHAVE_MEMORY_LEAK_TRACKER=FALSE
make -j4 install
cd /krita.appdir
# FIXME: How to find out which subset of plugins is really needed? I used strace when running the binary
cp -r /usr/plugins ./usr/bin/
# copy the Qt translation
cp -r /usr/translations ./usr
cp $(ldconfig -p | grep libsasl2.so.2 | cut -d ">" -f 2 | xargs) ./usr/lib/
cp $(ldconfig -p | grep libGL.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # otherwise segfaults!?
cp $(ldconfig -p | grep libGLU.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # otherwise segfaults!?
# Fedora 23 seemed to be missing SOMETHING from the Centos 6.7. The only message was:
# This application failed to start because it could not find or load the Qt platform plugin "xcb".
# Setting export QT_DEBUG_PLUGINS=1 revealed the cause.
# QLibraryPrivate::loadPlugin failed on "/usr/lib64/qt5/plugins/platforms/libqxcb.so" :
# "Cannot load library /usr/lib64/qt5/plugins/platforms/libqxcb.so: (/lib64/libEGL.so.1: undefined symbol: drmGetNodeTypeFromFd)"
# Which means that we have to copy libEGL.so.1 in too
cp $(ldconfig -p | grep libEGL.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # Otherwise F23 cannot load the Qt platform plugin "xcb"
# let's not copy xcb itself, that breaks on dri3 systems https://bugs.kde.org/show_bug.cgi?id=360552
#cp $(ldconfig -p | grep libxcb.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/
cp $(ldconfig -p | grep libfreetype.so.6 | cut -d ">" -f 2 | xargs) ./usr/lib/ # For Fedora 20
ldd usr/bin/krita | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true
#ldd usr/lib64/krita/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true
#ldd usr/lib64/plugins/imageformats/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true
ldd usr/bin/plugins/platforms/libqxcb.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true
# Copy in the indirect dependencies
FILES=$(find . -type f -executable)
for FILE in $FILES ; do
ldd "${FILE}" | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' usr/lib || true
done
#DEPS=""
#for FILE in $FILES ; do
# ldd "${FILE}" | grep "=>" | awk '{print $3}' | xargs -I '{}' echo '{}' > DEPSFILE
#done
#DEPS=$(cat DEPSFILE |sort | uniq)
#for FILE in $DEPS ; do
# if [ -f $FILE ] ; then
# echo $FILE
# cp --parents -rfL $FILE ./
# fi
#done
#rm -f DEPSFILE
# The following are assumed to be part of the base system
rm -f usr/lib/libcom_err.so.2 || true
rm -f usr/lib/libcrypt.so.1 || true
rm -f usr/lib/libdl.so.2 || true
rm -f usr/lib/libexpat.so.1 || true
#rm -f usr/lib/libfontconfig.so.1 || true
rm -f usr/lib/libgcc_s.so.1 || true
rm -f usr/lib/libglib-2.0.so.0 || true
rm -f usr/lib/libgpg-error.so.0 || true
rm -f usr/lib/libgssapi_krb5.so.2 || true
rm -f usr/lib/libgssapi.so.3 || true
rm -f usr/lib/libhcrypto.so.4 || true
rm -f usr/lib/libheimbase.so.1 || true
rm -f usr/lib/libheimntlm.so.0 || true
rm -f usr/lib/libhx509.so.5 || true
rm -f usr/lib/libICE.so.6 || true
rm -f usr/lib/libidn.so.11 || true
rm -f usr/lib/libk5crypto.so.3 || true
rm -f usr/lib/libkeyutils.so.1 || true
rm -f usr/lib/libkrb5.so.26 || true
rm -f usr/lib/libkrb5.so.3 || true
rm -f usr/lib/libkrb5support.so.0 || true
# rm -f usr/lib/liblber-2.4.so.2 || true # needed for debian wheezy
# rm -f usr/lib/libldap_r-2.4.so.2 || true # needed for debian wheezy
rm -f usr/lib/libm.so.6 || true
rm -f usr/lib/libp11-kit.so.0 || true
rm -f usr/lib/libpcre.so.3 || true
rm -f usr/lib/libpthread.so.0 || true
rm -f usr/lib/libresolv.so.2 || true
rm -f usr/lib/libroken.so.18 || true
rm -f usr/lib/librt.so.1 || true
rm -f usr/lib/libsasl2.so.2 || true
rm -f usr/lib/libSM.so.6 || true
rm -f usr/lib/libusb-1.0.so.0 || true
rm -f usr/lib/libuuid.so.1 || true
rm -f usr/lib/libwind.so.0 || true
rm -f usr/lib/libfontconfig.so.* || true
# Remove these libraries, we need to use the system versions; this means 11.04 is not supported (12.04 is our baseline)
rm -f usr/lib/libGL.so.* || true
rm -f usr/lib/libdrm.so.* || true
rm -f usr/lib/libX11.so.* || true
#rm -f usr/lib/libz.so.1 || true
# These seem to be available on most systems but not Ubuntu 11.04
# rm -f usr/lib/libffi.so.6 usr/lib/libGL.so.1 usr/lib/libglapi.so.0 usr/lib/libxcb.so.1 usr/lib/libxcb-glx.so.0 || true
# Delete potentially dangerous libraries
rm -f usr/lib/libstdc* usr/lib/libgobject* usr/lib/libc.so.* || true
rm -f usr/lib/libxcb.so.1
# Do NOT delete libX* because otherwise on Ubuntu 11.04:
# loaded library "Xcursor" malloc.c:3096: sYSMALLOc: Assertion (...) Aborted
# We don't bundle the developer stuff
rm -rf usr/include || true
rm -rf usr/lib/cmake3 || true
rm -rf usr/lib/pkgconfig || true
rm -rf usr/share/ECM/ || true
rm -rf usr/share/gettext || true
rm -rf usr/share/pkgconfig || true
+mv usr/share/locale usr/share/krita || true
+
strip usr/lib/kritaplugins/* usr/bin/* usr/lib/* || true
# Since we set /krita.appdir as the prefix, we need to patch it away too (FIXME)
# Probably it would be better to use /app as a prefix because it has the same length for all apps
cd usr/ ; find . -type f -exec sed -i -e 's|/krita.appdir/usr/|./././././././././|g' {} \; ; cd ..
# On openSUSE Qt is picking up the wrong libqxcb.so
# (the one from the system when in fact it should use the bundled one) - is this a Qt bug?
# Also, Krita has a hardcoded /usr which we patch away
cd usr/ ; find . -type f -exec sed -i -e 's|/usr|././|g' {} \; ; cd ..
# We do not bundle this, so let's not search that inside the AppImage.
# Fixes "Qt: Failed to create XKB context!" and lets us enter text
sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/bin/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so
sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/lib/libQt5XcbQpa.so.5
# Workaround for:
# D-Bus library appears to be incorrectly set up;
# failed to read machine uuid: Failed to open
# The file is more commonly in /etc/machine-id
# sed -i -e 's|/var/lib/dbus/machine-id|//././././etc/machine-id|g' ./usr/lib/libdbus-1.so.3
# or
rm -f ./usr/lib/libdbus-1.so.3 || true
cp ../AppImageKit/AppRun .
cp ./usr/share/applications/org.kde.krita.desktop krita.desktop
cp /krita/krita/pics/app/64-apps-calligrakrita.png calligrakrita.png
# replace krita with the lib-checking startup script.
#cd /krita.appdir/usr/bin
#mv krita krita.real
#wget https://raw.githubusercontent.com/boudewijnrempt/AppImages/master/recipes/krita/krita
#chmod a+rx krita
cd /
APP=krita
-# Source functions
-wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh
-. ./functions.sh
-
-# Install desktopintegration in usr/bin/krita.wrapper -- feel free to edit it
-cd /krita.appdir
-get_desktopintegration krita
-
cd /
VER=$(grep "#define KRITA_VERSION_STRING" krita_build/libs/version/kritaversion.h | cut -d '"' -f 2)
VERSION=$VER
VERSION="$(sed s/\ /-/g <<<$VERSION)"
echo $VERSION
if [[ "$ARCH" = "x86_64" ]] ; then
APPIMAGE=$APP"-"$VERSION"-x86_64.appimage"
fi
if [[ "$ARCH" = "i686" ]] ; then
APPIMAGE=$APP"-"$VERSION"-i386.appimage"
fi
echo $APPIMAGE
mkdir -p /out
rm -f /out/*.AppImage || true
AppImageKit/AppImageAssistant.AppDir/package /krita.appdir/ /out/$APPIMAGE
chmod a+rwx /out/$APPIMAGE # So that we can edit the AppImage outside of the Docker container
-
-cd /krita.appdir
-mv AppRun krita
-cd /
-mv krita.appdir $APP"-"$VERSION"-x86_64
-tar -czf $APP"-"$VERSION"-x86_64.tgz $APP"-"$VERSION"-x86_64
diff --git a/plugins/color/lcms2engine/IccColorSpaceEngine.cpp b/plugins/color/lcms2engine/IccColorSpaceEngine.cpp
index e79770ec4d..336c78249f 100644
--- a/plugins/color/lcms2engine/IccColorSpaceEngine.cpp
+++ b/plugins/color/lcms2engine/IccColorSpaceEngine.cpp
@@ -1,317 +1,336 @@
/*
* Copyright (c) 2007-2008 Cyrille Berger <cberger@cberger.net>
* Copyright (c) 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "IccColorSpaceEngine.h"
#include "KoColorModelStandardIds.h"
#include <klocalizedstring.h>
#include "LcmsColorSpace.h"
-#include <QDebug>
-
// -- KoLcmsColorConversionTransformation --
class KoLcmsColorConversionTransformation : public KoColorConversionTransformation
{
public:
KoLcmsColorConversionTransformation(const KoColorSpace *srcCs, quint32 srcColorSpaceType, LcmsColorProfileContainer *srcProfile,
const KoColorSpace *dstCs, quint32 dstColorSpaceType, LcmsColorProfileContainer *dstProfile,
Intent renderingIntent,
ConversionFlags conversionFlags)
: KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags)
, m_transform(0)
{
Q_ASSERT(srcCs);
Q_ASSERT(dstCs);
Q_ASSERT(renderingIntent < 4);
if (srcCs->colorDepthId() == Integer8BitsColorDepthID
|| srcCs->colorDepthId() == Integer16BitsColorDepthID) {
if ((srcProfile->name().contains(QLatin1String("linear"), Qt::CaseInsensitive) ||
dstProfile->name().contains(QLatin1String("linear"), Qt::CaseInsensitive)) &&
!conversionFlags.testFlag(KoColorConversionTransformation::NoOptimization)) {
conversionFlags |= KoColorConversionTransformation::NoOptimization;
}
}
m_transform = cmsCreateTransform(srcProfile->lcmsProfile(),
srcColorSpaceType,
dstProfile->lcmsProfile(),
dstColorSpaceType,
renderingIntent,
conversionFlags);
Q_ASSERT(m_transform);
}
~KoLcmsColorConversionTransformation() override
{
cmsDeleteTransform(m_transform);
}
public:
void transform(const quint8 *src, quint8 *dst, qint32 numPixels) const override
{
Q_ASSERT(m_transform);
qint32 srcPixelSize = srcColorSpace()->pixelSize();
qint32 dstPixelSize = dstColorSpace()->pixelSize();
cmsDoTransform(m_transform, const_cast<quint8 *>(src), dst, numPixels);
// Lcms does nothing to the destination alpha channel so we must convert that manually.
while (numPixels > 0) {
qreal alpha = srcColorSpace()->opacityF(src);
dstColorSpace()->setOpacity(dst, alpha, 1);
src += srcPixelSize;
dst += dstPixelSize;
numPixels--;
}
}
private:
mutable cmsHTRANSFORM m_transform;
};
class KoLcmsColorProofingConversionTransformation : public KoColorProofingConversionTransformation
{
public:
KoLcmsColorProofingConversionTransformation(const KoColorSpace *srcCs, quint32 srcColorSpaceType, LcmsColorProfileContainer *srcProfile,
const KoColorSpace *dstCs, quint32 dstColorSpaceType, LcmsColorProfileContainer *dstProfile,
const KoColorSpace *proofingSpace,
Intent renderingIntent,
Intent proofingIntent,
ConversionFlags conversionFlags,
quint8 *gamutWarning,
double adaptationState
)
: KoColorProofingConversionTransformation(srcCs, dstCs, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState)
, m_transform(0)
{
Q_ASSERT(srcCs);
Q_ASSERT(dstCs);
Q_ASSERT(renderingIntent < 4);
if (srcCs->colorDepthId() == Integer8BitsColorDepthID
|| srcCs->colorDepthId() == Integer16BitsColorDepthID) {
if ((srcProfile->name().contains(QLatin1String("linear"), Qt::CaseInsensitive) ||
dstProfile->name().contains(QLatin1String("linear"), Qt::CaseInsensitive)) &&
!conversionFlags.testFlag(KoColorConversionTransformation::NoOptimization)) {
conversionFlags |= KoColorConversionTransformation::NoOptimization;
}
}
quint16 alarm[cmsMAXCHANNELS];//this seems to be bgr???
alarm[0] = (cmsUInt16Number)gamutWarning[2]*256;
alarm[1] = (cmsUInt16Number)gamutWarning[1]*256;
alarm[2] = (cmsUInt16Number)gamutWarning[0]*256;
cmsSetAlarmCodes(alarm);
cmsSetAdaptationState(adaptationState);
m_transform = cmsCreateProofingTransform(srcProfile->lcmsProfile(),
srcColorSpaceType,
dstProfile->lcmsProfile(),
dstColorSpaceType,
dynamic_cast<const IccColorProfile *>(proofingSpace->profile())->asLcms()->lcmsProfile(),
renderingIntent,
proofingIntent,
conversionFlags);
cmsSetAdaptationState(1);
Q_ASSERT(m_transform);
}
~KoLcmsColorProofingConversionTransformation() override
{
cmsDeleteTransform(m_transform);
}
public:
void transform(const quint8 *src, quint8 *dst, qint32 numPixels) const override
{
Q_ASSERT(m_transform);
qint32 srcPixelSize = srcColorSpace()->pixelSize();
qint32 dstPixelSize = dstColorSpace()->pixelSize();
//cmsSetAdaptationState(0);
cmsDoTransform(m_transform, const_cast<quint8 *>(src), dst, numPixels);
// Lcms does nothing to the destination alpha channel so we must convert that manually.
while (numPixels > 0) {
qreal alpha = srcColorSpace()->opacityF(src);
dstColorSpace()->setOpacity(dst, alpha, 1);
src += srcPixelSize;
dst += dstPixelSize;
numPixels--;
}
//cmsSetAdaptationState(1);
}
private:
mutable cmsHTRANSFORM m_transform;
};
struct IccColorSpaceEngine::Private {
};
IccColorSpaceEngine::IccColorSpaceEngine() : KoColorSpaceEngine("icc", i18n("ICC Engine")), d(new Private)
{
}
IccColorSpaceEngine::~IccColorSpaceEngine()
{
delete d;
}
-void IccColorSpaceEngine::addProfile(const QString &filename)
+const KoColorProfile* IccColorSpaceEngine::addProfile(const QString &filename)
{
KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
KoColorProfile *profile = new IccColorProfile(filename);
Q_CHECK_PTR(profile);
// this our own loading code; sometimes it fails because of an lcms error
profile->load();
// and then lcms can read the profile from file itself without problems,
// quite often, and we can initialize it
if (!profile->valid()) {
cmsHPROFILE cmsp = cmsOpenProfileFromFile(filename.toLatin1(), "r");
profile = LcmsColorProfileContainer::createFromLcmsProfile(cmsp);
}
if (profile->valid()) {
- qDebug() << "Valid profile : " << profile->fileName() << profile->name();
+ dbgPigment << "Valid profile : " << profile->fileName() << profile->name();
+ registry->addProfile(profile);
+ } else {
+ dbgPigment << "Invalid profile : " << profile->fileName() << profile->name();
+ delete profile;
+ profile = 0;
+ }
+
+ return profile;
+}
+
+const KoColorProfile* IccColorSpaceEngine::addProfile(const QByteArray &data)
+{
+ KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
+
+ KoColorProfile *profile = new IccColorProfile(data);
+ Q_CHECK_PTR(profile);
+
+ if (profile->valid()) {
+ dbgPigment << "Valid profile : " << profile->fileName() << profile->name();
registry->addProfile(profile);
} else {
- qDebug() << "Invalid profile : " << profile->fileName() << profile->name();
+ dbgPigment << "Invalid profile : " << profile->fileName() << profile->name();
delete profile;
+ profile = 0;
}
+ return profile;
}
void IccColorSpaceEngine::removeProfile(const QString &filename)
{
KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
KoColorProfile *profile = new IccColorProfile(filename);
Q_CHECK_PTR(profile);
profile->load();
if (profile->valid() && registry->profileByName(profile->name())) {
registry->removeProfile(profile);
}
}
KoColorConversionTransformation *IccColorSpaceEngine::createColorTransformation(const KoColorSpace *srcColorSpace,
const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
Q_ASSERT(srcColorSpace);
Q_ASSERT(dstColorSpace);
return new KoLcmsColorConversionTransformation(
srcColorSpace, computeColorSpaceType(srcColorSpace),
dynamic_cast<const IccColorProfile *>(srcColorSpace->profile())->asLcms(), dstColorSpace, computeColorSpaceType(dstColorSpace),
dynamic_cast<const IccColorProfile *>(dstColorSpace->profile())->asLcms(), renderingIntent, conversionFlags);
}
KoColorProofingConversionTransformation *IccColorSpaceEngine::createColorProofingTransformation(const KoColorSpace *srcColorSpace,
const KoColorSpace *dstColorSpace,
const KoColorSpace *proofingSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::Intent proofingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags,
quint8 *gamutWarning,
double adaptationState) const
{
Q_ASSERT(srcColorSpace);
Q_ASSERT(dstColorSpace);
return new KoLcmsColorProofingConversionTransformation(
srcColorSpace, computeColorSpaceType(srcColorSpace),
dynamic_cast<const IccColorProfile *>(srcColorSpace->profile())->asLcms(), dstColorSpace, computeColorSpaceType(dstColorSpace),
dynamic_cast<const IccColorProfile *>(dstColorSpace->profile())->asLcms(), proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning,
adaptationState
);
}
quint32 IccColorSpaceEngine::computeColorSpaceType(const KoColorSpace *cs) const
{
Q_ASSERT(cs);
if (const KoLcmsInfo *lcmsInfo = dynamic_cast<const KoLcmsInfo *>(cs)) {
return lcmsInfo->colorSpaceType();
} else {
QString modelId = cs->colorModelId().id();
QString depthId = cs->colorDepthId().id();
// Compute the depth part of the type
quint32 depthType;
if (depthId == Integer8BitsColorDepthID.id()) {
depthType = BYTES_SH(1);
} else if (depthId == Integer16BitsColorDepthID.id()) {
depthType = BYTES_SH(2);
} else if (depthId == Float16BitsColorDepthID.id()) {
depthType = BYTES_SH(2);
} else if (depthId == Float32BitsColorDepthID.id()) {
depthType = BYTES_SH(4);
} else if (depthId == Float64BitsColorDepthID.id()) {
depthType = BYTES_SH(0);
} else {
qWarning() << "Unknow bit depth";
return 0;
}
// Compute the model part of the type
quint32 modelType = 0;
if (modelId == RGBAColorModelID.id()) {
if (depthId.startsWith(QLatin1Char('U'))) {
modelType = (COLORSPACE_SH(PT_RGB) | EXTRA_SH(1) | CHANNELS_SH(3) | DOSWAP_SH(1) | SWAPFIRST_SH(1));
} else if (depthId.startsWith(QLatin1Char('F'))) {
modelType = (COLORSPACE_SH(PT_RGB) | EXTRA_SH(1) | CHANNELS_SH(3));
}
} else if (modelId == XYZAColorModelID.id()) {
modelType = (COLORSPACE_SH(PT_XYZ) | EXTRA_SH(1) | CHANNELS_SH(3));
} else if (modelId == LABAColorModelID.id()) {
modelType = (COLORSPACE_SH(PT_Lab) | EXTRA_SH(1) | CHANNELS_SH(3));
} else if (modelId == CMYKAColorModelID.id()) {
modelType = (COLORSPACE_SH(PT_CMYK) | EXTRA_SH(1) | CHANNELS_SH(4));
} else if (modelId == GrayAColorModelID.id()) {
modelType = (COLORSPACE_SH(PT_GRAY) | EXTRA_SH(1) | CHANNELS_SH(1));
} else if (modelId == GrayColorModelID.id()) {
modelType = (COLORSPACE_SH(PT_GRAY) | CHANNELS_SH(1));
} else if (modelId == YCbCrAColorModelID.id()) {
modelType = (COLORSPACE_SH(PT_YCbCr) | EXTRA_SH(1) | CHANNELS_SH(3));
} else {
qWarning() << "Cannot convert colorspace to lcms modeltype";
return 0;
}
return depthType | modelType;
}
}
diff --git a/plugins/color/lcms2engine/IccColorSpaceEngine.h b/plugins/color/lcms2engine/IccColorSpaceEngine.h
index cc1654c142..85c9c9838c 100644
--- a/plugins/color/lcms2engine/IccColorSpaceEngine.h
+++ b/plugins/color/lcms2engine/IccColorSpaceEngine.h
@@ -1,47 +1,48 @@
/*
* Copyright (c) 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.
*/
#ifndef _KO_ICC_COLOR_SPACE_ENGINE_H_
#define _KO_ICC_COLOR_SPACE_ENGINE_H_
#include <KoColorSpaceEngine.h>
class IccColorSpaceEngine : public KoColorSpaceEngine
{
public:
IccColorSpaceEngine();
virtual ~IccColorSpaceEngine();
- void addProfile(const QString &filename);
+ const KoColorProfile *addProfile(const QString &filename);
+ const KoColorProfile *addProfile(const QByteArray &data);
void removeProfile(const QString &filename);
virtual KoColorConversionTransformation *createColorTransformation(const KoColorSpace *srcColorSpace,
const KoColorSpace *dstColorSpace,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
virtual KoColorProofingConversionTransformation *createColorProofingTransformation(const KoColorSpace *srcColorSpace,
const KoColorSpace *dstColorSpace,
const KoColorSpace *proofingSpace,
KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const;
quint32 computeColorSpaceType(const KoColorSpace *cs) const;
private:
struct Private;
Private *const d;
};
#endif
diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
index 7bdd731f81..1495534350 100644
--- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
+++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp
@@ -1,374 +1,383 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "IccColorProfile.h"
#include <stdint.h>
#include <limits.h>
#include <QFile>
#include <QSharedPointer>
#include "QDebug"
#include "LcmsColorProfileContainer.h"
#include "lcms2.h"
struct IccColorProfile::Data::Private {
QByteArray rawData;
};
IccColorProfile::Data::Data()
: d(new Private)
{
}
IccColorProfile::Data::Data(const QByteArray &rawData)
: d(new Private)
{
d->rawData = rawData;
}
IccColorProfile::Data::~Data()
{
}
QByteArray IccColorProfile::Data::rawData()
{
return d->rawData;
}
void IccColorProfile::Data::setRawData(const QByteArray &rawData)
{
d->rawData = rawData;
}
IccColorProfile::Container::Container()
{
}
IccColorProfile::Container::~Container()
{
}
struct IccColorProfile::Private {
struct Shared {
QScopedPointer<IccColorProfile::Data> data;
QScopedPointer<LcmsColorProfileContainer> lcmsProfile;
QVector<KoChannelInfo::DoubleRange> uiMinMaxes;
};
QSharedPointer<Shared> shared;
};
IccColorProfile::IccColorProfile(const QString &fileName)
: KoColorProfile(fileName), d(new Private)
{
// QSharedPointer lacks a reset in Qt 4.x
d->shared = QSharedPointer<Private::Shared>(new Private::Shared());
d->shared->data.reset(new Data());
}
IccColorProfile::IccColorProfile(const QByteArray &rawData)
: KoColorProfile(QString()), d(new Private)
{
d->shared = QSharedPointer<Private::Shared>(new Private::Shared());
d->shared->data.reset(new Data());
setRawData(rawData);
init();
}
IccColorProfile::IccColorProfile(const IccColorProfile &rhs)
: KoColorProfile(rhs)
, d(new Private(*rhs.d))
{
Q_ASSERT(d->shared);
}
IccColorProfile::~IccColorProfile()
{
Q_ASSERT(d->shared);
}
KoColorProfile *IccColorProfile::clone() const
{
return new IccColorProfile(*this);
}
QByteArray IccColorProfile::rawData() const
{
return d->shared->data->rawData();
}
void IccColorProfile::setRawData(const QByteArray &rawData)
{
d->shared->data->setRawData(rawData);
}
bool IccColorProfile::valid() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->valid();
}
return false;
}
float IccColorProfile::version() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->version();
}
return 0.0;
}
bool IccColorProfile::isSuitableForOutput() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->isSuitableForOutput();
}
return false;
}
bool IccColorProfile::isSuitableForPrinting() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->isSuitableForPrinting();
}
return false;
}
bool IccColorProfile::isSuitableForDisplay() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->isSuitableForDisplay();
}
return false;
}
bool IccColorProfile::supportsPerceptual() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->supportsPerceptual();
}
return false;
}
bool IccColorProfile::supportsSaturation() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->supportsSaturation();
}
return false;
}
bool IccColorProfile::supportsAbsolute() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->supportsAbsolute();
}
return false;
}
bool IccColorProfile::supportsRelative() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->supportsRelative();
}
return false;
}
bool IccColorProfile::hasColorants() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->hasColorants();
}
return false;
}
bool IccColorProfile::hasTRC() const
{
if (d->shared->lcmsProfile)
return d->shared->lcmsProfile->hasTRC();
return false;
}
QVector <qreal> IccColorProfile::getColorantsXYZ() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->getColorantsXYZ();
}
return QVector<qreal>(9);
}
QVector <qreal> IccColorProfile::getColorantsxyY() const
{
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->getColorantsxyY();
}
return QVector<qreal>(9);
}
QVector <qreal> IccColorProfile::getWhitePointXYZ() const
{
QVector <qreal> d50Dummy(3);
d50Dummy << 0.9642 << 1.0000 << 0.8249;
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->getWhitePointXYZ();
}
return d50Dummy;
}
QVector <qreal> IccColorProfile::getWhitePointxyY() const
{
QVector <qreal> d50Dummy(3);
d50Dummy << 0.34773 << 0.35952 << 1.0;
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->getWhitePointxyY();
}
return d50Dummy;
}
QVector <qreal> IccColorProfile::getEstimatedTRC() const
{
QVector <qreal> dummy(3);
dummy.fill(2.2);//estimated sRGB trc.
if (d->shared->lcmsProfile) {
return d->shared->lcmsProfile->getEstimatedTRC();
}
return dummy;
}
void IccColorProfile::linearizeFloatValue(QVector <qreal> & Value) const
{
if (d->shared->lcmsProfile)
d->shared->lcmsProfile->LinearizeFloatValue(Value);
}
void IccColorProfile::delinearizeFloatValue(QVector <qreal> & Value) const
{
if (d->shared->lcmsProfile)
d->shared->lcmsProfile->DelinearizeFloatValue(Value);
}
void IccColorProfile::linearizeFloatValueFast(QVector <qreal> & Value) const
{
if (d->shared->lcmsProfile)
d->shared->lcmsProfile->LinearizeFloatValueFast(Value);
}
void IccColorProfile::delinearizeFloatValueFast(QVector<qreal> &Value) const
{
if (d->shared->lcmsProfile)
d->shared->lcmsProfile->DelinearizeFloatValueFast(Value);
}
+QByteArray IccColorProfile::uniqueId() const
+{
+ QByteArray dummy;
+ if (d->shared->lcmsProfile) {
+ dummy = d->shared->lcmsProfile->getProfileUniqueId();
+ }
+ return dummy;
+}
+
bool IccColorProfile::load()
{
QFile file(fileName());
file.open(QIODevice::ReadOnly);
QByteArray rawData = file.readAll();
setRawData(rawData);
file.close();
if (init()) {
return true;
}
qWarning() << "Failed to load profile from " << fileName();
return false;
}
bool IccColorProfile::save()
{
return false;
}
bool IccColorProfile::init()
{
if (!d->shared->lcmsProfile) {
d->shared->lcmsProfile.reset(new LcmsColorProfileContainer(d->shared->data.data()));
}
if (d->shared->lcmsProfile->init()) {
setName(d->shared->lcmsProfile->name());
setInfo(d->shared->lcmsProfile->info());
setManufacturer(d->shared->lcmsProfile->manufacturer());
setCopyright(d->shared->lcmsProfile->copyright());
if (d->shared->lcmsProfile->valid()) {
calculateFloatUIMinMax();
}
return true;
} else {
return false;
}
}
LcmsColorProfileContainer *IccColorProfile::asLcms() const
{
Q_ASSERT(d->shared->lcmsProfile);
return d->shared->lcmsProfile.data();
}
bool IccColorProfile::operator==(const KoColorProfile &rhs) const
{
const IccColorProfile *rhsIcc = dynamic_cast<const IccColorProfile *>(&rhs);
if (rhsIcc) {
return d->shared == rhsIcc->d->shared;
}
return false;
}
const QVector<KoChannelInfo::DoubleRange> &IccColorProfile::getFloatUIMinMax(void) const
{
Q_ASSERT(!d->shared->uiMinMaxes.isEmpty());
return d->shared->uiMinMaxes;
}
void IccColorProfile::calculateFloatUIMinMax(void)
{
QVector<KoChannelInfo::DoubleRange> &ret = d->shared->uiMinMaxes;
cmsHPROFILE cprofile = d->shared->lcmsProfile->lcmsProfile();
Q_ASSERT(cprofile);
cmsColorSpaceSignature color_space_sig = cmsGetColorSpace(cprofile);
unsigned int num_channels = cmsChannelsOf(color_space_sig);
unsigned int color_space_mask = _cmsLCMScolorSpace(color_space_sig);
Q_ASSERT(num_channels >= 1 && num_channels <= 4); // num_channels==1 is for grayscale, we need to handle it
Q_ASSERT(color_space_mask);
// to try to find the max range of float/doubles for this profile,
// pass in min/max int and make the profile convert that
// this is far from perfect, we need a better way, if possible to get the "bounds" of a profile
uint16_t in_min_pixel[4] = {0, 0, 0, 0};
uint16_t in_max_pixel[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
qreal out_min_pixel[4] = {0, 0, 0, 0};
qreal out_max_pixel[4] = {0, 0, 0, 0};
cmsHTRANSFORM trans = cmsCreateTransform(
cprofile,
(COLORSPACE_SH(color_space_mask) | CHANNELS_SH(num_channels) | BYTES_SH(2)),
cprofile,
(COLORSPACE_SH(color_space_mask) | FLOAT_SH(1) | CHANNELS_SH(num_channels) | BYTES_SH(0)), //NOTE THAT 'BYTES' FIELD IS SET TO ZERO ON DLB because 8 bytes overflows the bitfield
INTENT_PERCEPTUAL, 0); // does the intent matter in this case?
if (trans) {
cmsDoTransform(trans, in_min_pixel, out_min_pixel, 1);
cmsDoTransform(trans, in_max_pixel, out_max_pixel, 1);
cmsDeleteTransform(trans);
}//else, we'll just default to [0..1] below
ret.resize(num_channels);
for (unsigned int i = 0; i < num_channels; ++i) {
if (out_min_pixel[i] < out_max_pixel[i]) {
ret[i].minVal = out_min_pixel[i];
ret[i].maxVal = out_max_pixel[i];
} else {
// aparently we can't even guarentee that converted_to_double(0x0000) < converted_to_double(0xFFFF)
// assume [0..1] in such cases
// we need to find a really solid way of determining the bounds of a profile, if possible
ret[i].minVal = 0;
ret[i].maxVal = 1;
}
}
}
diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
index 5d498229a8..c95ca0116d 100644
--- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
+++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.h
@@ -1,141 +1,143 @@
/*
* Copyright (c) 2007 Cyrille Berger <cberger@cberger.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_ICC_COLOR_PROFILE_H_
#define _KO_ICC_COLOR_PROFILE_H_
#include "KoColorProfile.h"
#include "KoChannelInfo.h"
class LcmsColorProfileContainer;
/**
* This class contains an ICC color profile.
*/
class IccColorProfile : public KoColorProfile
{
public:
using KoColorProfile::save;
/**
* Contains the data associated with a profile. This is
* shared through internal representation.
*/
class Data
{
public:
Data();
explicit Data(const QByteArray &rawData);
~Data();
QByteArray rawData();
void setRawData(const QByteArray &);
private:
struct Private;
QScopedPointer<Private> const d;
};
/**
* This class should be used to wrap the ICC profile
* representation coming from various CMS engine.
*/
class Container
{
public:
Container();
virtual ~Container();
public:
virtual QString name() const = 0;
virtual QString info() const = 0;
virtual QString manufacturer() const = 0;
virtual QString copyright() const = 0;
virtual bool valid() const = 0;
virtual bool isSuitableForOutput() const = 0;
virtual bool isSuitableForPrinting() const = 0;
virtual bool isSuitableForDisplay() const = 0;
virtual bool hasColorants() const = 0;
virtual QVector <double> getColorantsXYZ() const = 0;
virtual QVector <double> getColorantsxyY() const = 0;
virtual QVector <double> getWhitePointXYZ() const = 0;
virtual QVector <double> getWhitePointxyY() const = 0;
virtual QVector <double> getEstimatedTRC() const = 0;
+ virtual QByteArray getProfileUniqueId() const = 0;
};
public:
explicit IccColorProfile(const QString &fileName = QString());
explicit IccColorProfile(const QByteArray &rawData);
IccColorProfile(const IccColorProfile &rhs);
virtual ~IccColorProfile();
virtual KoColorProfile *clone() const;
virtual bool load();
virtual bool save();
/**
* @return an array with the raw data of the profile
*/
QByteArray rawData() const;
virtual bool valid() const;
virtual float version() const;
virtual bool isSuitableForOutput() const;
virtual bool isSuitableForPrinting() const;
virtual bool isSuitableForDisplay() const;
virtual bool supportsPerceptual() const;
virtual bool supportsSaturation() const;
virtual bool supportsAbsolute() const;
virtual bool supportsRelative() const;
virtual bool hasColorants() const;
virtual bool hasTRC() const;
virtual QVector <qreal> getColorantsXYZ() const;
virtual QVector <qreal> getColorantsxyY() const;
virtual QVector <qreal> getWhitePointXYZ() const;
virtual QVector <qreal> getWhitePointxyY() const;
virtual QVector <qreal> getEstimatedTRC() const;
virtual void linearizeFloatValue(QVector <qreal> & Value) const;
virtual void delinearizeFloatValue(QVector <qreal> & Value) const;
virtual void linearizeFloatValueFast(QVector <qreal> & Value) const;
virtual void delinearizeFloatValueFast(QVector <qreal> & Value) const;
+ virtual QByteArray uniqueId() const;
virtual bool operator==(const KoColorProfile &) const;
virtual QString type() const
{
return "icc";
}
/**
* Returns the set of min/maxes for each channel in this profile.
* These (sometimes approximate) min and maxes are suitable
* for UI building.
* Furthermore, then only apply to the floating point uses of this profile,
* and not the integer variants.
*/
const QVector<KoChannelInfo::DoubleRange> &getFloatUIMinMax(void) const;
protected:
void setRawData(const QByteArray &rawData);
public:
LcmsColorProfileContainer *asLcms() const;
protected:
bool init();
void calculateFloatUIMinMax(void);
private:
struct Private;
QScopedPointer<Private> d;
};
#endif
diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
index b520bb6e19..6d3cd44b48 100644
--- a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
+++ b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.cpp
@@ -1,531 +1,559 @@
/*
* This file is part of the KDE project
* Copyright (c) 2000 Matthias Elter <elter@kde.org>
* 2001 John Califf
* 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Thomas Zander <zander@kde.org>
* Copyright (c) 2007 Adrian Page <adrian@pagenet.plus.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU 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 "LcmsColorProfileContainer.h"
#include <cfloat>
#include <cmath>
#include <QTransform>
#include <QGenericMatrix>
#include <QDebug>
+#include "kis_debug.h"
+
class LcmsColorProfileContainer::Private
{
public:
cmsHPROFILE profile;
cmsColorSpaceSignature colorSpaceSignature;
cmsProfileClassSignature deviceClass;
QString productDescription;
QString manufacturer;
QString copyright;
QString name;
float version;
IccColorProfile::Data *data {0};
bool valid {false};
bool suitableForOutput {false};
bool hasColorants;
bool hasTRC;
bool adaptedFromD50;
cmsCIEXYZ mediaWhitePoint;
cmsCIExyY whitePoint;
cmsCIEXYZTRIPLE colorants;
cmsToneCurve *redTRC {0};
cmsToneCurve *greenTRC {0};
cmsToneCurve *blueTRC {0};
cmsToneCurve *grayTRC {0};
cmsToneCurve *redTRCReverse {0};
cmsToneCurve *greenTRCReverse {0};
cmsToneCurve *blueTRCReverse {0};
cmsToneCurve *grayTRCReverse {0};
cmsUInt32Number defaultIntent;
bool isPerceptualCLUT;
bool isRelativeCLUT;
bool isAbsoluteCLUT;
bool isSaturationCLUT;
bool isMatrixShaper;
+
+ QByteArray uniqueId;
};
LcmsColorProfileContainer::LcmsColorProfileContainer()
: d(new Private())
{
d->profile = 0;
}
LcmsColorProfileContainer::LcmsColorProfileContainer(IccColorProfile::Data *data)
: d(new Private())
{
d->data = data;
d->profile = 0;
init();
}
QByteArray LcmsColorProfileContainer::lcmsProfileToByteArray(const cmsHPROFILE profile)
{
cmsUInt32Number bytesNeeded = 0;
// Make a raw data image ready for saving
cmsSaveProfileToMem(profile, 0, &bytesNeeded); // calc size
QByteArray rawData;
rawData.resize(bytesNeeded);
if (rawData.size() >= (int)bytesNeeded) {
cmsSaveProfileToMem(profile, rawData.data(), &bytesNeeded); // fill buffer
} else {
qWarning() << "Couldn't resize the profile buffer, system is probably running out of memory.";
rawData.resize(0);
}
return rawData;
}
IccColorProfile *LcmsColorProfileContainer::createFromLcmsProfile(const cmsHPROFILE profile)
{
IccColorProfile *iccprofile = new IccColorProfile(lcmsProfileToByteArray(profile));
cmsCloseProfile(profile);
return iccprofile;
}
LcmsColorProfileContainer::~LcmsColorProfileContainer()
{
cmsCloseProfile(d->profile);
delete d;
}
#define _BUFFER_SIZE_ 1000
bool LcmsColorProfileContainer::init()
{
if (d->profile) {
cmsCloseProfile(d->profile);
}
d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size());
#ifndef NDEBUG
if (d->data->rawData().size() == 4096) {
qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code.";
}
#endif
if (d->profile) {
wchar_t buffer[_BUFFER_SIZE_];
d->colorSpaceSignature = cmsGetColorSpace(d->profile);
d->deviceClass = cmsGetDeviceClass(d->profile);
cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->name = QString::fromWCharArray(buffer);
//apparantly this should give us a localised string??? Not sure about this.
cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->productDescription = QString::fromWCharArray(buffer);
cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->manufacturer = QString::fromWCharArray(buffer);
cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
d->copyright = QString::fromWCharArray(buffer);
cmsProfileClassSignature profile_class;
profile_class = cmsGetDeviceClass(d->profile);
d->valid = (profile_class != cmsSigNamedColorClass);
//This is where obtain the whitepoint, and convert it to the actual white point of the profile in the case a Chromatic adaption tag is
//present. This is necessary for profiles following the v4 spec.
cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption.
if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)) {
d->mediaWhitePoint = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag));
baseMediaWhitePoint = d->mediaWhitePoint;
cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)) {
//the chromatic adaption tag represent a matrix from the actual white point of the profile to D50.
cmsCIEXYZ *CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag);
//We first put all our data into structures we can manipulate.
double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z};
QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy);
QTransform invertDummy(CAM1[0].X, CAM1[0].Y, CAM1[0].Z, CAM1[1].X, CAM1[1].Y, CAM1[1].Z, CAM1[2].X, CAM1[2].Y, CAM1[2].Z);
//we then abuse QTransform's invert function because it probably does matrix invertion 20 times better than I can program.
//if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeble
//effect when we start multiplying.
QTransform invertedDummy = invertDummy.inverted();
//we then put the QTransform into a generic 3x3 matrix.
double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(),
invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(),
invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33()
};
QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy);
//multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint.
QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix;
//and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it
//uses column/row for initialising.
d->mediaWhitePoint.X = result(0, 0);
d->mediaWhitePoint.Y = result(1, 0);
d->mediaWhitePoint.Z = result(2, 0);
cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
}
}
//This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles.
if (cmsIsTag(d->profile, cmsSigRedColorantTag)) {
cmsCIEXYZTRIPLE tempColorants;
tempColorants.Red = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag));
tempColorants.Green = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag));
tempColorants.Blue = *((cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag));
//convert to d65, this is useless.
cmsAdaptToIlluminant(&d->colorants.Red, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Red);
cmsAdaptToIlluminant(&d->colorants.Green, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Green);
cmsAdaptToIlluminant(&d->colorants.Blue, &baseMediaWhitePoint, &d->mediaWhitePoint, &tempColorants.Blue);
//d->colorants = tempColorants;
d->hasColorants = true;
} else {
//qDebug()<<d->name<<": has no colorants";
d->hasColorants = false;
}
//retrieve TRC.
if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) {
d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag));
d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag));
d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag));
d->redTRCReverse = cmsReverseToneCurve(d->redTRC);
d->greenTRCReverse = cmsReverseToneCurve(d->greenTRC);
d->blueTRCReverse = cmsReverseToneCurve(d->blueTRC);
d->hasTRC = true;
} else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag));
d->grayTRCReverse = cmsReverseToneCurve(d->grayTRC);
d->hasTRC = true;
} else {
d->hasTRC = false;
}
// Check if the profile can convert (something->this)
d->suitableForOutput = cmsIsMatrixShaper(d->profile)
|| (cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT) &&
cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT));
d->version = cmsGetProfileVersion(d->profile);
d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile);
d->isMatrixShaper = cmsIsMatrixShaper(d->profile);
d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT);
d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT);
return true;
}
return false;
}
cmsHPROFILE LcmsColorProfileContainer::lcmsProfile() const
{
return d->profile;
}
cmsColorSpaceSignature LcmsColorProfileContainer::colorSpaceSignature() const
{
return d->colorSpaceSignature;
}
cmsProfileClassSignature LcmsColorProfileContainer::deviceClass() const
{
return d->deviceClass;
}
QString LcmsColorProfileContainer::manufacturer() const
{
return d->manufacturer;
}
QString LcmsColorProfileContainer::copyright() const
{
return d->copyright;
}
bool LcmsColorProfileContainer::valid() const
{
return d->valid;
}
float LcmsColorProfileContainer::version() const
{
return d->version;
}
bool LcmsColorProfileContainer::isSuitableForOutput() const
{
return d->suitableForOutput;
}
bool LcmsColorProfileContainer::isSuitableForPrinting() const
{
return deviceClass() == cmsSigOutputClass;
}
bool LcmsColorProfileContainer::isSuitableForDisplay() const
{
return deviceClass() == cmsSigDisplayClass;
}
bool LcmsColorProfileContainer::supportsPerceptual() const
{
return d->isPerceptualCLUT;
}
bool LcmsColorProfileContainer::supportsSaturation() const
{
return d->isSaturationCLUT;
}
bool LcmsColorProfileContainer::supportsAbsolute() const
{
return d->isAbsoluteCLUT;//LCMS2 doesn't convert matrix shapers via absolute intent, because of V4 workflow.
}
bool LcmsColorProfileContainer::supportsRelative() const
{
if (d->isRelativeCLUT || d->isMatrixShaper){
return true;
}
return false;
}
bool LcmsColorProfileContainer::hasColorants() const
{
return d->hasColorants;
}
bool LcmsColorProfileContainer::hasTRC() const
{
return d->hasTRC;
}
QVector <double> LcmsColorProfileContainer::getColorantsXYZ() const
{
QVector <double> colorants(9);
colorants[0] = d->colorants.Red.X;
colorants[1] = d->colorants.Red.Y;
colorants[2] = d->colorants.Red.Z;
colorants[3] = d->colorants.Green.X;
colorants[4] = d->colorants.Green.Y;
colorants[5] = d->colorants.Green.Z;
colorants[6] = d->colorants.Blue.X;
colorants[7] = d->colorants.Blue.Y;
colorants[8] = d->colorants.Blue.Z;
return colorants;
}
QVector <double> LcmsColorProfileContainer::getColorantsxyY() const
{
cmsCIEXYZ temp1;
cmsCIExyY temp2;
QVector <double> colorants(9);
temp1.X = d->colorants.Red.X;
temp1.Y = d->colorants.Red.Y;
temp1.Z = d->colorants.Red.Z;
cmsXYZ2xyY(&temp2, &temp1);
colorants[0] = temp2.x;
colorants[1] = temp2.y;
colorants[2] = temp2.Y;
temp1.X = d->colorants.Green.X;
temp1.Y = d->colorants.Green.Y;
temp1.Z = d->colorants.Green.Z;
cmsXYZ2xyY(&temp2, &temp1);
colorants[3] = temp2.x;
colorants[4] = temp2.y;
colorants[5] = temp2.Y;
temp1.X = d->colorants.Blue.X;
temp1.Y = d->colorants.Blue.Y;
temp1.Z = d->colorants.Blue.Z;
cmsXYZ2xyY(&temp2, &temp1);
colorants[6] = temp2.x;
colorants[7] = temp2.y;
colorants[8] = temp2.Y;
return colorants;
}
QVector <double> LcmsColorProfileContainer::getWhitePointXYZ() const
{
QVector <double> tempWhitePoint(3);
tempWhitePoint[0] = d->mediaWhitePoint.X;
tempWhitePoint[1] = d->mediaWhitePoint.Y;
tempWhitePoint[2] = d->mediaWhitePoint.Z;
return tempWhitePoint;
}
QVector <double> LcmsColorProfileContainer::getWhitePointxyY() const
{
QVector <double> tempWhitePoint(3);
tempWhitePoint[0] = d->whitePoint.x;
tempWhitePoint[1] = d->whitePoint.y;
tempWhitePoint[2] = d->whitePoint.Y;
return tempWhitePoint;
}
QVector <double> LcmsColorProfileContainer::getEstimatedTRC() const
{
QVector <double> TRCtriplet(3);
if (d->hasColorants) {
if (cmsIsToneCurveLinear(d->redTRC)) {
TRCtriplet[0] = 1.0;
} else {
TRCtriplet[0] = cmsEstimateGamma(d->redTRC, 0.01);
}
if (cmsIsToneCurveLinear(d->greenTRC)) {
TRCtriplet[1] = 1.0;
} else {
TRCtriplet[1] = cmsEstimateGamma(d->greenTRC, 0.01);
}
if (cmsIsToneCurveLinear(d->blueTRC)) {
TRCtriplet[2] = 1.0;
} else {
TRCtriplet[2] = cmsEstimateGamma(d->blueTRC, 0.01);
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
if (cmsIsToneCurveLinear(d->grayTRC)) {
TRCtriplet.fill(1.0);
} else {
TRCtriplet.fill(cmsEstimateGamma(d->grayTRC, 0.01));
}
} else {
TRCtriplet.fill(1.0);
}
}
return TRCtriplet;
}
void LcmsColorProfileContainer::LinearizeFloatValue(QVector <double> & Value) const
{
if (d->hasColorants) {
if (!cmsIsToneCurveLinear(d->redTRC)) {
Value[0] = cmsEvalToneCurveFloat(d->redTRC, Value[0]);
}
if (!cmsIsToneCurveLinear(d->greenTRC)) {
Value[1] = cmsEvalToneCurveFloat(d->greenTRC, Value[1]);
}
if (!cmsIsToneCurveLinear(d->blueTRC)) {
Value[2] = cmsEvalToneCurveFloat(d->blueTRC, Value[2]);
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
Value[0] = cmsEvalToneCurveFloat(d->grayTRC, Value[0]);
}
}
}
void LcmsColorProfileContainer::DelinearizeFloatValue(QVector <double> & Value) const
{
if (d->hasColorants) {
if (!cmsIsToneCurveLinear(d->redTRC)) {
Value[0] = cmsEvalToneCurveFloat(d->redTRCReverse, Value[0]);
}
if (!cmsIsToneCurveLinear(d->greenTRC)) {
Value[1] = cmsEvalToneCurveFloat(d->greenTRCReverse, Value[1]);
}
if (!cmsIsToneCurveLinear(d->blueTRC)) {
Value[2] = cmsEvalToneCurveFloat(d->blueTRCReverse, Value[2]);
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
Value[0] = cmsEvalToneCurveFloat(d->grayTRCReverse, Value[0]);
}
}
}
void LcmsColorProfileContainer::LinearizeFloatValueFast(QVector <double> & Value) const
{
const qreal scale = 65535.0;
const qreal invScale = 1.0 / scale;
if (d->hasColorants) {
//we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
QVector <quint16> TRCtriplet(3);
TRCtriplet[0] = Value[0] * scale;
TRCtriplet[1] = Value[1] * scale;
TRCtriplet[2] = Value[2] * scale;
if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
TRCtriplet[0] = cmsEvalToneCurve16(d->redTRC, TRCtriplet[0]);
Value[0] = TRCtriplet[0] * invScale;
}
if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
TRCtriplet[1] = cmsEvalToneCurve16(d->greenTRC, TRCtriplet[1]);
Value[1] = TRCtriplet[1] * invScale;
}
if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
TRCtriplet[2] = cmsEvalToneCurve16(d->blueTRC, TRCtriplet[2]);
Value[2] = TRCtriplet[2] * invScale;
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
quint16 newValue = cmsEvalToneCurve16(d->grayTRC, Value[0] * scale);
Value[0] = newValue * invScale;
}
}
}
void LcmsColorProfileContainer::DelinearizeFloatValueFast(QVector <double> & Value) const
{
const qreal scale = 65535.0;
const qreal invScale = 1.0 / scale;
if (d->hasColorants) {
//we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
QVector <quint16> TRCtriplet(3);
TRCtriplet[0] = Value[0] * scale;
TRCtriplet[1] = Value[1] * scale;
TRCtriplet[2] = Value[2] * scale;
if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
TRCtriplet[0] = cmsEvalToneCurve16(d->redTRCReverse, TRCtriplet[0]);
Value[0] = TRCtriplet[0] * invScale;
}
if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
TRCtriplet[1] = cmsEvalToneCurve16(d->greenTRCReverse, TRCtriplet[1]);
Value[1] = TRCtriplet[1] * invScale;
}
if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
TRCtriplet[2] = cmsEvalToneCurve16(d->blueTRCReverse, TRCtriplet[2]);
Value[2] = TRCtriplet[2] * invScale;
}
} else {
if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
quint16 newValue = cmsEvalToneCurve16(d->grayTRCReverse, Value[0] * scale);
Value[0] = newValue * invScale;
}
}
}
QString LcmsColorProfileContainer::name() const
{
return d->name;
}
QString LcmsColorProfileContainer::info() const
{
return d->productDescription;
}
+
+QByteArray LcmsColorProfileContainer::getProfileUniqueId() const
+{
+ if (d->uniqueId.isEmpty() && d->profile) {
+ QByteArray id(sizeof(cmsProfileID), 0);
+ cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
+
+ bool isNull = std::all_of(id.constBegin(),
+ id.constEnd(),
+ [](char c) {return c == 0;});
+ if (isNull) {
+ if (cmsMD5computeID(d->profile)) {
+ cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
+ isNull = false;
+ }
+ }
+
+ if (!isNull) {
+ d->uniqueId = id;
+ }
+ }
+
+ return d->uniqueId;
+}
diff --git a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
index 9d851c9af3..4060510430 100644
--- a/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
+++ b/plugins/color/lcms2engine/colorprofiles/LcmsColorProfileContainer.h
@@ -1,118 +1,119 @@
/*
* This file is part of the KDE project
* Copyright (c) 2000 Matthias Elter <elter@kde.org>
* 2004 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 _KO_LCMS_COLORPROFILE_H
#define _KO_LCMS_COLORPROFILE_H
#include "IccColorProfile.h"
#include <lcms2.h>
#include <QByteArray>
#include <QString>
/**
* This class contains an LCMS color profile. Don't use it outside LcmsColorSpace.
*/
class LcmsColorProfileContainer : public IccColorProfile::Container
{
friend class IccColorProfile;
protected:
LcmsColorProfileContainer(IccColorProfile::Data *);
private:
/**
* Create a byte array from a lcms profile.
*/
static QByteArray lcmsProfileToByteArray(const cmsHPROFILE profile);
public:
/**
* @param profile lcms memory structure with the profile, it is freed after the call
* to this function
* @return an ICC profile created from an LCMS profile
*/
static IccColorProfile *createFromLcmsProfile(const cmsHPROFILE profile);
public:
virtual ~LcmsColorProfileContainer();
/**
* @return the ICC color space signature
*/
cmsColorSpaceSignature colorSpaceSignature() const;
/**
* @return the class of the color space signature
*/
cmsProfileClassSignature deviceClass() const;
/**
* @return the name of the manufacturer
*/
QString manufacturer() const;
/**
* @return the embedded copyright
*/
QString copyright() const;
/**
* @return the structure to use with LCMS functions
*/
cmsHPROFILE lcmsProfile() const;
virtual bool valid() const;
virtual float version() const;
virtual bool isSuitableForOutput() const;
virtual bool isSuitableForPrinting() const;
virtual bool isSuitableForDisplay() const;
virtual bool supportsPerceptual() const;
virtual bool supportsSaturation() const;
virtual bool supportsAbsolute() const;
virtual bool supportsRelative() const;
virtual bool hasColorants() const;
virtual bool hasTRC() const;
virtual QVector <double> getColorantsXYZ() const;
virtual QVector <double> getColorantsxyY() const;
virtual QVector <double> getWhitePointXYZ() const;
virtual QVector <double> getWhitePointxyY() const;
virtual QVector <double> getEstimatedTRC() const;
virtual void LinearizeFloatValue(QVector <double> & Value) const;
virtual void DelinearizeFloatValue(QVector <double> & Value) const;
virtual void LinearizeFloatValueFast(QVector <double> & Value) const;
virtual void DelinearizeFloatValueFast(QVector <double> & Value) const;
virtual QString name() const;
virtual QString info() const;
+ virtual QByteArray getProfileUniqueId() const;
protected:
LcmsColorProfileContainer();
private:
bool init();
class Private;
Private *const d;
};
#endif // KOCOLORPROFILE_H
diff --git a/plugins/dockers/CMakeLists.txt b/plugins/dockers/CMakeLists.txt
index a2c02809e8..9b542beec4 100644
--- a/plugins/dockers/CMakeLists.txt
+++ b/plugins/dockers/CMakeLists.txt
@@ -1,24 +1,25 @@
add_subdirectory(defaultdockers)
add_subdirectory(smallcolorselector)
add_subdirectory(specificcolorselector)
add_subdirectory(digitalmixer)
add_subdirectory(advancedcolorselector)
add_subdirectory(presetdocker)
add_subdirectory(historydocker)
add_subdirectory(channeldocker)
add_subdirectory(imagedocker)
add_subdirectory(artisticcolorselector)
add_subdirectory(tasksetdocker)
add_subdirectory(compositiondocker)
add_subdirectory(patterndocker)
add_subdirectory(griddocker)
+add_subdirectory(arrangedocker)
if(HAVE_OCIO)
add_subdirectory(lut)
endif()
add_subdirectory(overview)
add_subdirectory(palettedocker)
add_subdirectory(colorslider)
add_subdirectory(animation)
add_subdirectory(presethistory)
add_subdirectory(shapedockers)
add_subdirectory(histogram)
diff --git a/plugins/dockers/arrangedocker/CMakeLists.txt b/plugins/dockers/arrangedocker/CMakeLists.txt
new file mode 100644
index 0000000000..bfb9f4a2d5
--- /dev/null
+++ b/plugins/dockers/arrangedocker/CMakeLists.txt
@@ -0,0 +1,8 @@
+set(KRITA_ARRANGEDOCKER_SOURCES arrangedocker.cpp arrangedocker_dock.cpp arrange_docker_widget.cpp)
+ki18n_wrap_ui(KRITA_ARRANGEDOCKER_SOURCES
+ arrange_docker_widget.ui
+)
+
+add_library(kritaarrangedocker MODULE ${KRITA_ARRANGEDOCKER_SOURCES})
+target_link_libraries(kritaarrangedocker kritaui)
+install(TARGETS kritaarrangedocker DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/dockers/arrangedocker/arrange_docker_widget.cpp b/plugins/dockers/arrangedocker/arrange_docker_widget.cpp
new file mode 100644
index 0000000000..b0020342fa
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrange_docker_widget.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "arrange_docker_widget.h"
+#include "ui_arrange_docker_widget.h"
+
+#include "kis_debug.h"
+
+#include "kactioncollection.h"
+
+#include <QAction>
+#include <QToolButton>
+
+struct ArrangeDockerWidget::Private
+{
+};
+
+ArrangeDockerWidget::ArrangeDockerWidget(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::ArrangeDockerWidget),
+ m_d(new Private)
+{
+ ui->setupUi(this);
+}
+
+ArrangeDockerWidget::~ArrangeDockerWidget()
+{
+}
+
+
+void replaceAction(QToolButton *button, QAction *newAction)
+{
+ Q_FOREACH (QAction *action, button->actions()) {
+ button->removeAction(action);
+ }
+
+ if (newAction) {
+ button->setDefaultAction(newAction);
+ }
+}
+
+void ArrangeDockerWidget::setActionCollection(KActionCollection *collection)
+{
+ const bool enabled = collection->action("object_order_front");
+
+ if (enabled) {
+ replaceAction(ui->bringToFront, collection->action("object_order_front"));
+ replaceAction(ui->raiseLevel, collection->action("object_order_raise"));
+ replaceAction(ui->lowerLevel, collection->action("object_order_lower"));
+ replaceAction(ui->sendBack, collection->action("object_order_back"));
+
+ replaceAction(ui->leftAlign, collection->action("object_align_horizontal_left"));
+ replaceAction(ui->hCenterAlign, collection->action("object_align_horizontal_center"));
+ replaceAction(ui->rightAlign, collection->action("object_align_horizontal_right"));
+ replaceAction(ui->topAlign, collection->action("object_align_vertical_top"));
+ replaceAction(ui->vCenterAlign, collection->action("object_align_vertical_center"));
+ replaceAction(ui->bottomAlign, collection->action("object_align_vertical_bottom"));
+
+ replaceAction(ui->hDistributeLeft, collection->action("object_distribute_horizontal_left"));
+ replaceAction(ui->hDistributeCenter, collection->action("object_distribute_horizontal_center"));
+ replaceAction(ui->hDistributeRight, collection->action("object_distribute_horizontal_right"));
+ replaceAction(ui->hDistributeGaps, collection->action("object_distribute_horizontal_gaps"));
+
+ replaceAction(ui->vDistributeTop, collection->action("object_distribute_vertical_top"));
+ replaceAction(ui->vDistributeCenter, collection->action("object_distribute_vertical_center"));
+ replaceAction(ui->vDistributeBottom, collection->action("object_distribute_vertical_bottom"));
+ replaceAction(ui->vDistributeGaps, collection->action("object_distribute_vertical_gaps"));
+
+
+ replaceAction(ui->group, collection->action("object_group"));
+ replaceAction(ui->ungroup, collection->action("object_ungroup"));
+ }
+
+ setEnabled(enabled);
+}
+
+
diff --git a/plugins/dockers/arrangedocker/arrange_docker_widget.h b/plugins/dockers/arrangedocker/arrange_docker_widget.h
new file mode 100644
index 0000000000..96718f80ab
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrange_docker_widget.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef ARRANGE_DOCKER_WIDGET_H
+#define ARRANGE_DOCKER_WIDGET_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+#include "kactioncollection.h"
+
+
+namespace Ui {
+class ArrangeDockerWidget;
+}
+
+class ArrangeDockerWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ArrangeDockerWidget(QWidget *parent = 0);
+ ~ArrangeDockerWidget();
+
+ void setActionCollection(KActionCollection *collection);
+
+private:
+ Ui::ArrangeDockerWidget *ui;
+
+ struct Private;
+ const QScopedPointer<Private> m_d;
+};
+
+#endif // ARRANGE_DOCKER_WIDGET_H
diff --git a/plugins/dockers/arrangedocker/arrange_docker_widget.ui b/plugins/dockers/arrangedocker/arrange_docker_widget.ui
new file mode 100644
index 0000000000..efdc308b02
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrange_docker_widget.ui
@@ -0,0 +1,303 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ArrangeDockerWidget</class>
+ <widget class="QWidget" name="ArrangeDockerWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>838</width>
+ <height>448</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="horizontalSpacing">
+ <number>2</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QToolButton" name="leftAlign">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QToolButton" name="hCenterAlign">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="QToolButton" name="rightAlign">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" rowspan="2">
+ <widget class="QFrame" name="frame">
+ <property name="frameShape">
+ <enum>QFrame::VLine</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QToolButton" name="hDistributeLeft">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QToolButton" name="hDistributeCenter">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="6">
+ <widget class="QToolButton" name="hDistributeRight">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="7">
+ <widget class="QToolButton" name="hDistributeGaps">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QToolButton" name="topAlign">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QToolButton" name="vCenterAlign">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QToolButton" name="bottomAlign">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="4">
+ <widget class="QToolButton" name="vDistributeTop">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="5">
+ <widget class="QToolButton" name="vDistributeCenter">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QToolButton" name="vDistributeGaps">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Minimum</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>6</width>
+ <height>6</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="0">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Preferred</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>6</width>
+ <height>6</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="1">
+ <widget class="QToolButton" name="group">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <widget class="QToolButton" name="ungroup">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="3">
+ <widget class="QFrame" name="frame_2">
+ <property name="frameShape">
+ <enum>QFrame::VLine</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="4">
+ <widget class="QToolButton" name="bringToFront">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="5">
+ <widget class="QToolButton" name="raiseLevel">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="6">
+ <widget class="QToolButton" name="lowerLevel">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="7">
+ <widget class="QToolButton" name="sendBack">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="6">
+ <widget class="QToolButton" name="vDistributeBottom">
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>631</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>323</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/dockers/arrangedocker/arrangedocker.cpp b/plugins/dockers/arrangedocker/arrangedocker.cpp
new file mode 100644
index 0000000000..63f5929832
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrangedocker.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "arrangedocker.h"
+
+#include <kis_debug.h>
+#include <kpluginfactory.h>
+#include <klocalizedstring.h>
+
+#include <KoDockFactoryBase.h>
+#include <KoDockRegistry.h>
+#include "KisViewManager.h"
+
+#include "arrangedocker_dock.h"
+
+K_PLUGIN_FACTORY_WITH_JSON(ArrangeDockerPluginFactory, "krita_arrangedocker.json", registerPlugin<ArrangeDockerPlugin>();)
+
+class ArrangeDockerDockFactory : public KoDockFactoryBase {
+public:
+ ArrangeDockerDockFactory()
+ {
+ }
+
+ QString id() const override
+ {
+ return QString( "ArrangeDocker" );
+ }
+
+ virtual Qt::DockWidgetArea defaultDockWidgetArea() const
+ {
+ return Qt::RightDockWidgetArea;
+ }
+
+ QDockWidget* createDockWidget() override
+ {
+ ArrangeDockerDock * dockWidget = new ArrangeDockerDock();
+ dockWidget->setObjectName(id());
+
+ return dockWidget;
+ }
+
+ DockPosition defaultDockPosition() const override
+ {
+ return DockMinimized;
+ }
+private:
+
+
+};
+
+
+ArrangeDockerPlugin::ArrangeDockerPlugin(QObject *parent, const QVariantList &)
+ : QObject(parent)
+{
+ KoDockRegistry::instance()->add(new ArrangeDockerDockFactory());
+}
+
+ArrangeDockerPlugin::~ArrangeDockerPlugin()
+{
+ m_view = 0;
+}
+
+#include "arrangedocker.moc"
diff --git a/plugins/dockers/arrangedocker/arrangedocker.h b/plugins/dockers/arrangedocker/arrangedocker.h
new file mode 100644
index 0000000000..cc9d110b26
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrangedocker.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ARRANGE_DOCKER_H_
+#define _ARRANGE_DOCKER_H_
+
+#include <QObject>
+#include <QVariant>
+
+class KisViewManager;
+
+/**
+ * Template of view plugin
+ */
+class ArrangeDockerPlugin : public QObject
+{
+ Q_OBJECT
+ public:
+ ArrangeDockerPlugin(QObject *parent, const QVariantList &);
+ virtual ~ArrangeDockerPlugin();
+ private:
+ KisViewManager* m_view;
+};
+
+#endif
diff --git a/plugins/dockers/arrangedocker/arrangedocker_dock.cpp b/plugins/dockers/arrangedocker/arrangedocker_dock.cpp
new file mode 100644
index 0000000000..c414dbfa66
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrangedocker_dock.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "arrangedocker_dock.h"
+#include <klocalizedstring.h>
+
+#include "kis_canvas2.h"
+#include <KisViewManager.h>
+#include "arrange_docker_widget.h"
+
+#include <KoToolProxy.h>
+#include <KoShapeManager.h>
+
+
+ArrangeDockerDock::ArrangeDockerDock( )
+ : QDockWidget(i18n("Arrange"))
+ , m_canvas(0)
+{
+ m_configWidget = new ArrangeDockerWidget(this);
+ setWidget(m_configWidget);
+ setEnabled(m_canvas);
+}
+
+ArrangeDockerDock::~ArrangeDockerDock()
+{
+}
+
+void ArrangeDockerDock::setCanvas(KoCanvasBase * canvas)
+{
+ if(canvas && m_canvas == canvas)
+ return;
+
+ if (m_canvas) {
+ m_canvasConnections.clear();
+ m_canvas->disconnectCanvasObserver(this);
+ m_canvas->image()->disconnect(this);
+ }
+
+ m_canvas = canvas ? dynamic_cast<KisCanvas2*>(canvas) : 0;
+ setEnabled(m_canvas);
+
+ if (m_canvas) {
+ m_canvasConnections.addConnection(
+ m_canvas->toolProxy(),
+ SIGNAL(toolChanged(QString)),
+ this,
+ SLOT(slotToolChanged()));
+
+ m_canvasConnections.addConnection(
+ m_canvas->shapeManager(),
+ SIGNAL(selectionChanged()),
+ this,
+ SLOT(slotToolChanged()));
+
+ slotToolChanged();
+ }
+}
+
+void ArrangeDockerDock::unsetCanvas()
+{
+ setCanvas(0);
+}
+
+void ArrangeDockerDock::slotToolChanged()
+{
+ KActionCollection *collection = m_canvas->viewManager()->actionCollection();
+ m_configWidget->setActionCollection(collection);
+}
diff --git a/plugins/dockers/arrangedocker/arrangedocker_dock.h b/plugins/dockers/arrangedocker/arrangedocker_dock.h
new file mode 100644
index 0000000000..c424f0a5f2
--- /dev/null
+++ b/plugins/dockers/arrangedocker/arrangedocker_dock.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _GRID_DOCK_H_
+#define _GRID_DOCK_H_
+
+#include <QDockWidget>
+#include <KoCanvasObserverBase.h>
+#include "kis_signal_auto_connection.h"
+
+class KisCanvas2;
+class ArrangeDockerWidget;
+class KisSignalAutoConnection;
+
+class ArrangeDockerDock : public QDockWidget, public KoCanvasObserverBase {
+ Q_OBJECT
+public:
+ ArrangeDockerDock();
+ ~ArrangeDockerDock();
+ QString observerName() { return "ArrangeDockerDock"; }
+ virtual void setCanvas(KoCanvasBase *canvas);
+ virtual void unsetCanvas();
+
+private Q_SLOTS:
+ void slotToolChanged();
+
+private:
+ ArrangeDockerWidget *m_configWidget;
+ QPointer<KisCanvas2> m_canvas;
+ KisSignalAutoConnectionsStore m_canvasConnections;
+};
+
+
+#endif
diff --git a/plugins/dockers/arrangedocker/krita_arrangedocker.json b/plugins/dockers/arrangedocker/krita_arrangedocker.json
new file mode 100644
index 0000000000..57f7fc6b1c
--- /dev/null
+++ b/plugins/dockers/arrangedocker/krita_arrangedocker.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Grid Docker",
+ "Type": "Service",
+ "X-KDE-Library": "kritagriddocker",
+ "X-KDE-ServiceTypes": [
+ "Krita/Dock"
+ ],
+ "X-Krita-Version": "28"
+}
diff --git a/plugins/dockers/defaultdockers/kis_layer_box.cpp b/plugins/dockers/defaultdockers/kis_layer_box.cpp
index d660ea93f0..a9ee53c095 100644
--- a/plugins/dockers/defaultdockers/kis_layer_box.cpp
+++ b/plugins/dockers/defaultdockers/kis_layer_box.cpp
@@ -1,930 +1,930 @@
/*
* kis_layer_box.cc - part of Krita aka Krayon aka KimageShop
*
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (C) 2006 Gábor Lehel <illissius@gmail.com>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_layer_box.h"
#include <QToolButton>
#include <QLayout>
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>
#include <QRect>
#include <QString>
#include <QToolTip>
#include <QWidget>
#include <QComboBox>
#include <QCheckBox>
#include <QVBoxLayout>
#include <QPixmap>
#include <QList>
#include <QVector>
#include <QLabel>
#include <QMenu>
#include <QWidgetAction>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <kis_icon.h>
#include <KisNodeView.h>
#include <KoColorSpace.h>
#include <KoCompositeOpRegistry.h>
#include <KisDocument.h>
#include <kis_types.h>
#include <kis_image.h>
#include <kis_paint_device.h>
#include <kis_layer.h>
#include <kis_group_layer.h>
#include <kis_mask.h>
#include <kis_node.h>
#include <kis_base_node.h>
#include <kis_composite_ops_model.h>
#include <kis_keyframe_channel.h>
#include <kis_image_animation_interface.h>
#include "kis_action.h"
#include "kis_action_manager.h"
#include "widgets/kis_cmb_composite.h"
#include "widgets/kis_slider_spin_box.h"
#include "KisViewManager.h"
#include "kis_node_manager.h"
#include "kis_node_model.h"
#include "canvas/kis_canvas2.h"
#include "KisDocument.h"
#include "kis_dummies_facade_base.h"
#include "kis_shape_controller.h"
#include "kis_selection_mask.h"
#include "kis_config.h"
#include "KisView.h"
#include "krita_utils.h"
#include "sync_button_and_action.h"
#include "kis_color_label_selector_widget.h"
#include "kis_signals_blocker.h"
#include "kis_color_filter_combo.h"
#include "kis_node_filter_proxy_model.h"
#include "kis_layer_utils.h"
#include "ui_wdglayerbox.h"
inline void KisLayerBox::connectActionToButton(KisViewManager* view, QAbstractButton *button, const QString &id)
{
if (!view || !button) return;
KisAction *action = view->actionManager()->actionByName(id);
if (!action) return;
connect(button, SIGNAL(clicked()), action, SLOT(trigger()));
connect(action, SIGNAL(sigEnableSlaves(bool)), button, SLOT(setEnabled(bool)));
}
inline void KisLayerBox::addActionToMenu(QMenu *menu, const QString &id)
{
if (m_canvas) {
menu->addAction(m_canvas->viewManager()->actionManager()->actionByName(id));
}
}
KisLayerBox::KisLayerBox()
: QDockWidget(i18n("Layers"))
, m_canvas(0)
, m_wdgLayerBox(new Ui_WdgLayerBox)
, m_thumbnailCompressor(500, KisSignalCompressor::FIRST_INACTIVE)
, m_colorLabelCompressor(900, KisSignalCompressor::FIRST_INACTIVE)
{
KisConfig cfg;
QWidget* mainWidget = new QWidget(this);
setWidget(mainWidget);
m_opacityDelayTimer.setSingleShot(true);
m_wdgLayerBox->setupUi(mainWidget);
connect(m_wdgLayerBox->listLayers,
SIGNAL(contextMenuRequested(const QPoint&, const QModelIndex&)),
this, SLOT(slotContextMenuRequested(const QPoint&, const QModelIndex&)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(collapsed(const QModelIndex&)), SLOT(slotCollapsed(const QModelIndex &)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(expanded(const QModelIndex&)), SLOT(slotExpanded(const QModelIndex &)));
connect(m_wdgLayerBox->listLayers,
SIGNAL(selectionChanged(const QModelIndexList&)), SLOT(selectionChanged(const QModelIndexList&)));
m_wdgLayerBox->bnAdd->setIcon(KisIconUtils::loadIcon("addlayer"));
m_wdgLayerBox->bnDelete->setIcon(KisIconUtils::loadIcon("deletelayer"));
m_wdgLayerBox->bnDelete->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnRaise->setEnabled(false);
m_wdgLayerBox->bnRaise->setIcon(KisIconUtils::loadIcon("arrowupblr"));
m_wdgLayerBox->bnRaise->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnLower->setEnabled(false);
m_wdgLayerBox->bnLower->setIcon(KisIconUtils::loadIcon("arrowdown"));
m_wdgLayerBox->bnLower->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnProperties->setIcon(KisIconUtils::loadIcon("properties"));
m_wdgLayerBox->bnProperties->setIconSize(QSize(22, 22));
m_wdgLayerBox->bnDuplicate->setIcon(KisIconUtils::loadIcon("duplicatelayer"));
m_wdgLayerBox->bnDuplicate->setIconSize(QSize(22, 22));
if (cfg.sliderLabels()) {
m_wdgLayerBox->opacityLabel->hide();
m_wdgLayerBox->doubleOpacity->setPrefix(QString("%1: ").arg(i18n("Opacity")));
}
m_wdgLayerBox->doubleOpacity->setRange(0, 100, 0);
m_wdgLayerBox->doubleOpacity->setSuffix("%");
connect(m_wdgLayerBox->doubleOpacity, SIGNAL(valueChanged(qreal)), SLOT(slotOpacitySliderMoved(qreal)));
connect(&m_opacityDelayTimer, SIGNAL(timeout()), SLOT(slotOpacityChanged()));
connect(m_wdgLayerBox->cmbComposite, SIGNAL(activated(int)), SLOT(slotCompositeOpChanged(int)));
m_selectOpaque = new KisAction(i18n("&Select Opaque"), this);
m_selectOpaque->setActivationFlags(KisAction::ACTIVE_LAYER);
m_selectOpaque->setActivationConditions(KisAction::SELECTION_EDITABLE);
m_selectOpaque->setObjectName("select_opaque");
connect(m_selectOpaque, SIGNAL(triggered(bool)), this, SLOT(slotSelectOpaque()));
m_actions.append(m_selectOpaque);
m_newLayerMenu = new QMenu(this);
m_wdgLayerBox->bnAdd->setMenu(m_newLayerMenu);
m_wdgLayerBox->bnAdd->setPopupMode(QToolButton::MenuButtonPopup);
m_nodeModel = new KisNodeModel(this);
m_filteringModel = new KisNodeFilterProxyModel(this);
m_filteringModel->setNodeModel(m_nodeModel);
/**
* Connect model updateUI() to enable/disable controls.
* Note: nodeActivated() is connected separately in setImage(), because
* it needs particular order of calls: first the connection to the
* node manager should be called, then updateUI()
*/
connect(m_nodeModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), SLOT(updateUI()));
connect(m_nodeModel, SIGNAL(modelReset()), SLOT(slotModelReset()));
KisAction *showGlobalSelectionMask = new KisAction(i18n("&Show Global Selection Mask"), this);
showGlobalSelectionMask->setObjectName("show-global-selection-mask");
showGlobalSelectionMask->setActivationFlags(KisAction::ACTIVE_IMAGE);
showGlobalSelectionMask->setToolTip(i18nc("@info:tooltip", "Shows global selection as a usual selection mask in <b>Layers</b> docker"));
showGlobalSelectionMask->setCheckable(true);
connect(showGlobalSelectionMask, SIGNAL(triggered(bool)), SLOT(slotEditGlobalSelection(bool)));
m_actions.append(showGlobalSelectionMask);
showGlobalSelectionMask->setChecked(cfg.showGlobalSelection());
m_colorSelector = new KisColorLabelSelectorWidget(this);
connect(m_colorSelector, SIGNAL(currentIndexChanged(int)), SLOT(slotColorLabelChanged(int)));
m_colorSelectorAction = new QWidgetAction(this);
m_colorSelectorAction->setDefaultWidget(m_colorSelector);
connect(m_nodeModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
&m_colorLabelCompressor, SLOT(start()));
m_wdgLayerBox->listLayers->setModel(m_filteringModel);
// this connection should be done *after* the setModel() call to
// happen later than the internal selection model
connect(m_filteringModel.data(), &KisNodeFilterProxyModel::rowsAboutToBeRemoved,
this, &KisLayerBox::slotAboutToRemoveRows);
connect(m_wdgLayerBox->cmbFilter, SIGNAL(selectedColorsChanged()), SLOT(updateLayerFiltering()));
setEnabled(false);
connect(&m_thumbnailCompressor, SIGNAL(timeout()), SLOT(updateThumbnail()));
connect(&m_colorLabelCompressor, SIGNAL(timeout()), SLOT(updateAvailableLabels()));
}
KisLayerBox::~KisLayerBox()
{
delete m_wdgLayerBox;
}
void expandNodesRecursively(KisNodeSP root, QPointer<KisNodeFilterProxyModel> filteringModel, KisNodeView *nodeView)
{
if (!root) return;
if (filteringModel.isNull()) return;
if (!nodeView) return;
nodeView->blockSignals(true);
KisNodeSP node = root->firstChild();
while (node) {
QModelIndex idx = filteringModel->indexFromNode(node);
if (idx.isValid()) {
nodeView->setExpanded(idx, !node->collapsed());
}
if (node->childCount() > 0) {
expandNodesRecursively(node, filteringModel, nodeView);
}
node = node->nextSibling();
}
nodeView->blockSignals(false);
}
void KisLayerBox::setMainWindow(KisViewManager* kisview)
{
m_nodeManager = kisview->nodeManager();
Q_FOREACH (KisAction *action, m_actions) {
kisview->actionManager()->
addAction(action->objectName(),
action);
}
connectActionToButton(kisview, m_wdgLayerBox->bnAdd, "add_new_paint_layer");
connectActionToButton(kisview, m_wdgLayerBox->bnDuplicate, "duplicatelayer");
KisActionManager *actionManager = kisview->actionManager();
KisAction *action = actionManager->createAction("RenameCurrentLayer");
Q_ASSERT(action);
connect(action, SIGNAL(triggered()), this, SLOT(slotRenameCurrentNode()));
m_propertiesAction = actionManager->createAction("layer_properties");
Q_ASSERT(m_propertiesAction);
new SyncButtonAndAction(m_propertiesAction, m_wdgLayerBox->bnProperties, this);
connect(m_propertiesAction, SIGNAL(triggered()), this, SLOT(slotPropertiesClicked()));
m_removeAction = actionManager->createAction("remove_layer");
Q_ASSERT(m_removeAction);
new SyncButtonAndAction(m_removeAction, m_wdgLayerBox->bnDelete, this);
connect(m_removeAction, SIGNAL(triggered()), this, SLOT(slotRmClicked()));
action = actionManager->createAction("move_layer_up");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnRaise, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotRaiseClicked()));
action = actionManager->createAction("move_layer_down");
Q_ASSERT(action);
new SyncButtonAndAction(action, m_wdgLayerBox->bnLower, this);
connect(action, SIGNAL(triggered()), this, SLOT(slotLowerClicked()));
}
void KisLayerBox::setCanvas(KoCanvasBase *canvas)
{
if (m_canvas == canvas)
return;
setEnabled(canvas != 0);
if (m_canvas) {
m_canvas->disconnectCanvasObserver(this);
m_nodeModel->setDummiesFacade(0, 0, 0, 0, 0);
if (m_image) {
KisImageAnimationInterface *animation = m_image->animationInterface();
animation->disconnect(this);
}
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
}
m_canvas = dynamic_cast<KisCanvas2*>(canvas);
if (m_canvas) {
m_image = m_canvas->image();
connect(m_image, SIGNAL(sigImageUpdated(QRect)), &m_thumbnailCompressor, SLOT(start()));
KisDocument* doc = static_cast<KisDocument*>(m_canvas->imageView()->document());
KisShapeController *kritaShapeController =
dynamic_cast<KisShapeController*>(doc->shapeController());
KisDummiesFacadeBase *kritaDummiesFacade =
static_cast<KisDummiesFacadeBase*>(kritaShapeController);
m_nodeModel->setDummiesFacade(kritaDummiesFacade, m_image, kritaShapeController, m_nodeManager->nodeSelectionAdapter(), m_nodeManager->nodeInsertionAdapter());
connect(m_image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted()));
connect(m_image, SIGNAL(sigNodeCollapsedChanged()), SLOT(slotNodeCollapsedChanged()));
// cold start
if (m_nodeManager) {
setCurrentNode(m_nodeManager->activeNode());
// Connection KisNodeManager -> KisLayerBox
connect(m_nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)),
this, SLOT(setCurrentNode(KisNodeSP)));
connect(m_nodeManager,
SIGNAL(sigUiNeedChangeSelectedNodes(const QList<KisNodeSP> &)),
SLOT(slotNodeManagerChangedSelection(const QList<KisNodeSP> &)));
}
else {
setCurrentNode(m_canvas->imageView()->currentNode());
}
// Connection KisLayerBox -> KisNodeManager (isolate layer)
connect(m_nodeModel, SIGNAL(toggleIsolateActiveNode()),
m_nodeManager, SLOT(toggleIsolateActiveNode()));
KisImageAnimationInterface *animation = m_image->animationInterface();
connect(animation, &KisImageAnimationInterface::sigUiTimeChanged, this, &KisLayerBox::slotImageTimeChanged);
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
m_wdgLayerBox->listLayers->scrollTo(m_wdgLayerBox->listLayers->currentIndex());
updateAvailableLabels();
addActionToMenu(m_newLayerMenu, "add_new_paint_layer");
addActionToMenu(m_newLayerMenu, "add_new_group_layer");
addActionToMenu(m_newLayerMenu, "add_new_clone_layer");
addActionToMenu(m_newLayerMenu, "add_new_shape_layer");
addActionToMenu(m_newLayerMenu, "add_new_adjustment_layer");
addActionToMenu(m_newLayerMenu, "add_new_fill_layer");
addActionToMenu(m_newLayerMenu, "add_new_file_layer");
m_newLayerMenu->addSeparator();
addActionToMenu(m_newLayerMenu, "add_new_transparency_mask");
addActionToMenu(m_newLayerMenu, "add_new_filter_mask");
addActionToMenu(m_newLayerMenu, "add_new_colorize_mask");
addActionToMenu(m_newLayerMenu, "add_new_transform_mask");
addActionToMenu(m_newLayerMenu, "add_new_selection_mask");
}
}
void KisLayerBox::unsetCanvas()
{
setEnabled(false);
if (m_canvas) {
m_newLayerMenu->clear();
}
m_filteringModel->unsetDummiesFacade();
disconnect(m_image, 0, this, 0);
disconnect(m_nodeManager, 0, this, 0);
disconnect(m_nodeModel, 0, m_nodeManager, 0);
m_nodeManager->slotSetSelectedNodes(KisNodeList());
m_canvas = 0;
}
void KisLayerBox::notifyImageDeleted()
{
setCanvas(0);
}
void KisLayerBox::updateUI()
{
if (!m_canvas) return;
if (!m_nodeManager) return;
KisNodeSP activeNode = m_nodeManager->activeNode();
if (activeNode != m_activeNode) {
if( !m_activeNode.isNull() )
m_activeNode->disconnect(this);
m_activeNode = activeNode;
if (activeNode) {
KisKeyframeChannel *opacityChannel = activeNode->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), false);
if (opacityChannel) {
watchOpacityChannel(opacityChannel);
} else {
watchOpacityChannel(0);
connect(activeNode.data(), &KisNode::keyframeChannelAdded, this, &KisLayerBox::slotKeyframeChannelAdded);
}
}
}
m_wdgLayerBox->bnRaise->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->nextSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->bnLower->setEnabled(activeNode && activeNode->isEditable(false) && (activeNode->prevSibling()
|| (activeNode->parent() && activeNode->parent() != m_image->root())));
m_wdgLayerBox->doubleOpacity->setEnabled(activeNode && activeNode->isEditable(false));
m_wdgLayerBox->cmbComposite->setEnabled(activeNode && activeNode->isEditable(false));
if (activeNode) {
if (m_nodeManager->activePaintDevice()) {
slotFillCompositeOps(m_nodeManager->activeColorSpace());
} else {
slotFillCompositeOps(m_image->colorSpace());
}
if (activeNode->inherits("KisColorizeMask") ||
activeNode->inherits("KisLayer")) {
m_wdgLayerBox->doubleOpacity->setEnabled(true);
slotSetOpacity(activeNode->opacity() * 100.0 / 255);
const KoCompositeOp* compositeOp = activeNode->compositeOp();
if (compositeOp) {
slotSetCompositeOp(compositeOp);
} else {
m_wdgLayerBox->cmbComposite->setEnabled(false);
}
const KisGroupLayer *group = qobject_cast<const KisGroupLayer*>(activeNode.data());
bool compositeSelectionActive = !(group && group->passThroughMode());
m_wdgLayerBox->cmbComposite->setEnabled(compositeSelectionActive);
} else if (activeNode->inherits("KisMask")) {
m_wdgLayerBox->cmbComposite->setEnabled(false);
m_wdgLayerBox->doubleOpacity->setEnabled(false);
}
}
}
/**
- * This method is callen *only* when non-GUI code requested the
+ * This method is called *only* when non-GUI code requested the
* change of the current node
*/
void KisLayerBox::setCurrentNode(KisNodeSP node)
{
m_filteringModel->setActiveNode(node);
QModelIndex index = node ? m_filteringModel->indexFromNode(node) : QModelIndex();
m_filteringModel->setData(index, true, KisNodeModel::ActiveRole);
updateUI();
}
void KisLayerBox::slotModelReset()
{
if(m_nodeModel->hasDummiesFacade()) {
QItemSelection selection;
Q_FOREACH (const KisNodeSP node, m_nodeManager->selectedNodes()) {
const QModelIndex &idx = m_filteringModel->indexFromNode(node);
if(idx.isValid()){
QItemSelectionRange selectionRange(idx);
selection << selectionRange;
}
}
m_wdgLayerBox->listLayers->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
}
updateUI();
}
void KisLayerBox::slotSetCompositeOp(const KoCompositeOp* compositeOp)
{
KoID opId = KoCompositeOpRegistry::instance().getKoID(compositeOp->id());
m_wdgLayerBox->cmbComposite->blockSignals(true);
m_wdgLayerBox->cmbComposite->selectCompositeOp(opId);
m_wdgLayerBox->cmbComposite->blockSignals(false);
}
void KisLayerBox::slotFillCompositeOps(const KoColorSpace* colorSpace)
{
m_wdgLayerBox->cmbComposite->validate(colorSpace);
}
// range: 0-100
void KisLayerBox::slotSetOpacity(double opacity)
{
Q_ASSERT(opacity >= 0 && opacity <= 100);
m_wdgLayerBox->doubleOpacity->blockSignals(true);
m_wdgLayerBox->doubleOpacity->setValue(opacity);
m_wdgLayerBox->doubleOpacity->blockSignals(false);
}
void KisLayerBox::slotContextMenuRequested(const QPoint &pos, const QModelIndex &index)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
KisNodeSP activeNode = m_nodeManager->activeNode();
if (nodes.isEmpty() || !activeNode) return;
if (m_canvas) {
QMenu menu;
const bool singleLayer = nodes.size() == 1;
if (index.isValid()) {
menu.addAction(m_propertiesAction);
if (singleLayer) {
addActionToMenu(&menu, "layer_style");
}
{
KisSignalsBlocker b(m_colorSelector);
m_colorSelector->setCurrentIndex(singleLayer ? activeNode->colorLabelIndex() : -1);
}
menu.addAction(m_colorSelectorAction);
menu.addSeparator();
addActionToMenu(&menu, "cut_layer_clipboard");
addActionToMenu(&menu, "copy_layer_clipboard");
addActionToMenu(&menu, "paste_layer_from_clipboard");
menu.addAction(m_removeAction);
addActionToMenu(&menu, "duplicatelayer");
addActionToMenu(&menu, "merge_layer");
if (singleLayer) {
addActionToMenu(&menu, "flatten_image");
addActionToMenu(&menu, "flatten_layer");
}
menu.addSeparator();
QMenu *selectMenu = menu.addMenu(i18n("&Select"));
addActionToMenu(selectMenu, "select_all_layers");
addActionToMenu(selectMenu, "select_visible_layers");
addActionToMenu(selectMenu, "select_invisible_layers");
addActionToMenu(selectMenu, "select_locked_layers");
addActionToMenu(selectMenu, "select_unlocked_layers");
QMenu *groupMenu = menu.addMenu(i18n("&Group"));
addActionToMenu(groupMenu, "create_quick_group");
addActionToMenu(groupMenu, "create_quick_clipping_group");
addActionToMenu(groupMenu, "quick_ungroup");
if (singleLayer) {
QMenu *addLayerMenu = menu.addMenu(i18n("&Add"));
addActionToMenu(addLayerMenu, "add_new_transparency_mask");
addActionToMenu(addLayerMenu, "add_new_filter_mask");
addActionToMenu(addLayerMenu, "add_new_colorize_mask");
addActionToMenu(addLayerMenu, "add_new_transform_mask");
addActionToMenu(addLayerMenu, "add_new_selection_mask");
QMenu *convertToMenu = menu.addMenu(i18n("&Convert"));
addActionToMenu(convertToMenu, "convert_to_paint_layer");
addActionToMenu(convertToMenu, "convert_to_transparency_mask");
addActionToMenu(convertToMenu, "convert_to_filter_mask");
addActionToMenu(convertToMenu, "convert_to_selection_mask");
QMenu *splitAlphaMenu = menu.addMenu(i18n("S&plit Alpha"));
addActionToMenu(splitAlphaMenu, "split_alpha_into_mask");
addActionToMenu(splitAlphaMenu, "split_alpha_write");
addActionToMenu(splitAlphaMenu, "split_alpha_save_merged");
}
menu.addSeparator();
if (singleLayer) {
addActionToMenu(&menu, "show_in_timeline");
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node && !node->inherits("KisTransformMask")) {
addActionToMenu(&menu, "isolate_layer");
}
menu.addAction(m_selectOpaque);
}
}
menu.exec(pos);
}
}
void KisLayerBox::slotMergeLayer()
{
if (!m_canvas) return;
m_nodeManager->mergeLayer();
}
void KisLayerBox::slotMinimalView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::MinimalMode);
}
void KisLayerBox::slotDetailedView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::DetailedMode);
}
void KisLayerBox::slotThumbnailView()
{
m_wdgLayerBox->listLayers->setDisplayMode(KisNodeView::ThumbnailMode);
}
void KisLayerBox::slotRmClicked()
{
if (!m_canvas) return;
m_nodeManager->removeNode();
}
void KisLayerBox::slotRaiseClicked()
{
if (!m_canvas) return;
m_nodeManager->raiseNode();
}
void KisLayerBox::slotLowerClicked()
{
if (!m_canvas) return;
m_nodeManager->lowerNode();
}
void KisLayerBox::slotPropertiesClicked()
{
if (!m_canvas) return;
if (KisNodeSP active = m_nodeManager->activeNode()) {
m_nodeManager->nodeProperties(active);
}
}
void KisLayerBox::slotCompositeOpChanged(int index)
{
Q_UNUSED(index);
if (!m_canvas) return;
QString compositeOp = m_wdgLayerBox->cmbComposite->selectedCompositeOp().id();
m_nodeManager->nodeCompositeOpChanged(m_nodeManager->activeColorSpace()->compositeOp(compositeOp));
}
void KisLayerBox::slotOpacityChanged()
{
if (!m_canvas) return;
m_blockOpacityUpdate = true;
m_nodeManager->nodeOpacityChanged(m_newOpacity, true);
m_blockOpacityUpdate = false;
}
void KisLayerBox::slotOpacitySliderMoved(qreal opacity)
{
m_newOpacity = opacity;
m_opacityDelayTimer.start(200);
}
void KisLayerBox::slotCollapsed(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(true);
}
}
void KisLayerBox::slotExpanded(const QModelIndex &index)
{
KisNodeSP node = m_filteringModel->nodeFromIndex(index);
if (node) {
node->setCollapsed(false);
}
}
void KisLayerBox::slotSelectOpaque()
{
if (!m_canvas) return;
QAction *action = m_canvas->viewManager()->actionManager()->actionByName("selectopaque");
if (action) {
action->trigger();
}
}
void KisLayerBox::slotNodeCollapsedChanged()
{
expandNodesRecursively(m_image->rootLayer(), m_filteringModel, m_wdgLayerBox->listLayers);
}
inline bool isSelectionMask(KisNodeSP node)
{
return dynamic_cast<KisSelectionMask*>(node.data());
}
KisNodeSP KisLayerBox::findNonHidableNode(KisNodeSP startNode)
{
if (isSelectionMask(startNode) &&
startNode->parent() &&
!startNode->parent()->parent()) {
KisNodeSP node = startNode->prevSibling();
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
if (!node) {
node = startNode->nextSibling();
while (node && isSelectionMask(node)) {
node = node->nextSibling();
}
}
if (!node) {
node = m_image->root()->lastChild();
while (node && isSelectionMask(node)) {
node = node->prevSibling();
}
}
KIS_ASSERT_RECOVER_NOOP(node && "cannot activate any node!");
startNode = node;
}
return startNode;
}
void KisLayerBox::slotEditGlobalSelection(bool showSelections)
{
KisNodeSP lastActiveNode = m_nodeManager->activeNode();
KisNodeSP activateNode = lastActiveNode;
if (!showSelections) {
activateNode = findNonHidableNode(activateNode);
}
m_nodeModel->setShowGlobalSelection(showSelections);
if (showSelections) {
KisNodeSP newMask = m_image->rootLayer()->selectionMask();
if (newMask) {
activateNode = newMask;
}
}
if (activateNode) {
if (lastActiveNode != activateNode) {
m_nodeManager->slotNonUiActivatedNode(activateNode);
} else {
setCurrentNode(lastActiveNode);
}
}
}
void KisLayerBox::selectionChanged(const QModelIndexList selection)
{
if (!m_nodeManager) return;
/**
* When the user clears the extended selection by clicking on the
* empty area of the docker, the selection should be reset on to
* the active layer, which might be even unselected(!).
*/
if (selection.isEmpty() && m_nodeManager->activeNode()) {
QModelIndex selectedIndex =
m_filteringModel->indexFromNode(m_nodeManager->activeNode());
m_wdgLayerBox->listLayers->selectionModel()->
setCurrentIndex(selectedIndex, QItemSelectionModel::ClearAndSelect);
return;
}
QList<KisNodeSP> selectedNodes;
Q_FOREACH (const QModelIndex &idx, selection) {
selectedNodes << m_filteringModel->nodeFromIndex(idx);
}
m_nodeManager->slotSetSelectedNodes(selectedNodes);
updateUI();
}
void KisLayerBox::slotAboutToRemoveRows(const QModelIndex &parent, int start, int end)
{
/**
* Qt has changed its behavior when deleting an item. Previously
* the selection priority was on the next item in the list, and
* now it has shanged to the previous item. Here we just adjust
* the selected item after the node removal. Please take care that
* this method overrides what was done by the corresponding method
* of QItemSelectionModel, which *has already done* its work. That
* is why we use (start - 1) and (end + 1) in the activation
* condition.
*
* See bug: https://bugs.kde.org/show_bug.cgi?id=345601
*/
QModelIndex currentIndex = m_wdgLayerBox->listLayers->currentIndex();
QAbstractItemModel *model = m_filteringModel;
if (currentIndex.isValid() && parent == currentIndex.parent()
&& currentIndex.row() >= start - 1 && currentIndex.row() <= end + 1) {
QModelIndex old = currentIndex;
if (model && end < model->rowCount(parent) - 1) // there are rows left below the change
currentIndex = model->index(end + 1, old.column(), parent);
else if (start > 0) // there are rows left above the change
currentIndex = model->index(start - 1, old.column(), parent);
else // there are no rows left in the table
currentIndex = QModelIndex();
if (currentIndex.isValid() && currentIndex != old) {
m_wdgLayerBox->listLayers->setCurrentIndex(currentIndex);
}
}
}
void KisLayerBox::slotNodeManagerChangedSelection(const KisNodeList &nodes)
{
if (!m_nodeManager) return;
QModelIndexList newSelection;
Q_FOREACH(KisNodeSP node, nodes) {
newSelection << m_filteringModel->indexFromNode(node);
}
QItemSelectionModel *model = m_wdgLayerBox->listLayers->selectionModel();
if (KritaUtils::compareListsUnordered(newSelection, model->selectedIndexes())) {
return;
}
QItemSelection selection;
Q_FOREACH(const QModelIndex &idx, newSelection) {
selection.select(idx, idx);
}
model->select(selection, QItemSelectionModel::ClearAndSelect);
}
void KisLayerBox::updateThumbnail()
{
m_wdgLayerBox->listLayers->updateNode(m_wdgLayerBox->listLayers->currentIndex());
}
void KisLayerBox::slotRenameCurrentNode()
{
m_wdgLayerBox->listLayers->edit(m_wdgLayerBox->listLayers->currentIndex());
}
void KisLayerBox::slotColorLabelChanged(int label)
{
KisNodeList nodes = m_nodeManager->selectedNodes();
Q_FOREACH(KisNodeSP node, nodes) {
auto applyLabelFunc =
[label](KisNodeSP node) {
node->setColorLabelIndex(label);
};
KisLayerUtils::recursiveApplyNodes(node, applyLabelFunc);
}
}
void KisLayerBox::updateAvailableLabels()
{
if (!m_image) return;
m_wdgLayerBox->cmbFilter->updateAvailableLabels(m_image->root());
}
void KisLayerBox::updateLayerFiltering()
{
m_filteringModel->setAcceptedLabels(m_wdgLayerBox->cmbFilter->selectedColors());
}
void KisLayerBox::slotKeyframeChannelAdded(KisKeyframeChannel *channel)
{
if (channel->id() == KisKeyframeChannel::Opacity.id()) {
watchOpacityChannel(channel);
}
}
void KisLayerBox::watchOpacityChannel(KisKeyframeChannel *channel)
{
if (m_opacityChannel) {
m_opacityChannel->disconnect(this);
}
m_opacityChannel = channel;
if (m_opacityChannel) {
connect(m_opacityChannel, SIGNAL(sigKeyframeAdded(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeRemoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeMoved(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeMoved(KisKeyframeSP)));
connect(m_opacityChannel, SIGNAL(sigKeyframeChanged(KisKeyframeSP)), this, SLOT(slotOpacityKeyframeChanged(KisKeyframeSP)));
}
}
void KisLayerBox::slotOpacityKeyframeChanged(KisKeyframeSP keyframe)
{
Q_UNUSED(keyframe);
if (m_blockOpacityUpdate) return;
updateUI();
}
void KisLayerBox::slotOpacityKeyframeMoved(KisKeyframeSP keyframe, int fromTime)
{
Q_UNUSED(fromTime);
slotOpacityKeyframeChanged(keyframe);
}
void KisLayerBox::slotImageTimeChanged(int time)
{
Q_UNUSED(time);
updateUI();
}
#include "moc_kis_layer_box.cpp"
diff --git a/plugins/dockers/historydocker/HistoryDock.cpp b/plugins/dockers/historydocker/HistoryDock.cpp
index ce36a274cb..d174935fa4 100644
--- a/plugins/dockers/historydocker/HistoryDock.cpp
+++ b/plugins/dockers/historydocker/HistoryDock.cpp
@@ -1,84 +1,85 @@
/* This file is part of the KDE project
* Copyright (C) 2010 Matus Talcik <matus.talcik@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "HistoryDock.h"
#include <KoDocumentResourceManager.h>
#include <kis_config.h>
#include <kis_icon_utils.h>
#include <QDebug>
#include <QWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QSpacerItem>
#include <DlgConfigureHistoryDock.h>
HistoryDock::HistoryDock()
: QDockWidget()
, m_historyCanvas(0)
{
QWidget *page = new QWidget(this);
QVBoxLayout *vl = new QVBoxLayout(page);
m_undoView = new KisUndoView(this);
vl->addWidget(m_undoView);
- QHBoxLayout *hl = new QHBoxLayout(page);
+ QHBoxLayout *hl = new QHBoxLayout();
hl->addSpacerItem(new QSpacerItem(10, 1, QSizePolicy::Expanding, QSizePolicy::Fixed));
m_bnConfigure = new QToolButton(page);
m_bnConfigure->setIcon(KisIconUtils::loadIcon("configure"));
connect(m_bnConfigure, SIGNAL(clicked(bool)), SLOT(configure()));
hl->addWidget(m_bnConfigure);
vl->addItem(hl);
+ vl->addLayout(hl);
setWidget(page);
setWindowTitle(i18n("Undo History"));
}
void HistoryDock::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
QPointer<KisCanvas2> myCanvas = dynamic_cast<KisCanvas2*>(canvas);
if (myCanvas
&& myCanvas->shapeController()
&& myCanvas->shapeController()->resourceManager()
&& myCanvas->shapeController()->resourceManager()->undoStack()) {
KUndo2Stack* undoStack = canvas->shapeController()->resourceManager()->undoStack();
m_undoView->setStack(undoStack);
KisConfig cfg;
m_undoView->stack()->setUseCumulativeUndoRedo(cfg.useCumulativeUndoRedo());
m_undoView->stack()->setTimeT1(cfg.stackT1());
m_undoView->stack()->setTimeT2(cfg.stackT2());
m_undoView->stack()->setStrokesN(cfg.stackN());
}
m_undoView->setCanvas( myCanvas );
}
void HistoryDock::configure()
{
DlgConfigureHistoryDock dlg(m_undoView, m_undoView->stack(), this);
dlg.exec();
}
void HistoryDock::unsetCanvas()
{
m_historyCanvas = 0;
setEnabled(false);
m_undoView->setStack(0);
}
diff --git a/plugins/dockers/shapedockers/CMakeLists.txt b/plugins/dockers/shapedockers/CMakeLists.txt
index 5dffd82eb3..64825c8865 100644
--- a/plugins/dockers/shapedockers/CMakeLists.txt
+++ b/plugins/dockers/shapedockers/CMakeLists.txt
@@ -1,20 +1,14 @@
project(calligradockers)
set(calligradockers_SRCS
-
- shapeproperties/ShapePropertiesDocker.cpp
- shapeproperties/ShapePropertiesDockerFactory.cpp
-
- shapecollection/CollectionShapeFactory.cpp
shapecollection/ShapeCollectionDocker.cpp
shapecollection/CollectionItemModel.cpp
- shapecollection/OdfCollectionLoader.cpp
Plugin.cpp
)
add_library(krita_docker_defaults MODULE ${calligradockers_SRCS})
target_link_libraries(krita_docker_defaults kritawidgets)
install(TARGETS krita_docker_defaults DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/dockers/shapedockers/Plugin.cpp b/plugins/dockers/shapedockers/Plugin.cpp
index 778ffda242..b9f6fe9fa3 100644
--- a/plugins/dockers/shapedockers/Plugin.cpp
+++ b/plugins/dockers/shapedockers/Plugin.cpp
@@ -1,38 +1,36 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "Plugin.h"
-#include "shapeproperties/ShapePropertiesDockerFactory.h"
#include "shapecollection/ShapeCollectionDocker.h"
#include <KoDockRegistry.h>
#include <kpluginfactory.h>
K_PLUGIN_FACTORY_WITH_JSON(PluginFactory, "calligra_docker_defaults.json", registerPlugin<Plugin>();)
Plugin::Plugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
Q_UNUSED(parent);
- KoDockRegistry::instance()->add(new ShapePropertiesDockerFactory());
KoDockRegistry::instance()->add(new ShapeCollectionDockerFactory());
}
#include <Plugin.moc>
diff --git a/plugins/dockers/shapedockers/shapecollection/CollectionShapeFactory.cpp b/plugins/dockers/shapedockers/shapecollection/CollectionShapeFactory.cpp
deleted file mode 100644
index 197e05042b..0000000000
--- a/plugins/dockers/shapedockers/shapecollection/CollectionShapeFactory.cpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "CollectionShapeFactory.h"
-
-#include <KoShape.h>
-#include <KoDrag.h>
-#include <KoShapeOdfSaveHelper.h>
-#include <KoOdf.h>
-#include <KoShapeLoadingContext.h>
-#include <KoShapeBasedDocumentBase.h>
-#include <KoOdfLoadingContext.h>
-#include <KoStore.h>
-#include <KoOdfReadStore.h>
-#include <KoXmlNS.h>
-#include <KoShapeRegistry.h>
-
-#include <QDebug>
-#include <QMimeData>
-#include <QBuffer>
-
-CollectionShapeFactory::CollectionShapeFactory(const QString &id, KoShape *shape)
- : KoShapeFactoryBase(id, shape->name())
- , m_shape(shape)
-{
-}
-
-CollectionShapeFactory::~CollectionShapeFactory()
-{
- delete m_shape;
-}
-
-KoShape *CollectionShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
-{
- QList<KoShape *> shapes;
-
- shapes << m_shape;
-
- KoDrag drag;
- KoShapeOdfSaveHelper saveHelper(shapes);
- drag.setOdf(KoOdf::mimeType(KoOdf::Graphics), saveHelper);
- QMimeData *data = drag.mimeData();
-
- QByteArray arr = data->data(KoOdf::mimeType(KoOdf::Graphics));
- KoShape *shape = 0;
-
- if (!arr.isEmpty()) {
- QBuffer buffer(&arr);
- KoStore *store = KoStore::createStore(&buffer, KoStore::Read);
- KoOdfReadStore odfStore(store); // Note: KoDfReadstore will not delete the KoStore *store;
-
- QString errorMessage;
- if (!odfStore.loadAndParse(errorMessage)) {
- qCritical() << "loading and parsing failed:" << errorMessage << endl;
- delete store;
- return 0;
- }
-
- KoXmlElement content = odfStore.contentDoc().documentElement();
- KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
-
- if (realBody.isNull()) {
- qCritical() << "No body tag found!" << endl;
- delete store;
- return 0;
- }
-
- KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, KoOdf::bodyContentElement(KoOdf::Text, false));
-
- if (body.isNull()) {
- qCritical() << "No" << KoOdf::bodyContentElement(KoOdf::Text, true) << "tag found!" << endl;
- delete store;
- return 0;
- }
-
- KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
- KoShapeLoadingContext context(loadingContext, documentResources);
-
- KoXmlElement element;
-
- forEachElement(element, body) {
- KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(element, context);
- if (shape) {
- delete data;
- delete store;
- return shape;
- }
- }
- delete store;
- }
-
- delete data;
- return shape;
-}
-
-bool CollectionShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
-{
- Q_UNUSED(e);
- Q_UNUSED(context);
- return false;
-}
diff --git a/plugins/dockers/shapedockers/shapecollection/CollectionShapeFactory.h b/plugins/dockers/shapedockers/shapecollection/CollectionShapeFactory.h
deleted file mode 100644
index 730eb17b1f..0000000000
--- a/plugins/dockers/shapedockers/shapecollection/CollectionShapeFactory.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#ifndef KOCOLLECTIONSHAPEFACTORY_H
-#define KOCOLLECTIONSHAPEFACTORY_H
-
-#include <KoShapeFactoryBase.h>
-
-class CollectionShapeFactory : public KoShapeFactoryBase
-{
-public:
- CollectionShapeFactory(const QString &id, KoShape *shape);
- ~CollectionShapeFactory();
-
- virtual KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const;
- virtual bool supports(const KoXmlElement &e, KoShapeLoadingContext &context) const;
-
-private:
- KoShape *m_shape;
-};
-
-#endif //KOCOLLECTIONSHAPEFACTORY_H
diff --git a/plugins/dockers/shapedockers/shapecollection/OdfCollectionLoader.cpp b/plugins/dockers/shapedockers/shapecollection/OdfCollectionLoader.cpp
deleted file mode 100644
index 0fc0f3a998..0000000000
--- a/plugins/dockers/shapedockers/shapecollection/OdfCollectionLoader.cpp
+++ /dev/null
@@ -1,186 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "OdfCollectionLoader.h"
-
-#include <KoStore.h>
-#include <KoOdfReadStore.h>
-#include <KoOdfLoadingContext.h>
-#include <KoXmlNS.h>
-#include <KoShape.h>
-#include <KoShapeRegistry.h>
-#include <KoShapeLoadingContext.h>
-#include <KoOdf.h>
-
-#include <klocalizedstring.h>
-#include <QDebug>
-
-#include <QTimer>
-#include <QDir>
-#include <QFile>
-#include <QByteArray>
-
-OdfCollectionLoader::OdfCollectionLoader(const QString &path, QObject *parent)
- : QObject(parent)
-{
- m_path = path;
- m_odfStore = 0;
- m_shapeLoadingContext = 0;
- m_loadingContext = 0;
-
- m_loadingTimer = new QTimer(this);
- m_loadingTimer->setInterval(0);
- connect(m_loadingTimer, SIGNAL(timeout()),
- this, SLOT(loadShape()));
-}
-
-OdfCollectionLoader::~OdfCollectionLoader()
-{
- delete m_shapeLoadingContext;
- delete m_loadingContext;
- m_shapeLoadingContext = 0;
- m_loadingContext = 0;
-
- if (m_odfStore) {
- delete m_odfStore->store();
- delete m_odfStore;
- m_odfStore = 0;
- }
-}
-
-void OdfCollectionLoader::load()
-{
- QDir dir(m_path);
- m_fileList = dir.entryList(QStringList() << "*.odg", QDir::Files);
-
- if (m_fileList.isEmpty()) {
- qCritical() << "Found no shapes in the collection!" << m_path;
- emit loadingFailed(i18n("Found no shapes in the collection! %1", m_path));
- return;
- }
-
- nextFile();
-}
-
-void OdfCollectionLoader::loadShape()
-{
- //qDebug() << m_shape.tagName();
- KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(m_shape, *m_shapeLoadingContext);
-
- if (shape) {
- if (!shape->parent()) {
- m_shapeList.append(shape);
- }
- }
-
- m_shape = m_shape.nextSibling().toElement();
-
- if (m_shape.isNull()) {
- m_page = m_page.nextSibling().toElement();
-
- if (m_page.isNull()) {
- m_loadingTimer->stop();
-
- if (m_fileList.isEmpty()) {
- emit loadingFinished();
- } else {
- nextFile();
- }
- } else {
- m_shape = m_page.firstChild().toElement();
- }
- }
-}
-
-void OdfCollectionLoader::nextFile()
-{
- QString file = m_fileList.takeFirst();
- QString filepath = m_path + file;
- loadNativeFile(filepath);
-}
-
-void OdfCollectionLoader::loadNativeFile(const QString &path)
-{
- delete m_shapeLoadingContext;
- delete m_loadingContext;
- m_shapeLoadingContext = 0;
- m_loadingContext = 0;
-
- if (m_odfStore) {
- delete m_odfStore->store();
- delete m_odfStore;
- m_odfStore = 0;
- }
-
- KoStore *store = KoStore::createStore(path, KoStore::Read);
-
- if (store->bad()) {
- emit loadingFailed(i18n("Not a valid Calligra file: %1", m_path));
- delete store;
- return;
- }
-
- m_odfStore = new KoOdfReadStore(store); // Owns the store now
- QString errorMessage;
-
- if (!m_odfStore->loadAndParse(errorMessage)) {
- emit loadingFailed(errorMessage);
- return;
- }
-
- KoOdfLoadingContext *m_loadingContext = new KoOdfLoadingContext(m_odfStore->styles(), m_odfStore->store());
- // it ok here to pass an empty resourceManager as we don't have a document
- // tz: not sure if that is 100% correct what if an image is loaded in the collection it needs a image collection
- m_shapeLoadingContext = new KoShapeLoadingContext(*m_loadingContext, 0);
-
- KoXmlElement content = m_odfStore->contentDoc().documentElement();
- KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body"));
-
- if (realBody.isNull()) {
- qCritical() << "No body tag found!" << endl;
- emit loadingFailed(i18n("No body tag found in file: %1", path));
- return;
- }
-
- m_body = KoXml::namedItemNS(realBody, KoXmlNS::office, "drawing");
-
- if (m_body.isNull()) {
- qCritical() << "No office:drawing tag found!" << endl;
- emit loadingFailed(i18n("No office:drawing tag found in file: %1", path));
- return;
- }
-
- m_page = m_body.firstChild().toElement();
-
- if (m_page.isNull()) {
- qCritical() << "No shapes found!" << endl;
- emit loadingFailed(i18n("No shapes found in file: %1", path));
- return;
- }
-
- m_shape = m_page.firstChild().toElement();
-
- if (m_shape.isNull()) {
- qCritical() << "No shapes found!" << endl;
- emit loadingFailed(i18n("No shapes found in file: %1", path));
- return;
- }
-
- m_loadingTimer->start();
-}
diff --git a/plugins/dockers/shapedockers/shapecollection/OdfCollectionLoader.h b/plugins/dockers/shapedockers/shapecollection/OdfCollectionLoader.h
deleted file mode 100644
index 50d8beb93a..0000000000
--- a/plugins/dockers/shapedockers/shapecollection/OdfCollectionLoader.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#ifndef KOODFCOLLECTIONLOADER_H
-#define KOODFCOLLECTIONLOADER_H
-
-#include <KoXmlReader.h>
-
-#include <QObject>
-#include <QList>
-#include <QStringList>
-
-class KoOdfReadStore;
-class KoOdfLoadingContext;
-class KoShapeLoadingContext;
-class QTimer;
-class KoShape;
-
-class KUrl;
-
-class OdfCollectionLoader : public QObject
-{
- Q_OBJECT
-public:
- explicit OdfCollectionLoader(const QString &path, QObject *parent = 0);
- ~OdfCollectionLoader();
-
- void load();
-
- QList<KoShape *> shapeList() const
- {
- return m_shapeList;
- }
- QString collectionPath() const
- {
- return m_path;
- }
-
-protected:
- void nextFile();
- void loadNativeFile(const QString &path);
- QString findMimeTypeByUrl(const KUrl &url);
-
-protected Q_SLOTS:
- void loadShape();
-
-private:
- KoOdfReadStore *m_odfStore;
- QTimer *m_loadingTimer;
- KoOdfLoadingContext *m_loadingContext;
- KoShapeLoadingContext *m_shapeLoadingContext;
- KoXmlElement m_body;
- KoXmlElement m_page;
- KoXmlElement m_shape;
- QList<KoShape *> m_shapeList;
- QString m_path;
- QStringList m_fileList;
-
-Q_SIGNALS:
- /**
- * Emitted when the loading failed
- * @param reason Reason the loading failed.
- */
- void loadingFailed(const QString &reason);
-
- void loadingFinished();
-};
-
-#endif //KOODFCOLLECTIONLOADER_H
diff --git a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp b/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp
index eeef53eda9..ff3a8cf707 100644
--- a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp
+++ b/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.cpp
@@ -1,571 +1,516 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "ShapeCollectionDocker.h"
#include "CollectionItemModel.h"
-#include "OdfCollectionLoader.h"
-#include "CollectionShapeFactory.h"
#include <KoShapeFactoryBase.h>
#include <KoShapeRegistry.h>
#include <KoCanvasController.h>
#include <KoToolManager.h>
#include <KoCreateShapesTool.h>
#include <KoShape.h>
#include <KoZoomHandler.h>
#include <KoShapePaintingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <KoResourcePaths.h>
#include <kdesktopfile.h>
#include <kconfiggroup.h>
#include <kmessagebox.h>
#include <ksharedconfig.h>
#include <QGridLayout>
#include <QListView>
#include <QListWidget>
#include <QStandardItemModel>
#include <QList>
#include <QSize>
#include <QToolButton>
#include <QDir>
#include <QMenu>
#include <QPainter>
#include <QDebug>
//This class is needed so that the menu returns a sizehint based on the layout and not on the number (0) of menu items
class CollectionMenu : public QMenu
{
public:
CollectionMenu(QWidget *parent = 0);
QSize sizeHint() const override;
};
CollectionMenu::CollectionMenu(QWidget *parent)
: QMenu(parent)
{
}
QSize CollectionMenu::sizeHint() const
{
return layout()->sizeHint();
}
//
// ShapeCollectionDockerFactory
//
ShapeCollectionDockerFactory::ShapeCollectionDockerFactory()
: KoDockFactoryBase()
{
}
QString ShapeCollectionDockerFactory::id() const
{
return QString("ShapeCollectionDocker");
}
QDockWidget *ShapeCollectionDockerFactory::createDockWidget()
{
ShapeCollectionDocker *docker = new ShapeCollectionDocker();
return docker;
}
void ShapeCollectionDocker::locationChanged(Qt::DockWidgetArea area)
{
resize(0, 0);
switch (area) {
case Qt::TopDockWidgetArea:
case Qt::BottomDockWidgetArea:
m_spacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding);
break;
case Qt::LeftDockWidgetArea:
case Qt::RightDockWidgetArea:
m_spacer->changeSize(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
break;
default:
break;
}
m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
m_layout->invalidate();
}
//
// ShapeCollectionDocker
//
ShapeCollectionDocker::ShapeCollectionDocker(QWidget *parent)
: QDockWidget(parent)
{
setWindowTitle(i18n("Add Shape"));
QWidget *mainWidget = new QWidget(this);
m_layout = new QGridLayout(mainWidget);
m_layout->setMargin(0);
m_layout->setHorizontalSpacing(0);
m_layout->setVerticalSpacing(0);
m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
setWidget(mainWidget);
m_quickView = new QListView(mainWidget);
m_layout->addWidget(m_quickView, 0, 0);
m_quickView->setViewMode(QListView::IconMode);
m_quickView->setDragDropMode(QListView::DragOnly);
m_quickView->setSelectionMode(QListView::SingleSelection);
m_quickView->setResizeMode(QListView::Adjust);
m_quickView->setFlow(QListView::LeftToRight);
m_quickView->setGridSize(QSize(40, 44));
m_quickView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_quickView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_quickView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_quickView->setTextElideMode(Qt::ElideNone);
m_quickView->setWordWrap(true);
m_spacer = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_layout->addItem(m_spacer, 1, 2);
m_layout->setRowStretch(1, 1);
m_layout->setColumnStretch(2, 1);
connect(this, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(locationChanged(Qt::DockWidgetArea)));
connect(m_quickView, SIGNAL(clicked(QModelIndex)),
this, SLOT(activateShapeCreationToolFromQuick(QModelIndex)));
m_moreShapes = new QToolButton(mainWidget);
m_moreShapes->setText(i18n("More"));
m_moreShapes->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_moreShapes->setIconSize(QSize(32, 32));
m_moreShapes->setIcon(koIcon("shape-choose"));
m_moreShapes->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_layout->addWidget(m_moreShapes, 0, 1);
m_moreShapesContainer = new CollectionMenu(mainWidget);
m_moreShapes->setMenu(m_moreShapesContainer);
m_moreShapes->setPopupMode(QToolButton::InstantPopup);
QGridLayout *containerLayout = new QGridLayout(m_moreShapesContainer);
containerLayout->setMargin(4);
m_collectionChooser = new QListWidget(m_moreShapesContainer);
containerLayout->addWidget(m_collectionChooser, 0, 0, 1, 2);
m_collectionChooser->setViewMode(QListView::IconMode);
m_collectionChooser->setSelectionMode(QListView::SingleSelection);
m_collectionChooser->setResizeMode(QListView::Adjust);
m_collectionChooser->setGridSize(QSize(75, 64));
m_collectionChooser->setMovement(QListView::Static);
m_collectionChooser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_collectionChooser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
connect(m_collectionChooser, SIGNAL(itemClicked(QListWidgetItem*)),
this, SLOT(activateShapeCollection(QListWidgetItem*)));
m_addCollectionButton = new QToolButton(m_moreShapesContainer);
containerLayout->addWidget(m_addCollectionButton, 1, 0);
m_addCollectionButton->setIcon(koIcon("list-add"));
m_addCollectionButton->setIconSize(QSize(16, 16));
m_addCollectionButton->setToolTip(i18n("Open Shape Collection"));
m_addCollectionButton->setPopupMode(QToolButton::InstantPopup);
m_addCollectionButton->setVisible(false);
m_closeCollectionButton = new QToolButton(m_moreShapesContainer);
containerLayout->addWidget(m_closeCollectionButton, 1, 1);
m_closeCollectionButton->setIcon(koIcon("list-remove"));
m_closeCollectionButton->setIconSize(QSize(16, 16));
m_closeCollectionButton->setToolTip(i18n("Remove Shape Collection"));
m_closeCollectionButton->setVisible(false);
connect(m_closeCollectionButton, SIGNAL(clicked()),
this, SLOT(removeCurrentCollection()));
if (!KoResourcePaths::resourceDirs("app_shape_collections").isEmpty()) {
buildAddCollectionMenu();
}
m_collectionView = new QListView(m_moreShapesContainer);
containerLayout->addWidget(m_collectionView, 0, 2, -1, 1);
m_collectionView->setViewMode(QListView::IconMode);
m_collectionView->setDragDropMode(QListView::DragOnly);
m_collectionView->setSelectionMode(QListView::SingleSelection);
m_collectionView->setResizeMode(QListView::Adjust);
m_collectionView->setGridSize(QSize(48 + 20, 48));
m_collectionView->setFixedSize(QSize(165, 345));
m_collectionView->setWordWrap(true);
connect(m_collectionView, SIGNAL(clicked(QModelIndex)),
this, SLOT(activateShapeCreationTool(QModelIndex)));
// Load the default shapes and add them to the combobox
loadDefaultShapes();
}
void ShapeCollectionDocker::setCanvas(KoCanvasBase *canvas)
{
setEnabled(canvas != 0);
}
void ShapeCollectionDocker::unsetCanvas()
{
setEnabled(false);
}
void ShapeCollectionDocker::loadDefaultShapes()
{
QList<KoCollectionItem> defaultList;
QList<KoCollectionItem> arrowList;
QList<KoCollectionItem> funnyList;
QList<KoCollectionItem> geometricList;
QList<KoCollectionItem> quicklist;
int quickCount = 0;
QStringList quickShapes;
quickShapes << "TextShapeID" << "PictureShape" << "ChartShape" << "ArtisticText";
KConfigGroup cfg = KSharedConfig::openConfig()->group("KoShapeCollection");
quickShapes = cfg.readEntry("QuickShapes", quickShapes);
Q_FOREACH (const QString &id, KoShapeRegistry::instance()->keys()) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
// don't show hidden factories
if (factory->hidden()) {
continue;
}
bool oneAdded = false;
Q_FOREACH (const KoShapeTemplate &shapeTemplate, factory->templates()) {
oneAdded = true;
KoCollectionItem temp;
temp.id = shapeTemplate.id;
temp.name = shapeTemplate.name;
temp.toolTip = shapeTemplate.toolTip;
temp.icon = KisIconUtils::loadIcon(shapeTemplate.iconName);
temp.properties = shapeTemplate.properties;
if (shapeTemplate.family == "funny") {
funnyList.append(temp);
} else if (shapeTemplate.family == "arrow") {
arrowList.append(temp);
} else if (shapeTemplate.family == "geometric") {
geometricList.append(temp);
} else {
defaultList.append(temp);
}
QString id = temp.id;
if (!shapeTemplate.templateId.isEmpty()) {
id += '_' + shapeTemplate.templateId;
}
if (quickShapes.contains(id)) {
quicklist.append(temp);
quickCount++;
}
}
if (!oneAdded) {
KoCollectionItem temp;
temp.id = factory->id();
temp.name = factory->name();
temp.toolTip = factory->toolTip();
temp.icon = KisIconUtils::loadIcon(factory->iconName());
temp.properties = 0;
if (factory->family() == "funny") {
funnyList.append(temp);
} else if (factory->family() == "arrow") {
arrowList.append(temp);
} else if (factory->family() == "geometric") {
geometricList.append(temp);
} else {
defaultList.append(temp);
}
if (quickShapes.contains(temp.id)) {
quicklist.append(temp);
quickCount++;
}
}
}
CollectionItemModel *model = new CollectionItemModel(this);
model->setShapeTemplateList(defaultList);
addCollection("default", i18n("Default"), model);
model = new CollectionItemModel(this);
model->setShapeTemplateList(geometricList);
addCollection("geometric", i18n("Geometrics"), model);
model = new CollectionItemModel(this);
model->setShapeTemplateList(arrowList);
addCollection("arrow", i18n("Arrows"), model);
model = new CollectionItemModel(this);
model->setShapeTemplateList(funnyList);
addCollection("funny", i18n("Funny"), model);
CollectionItemModel *quickModel = new CollectionItemModel(this);
quickModel->setShapeTemplateList(quicklist);
m_quickView->setModel(quickModel);
int fw = m_quickView->frameWidth();
m_quickView->setMaximumSize(QSize(quickCount * 40 + 2 * fw + 1, 44 + 2 * fw + 1));
m_quickView->setMinimumSize(QSize(quickCount * 40 + 2 * fw + 1, 44 + 2 * fw + 1));
m_collectionChooser->setMinimumSize(QSize(75 + 2 * fw, 0));
m_collectionChooser->setMaximumSize(QSize(75 + 2 * fw, 1000));
m_collectionChooser->setCurrentRow(0);
activateShapeCollection(m_collectionChooser->item(0));
}
void ShapeCollectionDocker::activateShapeCreationToolFromQuick(const QModelIndex &index)
{
m_collectionView->setFont(m_quickView->font());
if (!index.isValid()) {
return;
}
KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
if (canvasController) {
KoCreateShapesTool *tool = KoToolManager::instance()->shapeCreatorTool(canvasController->canvas());
QString id = m_quickView->model()->data(index, Qt::UserRole).toString();
const KoProperties *properties = static_cast<CollectionItemModel *>(m_quickView->model())->properties(index);
tool->setShapeId(id);
tool->setShapeProperties(properties);
KoToolManager::instance()->switchToolRequested(KoCreateShapesTool_ID);
}
m_quickView->clearSelection();
}
void ShapeCollectionDocker::activateShapeCreationTool(const QModelIndex &index)
{
if (!index.isValid()) {
return;
}
KoCanvasController *canvasController = KoToolManager::instance()->activeCanvasController();
if (canvasController) {
KoCreateShapesTool *tool = KoToolManager::instance()->shapeCreatorTool(canvasController->canvas());
QString id = m_collectionView->model()->data(index, Qt::UserRole).toString();
const KoProperties *properties = static_cast<CollectionItemModel *>(m_collectionView->model())->properties(index);
tool->setShapeId(id);
tool->setShapeProperties(properties);
KoToolManager::instance()->switchToolRequested(KoCreateShapesTool_ID);
}
m_moreShapesContainer->hide();
}
void ShapeCollectionDocker::activateShapeCollection(QListWidgetItem *item)
{
QString id = item->data(Qt::UserRole).toString();
if (m_modelMap.contains(id)) {
m_collectionView->setModel(m_modelMap[id]);
} else {
qCritical() << "Didn't find a model with id ==" << id;
}
m_closeCollectionButton->setEnabled(id != "default");
}
bool ShapeCollectionDocker::addCollection(const QString &id, const QString &title,
CollectionItemModel *model)
{
if (m_modelMap.contains(id)) {
return false;
}
m_modelMap.insert(id, model);
QListWidgetItem *collectionChooserItem = new QListWidgetItem(koIcon("shape-choose"), title);
collectionChooserItem->setData(Qt::UserRole, id);
m_collectionChooser->addItem(collectionChooserItem);
return true;
}
void ShapeCollectionDocker::buildAddCollectionMenu()
{
QStringList dirs = KoResourcePaths::resourceDirs("app_shape_collections");
QMenu *menu = new QMenu(m_addCollectionButton);
m_addCollectionButton->setMenu(menu);
Q_FOREACH (const QString &dirName, dirs) {
QDir dir(dirName);
if (!dir.exists()) {
continue;
}
QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
Q_FOREACH (const QString &collectionDirName, collectionDirs) {
scanCollectionDir(dirName + collectionDirName, menu);
}
}
}
void ShapeCollectionDocker::scanCollectionDir(const QString &path, QMenu *menu)
{
QDir dir(path);
if (!dir.exists(".directory")) {
return;
}
KDesktopFile directory(dir.absoluteFilePath(".directory"));
KConfigGroup dg = directory.desktopGroup();
QString name = dg.readEntry("Name");
QString icon = dg.readEntry("Icon");
QString type = dg.readEntry("X-KDE-DirType");
if (type == "subdir") {
QMenu *submenu = menu->addMenu(QIcon(dir.absoluteFilePath(icon)), name);
QStringList collectionDirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
Q_FOREACH (const QString &collectionDirName, collectionDirs) {
scanCollectionDir(dir.absoluteFilePath(collectionDirName), submenu);
}
} else {
QAction *action = menu->addAction(QIcon(dir.absoluteFilePath(icon)), name, this, SLOT(loadCollection()));
action->setIconText(name);
action->setData(QVariant(type + ':' + path + QDir::separator()));
action->setEnabled(!m_modelMap.contains(action->data().toString()));
}
}
void ShapeCollectionDocker::loadCollection()
{
QAction *action = qobject_cast<QAction *>(sender());
if (!action) {
return;
}
QString path = action->data().toString();
int index = path.indexOf(':');
QString type = path.left(index);
path = path.mid(index + 1);
if (m_modelMap.contains(path)) {
return;
}
CollectionItemModel *model = new CollectionItemModel(this);
addCollection(path, action->iconText(), model);
action->setEnabled(false);
-
- if (type == "odg-collection") {
- OdfCollectionLoader *loader = new OdfCollectionLoader(path, this);
- connect(loader, SIGNAL(loadingFailed(QString)),
- this, SLOT(onLoadingFailed(QString)));
- connect(loader, SIGNAL(loadingFinished()),
- this, SLOT(onLoadingFinished()));
-
- loader->load();
- }
-}
-
-void ShapeCollectionDocker::onLoadingFailed(const QString &reason)
-{
- OdfCollectionLoader *loader = qobject_cast<OdfCollectionLoader *>(sender());
-
- if (loader) {
- removeCollection(loader->collectionPath());
- QList<KoShape *> shapeList = loader->shapeList();
- qDeleteAll(shapeList);
- loader->deleteLater();
- }
-
- KMessageBox::error(this, reason, i18n("Collection Error"));
}
-void ShapeCollectionDocker::onLoadingFinished()
-{
- OdfCollectionLoader *loader = qobject_cast<OdfCollectionLoader *>(sender());
-
- if (!loader) {
- qWarning() << "Not called by a OdfCollectionLoader!";
- return;
- }
-
- QList<KoCollectionItem> templateList;
- QList<KoShape *> shapeList = loader->shapeList();
-
- Q_FOREACH (KoShape *shape, shapeList) {
- KoCollectionItem temp;
- temp.id = loader->collectionPath() + shape->name();
- temp.toolTip = shape->name();
- temp.icon = generateShapeIcon(shape);
- templateList.append(temp);
- CollectionShapeFactory *factory =
- new CollectionShapeFactory(loader->collectionPath() + shape->name(), shape);
- KoShapeRegistry::instance()->add(loader->collectionPath() + shape->name(), factory);
- }
-
- CollectionItemModel *model = m_modelMap[loader->collectionPath()];
- model->setShapeTemplateList(templateList);
-
- loader->deleteLater();
- //TODO m_collectionsCombo->setCurrentIndex(m_collectionsCombo->findData(loader->collectionPath()));
-}
QIcon ShapeCollectionDocker::generateShapeIcon(KoShape *shape)
{
KoZoomHandler converter;
qreal diffx = 30 / converter.documentToViewX(shape->size().width());
qreal diffy = 30 / converter.documentToViewY(shape->size().height());
converter.setZoom(qMin(diffx, diffy));
QPixmap pixmap(qRound(converter.documentToViewX(shape->size().width())) + 2, qRound(converter.documentToViewY(shape->size().height())) + 2);
pixmap.fill(Qt::white);
QPainter painter(&pixmap);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.translate(1, 1);
KoShapePaintingContext paintContext; //FIXME
shape->paint(painter, converter, paintContext);
painter.end();
return QIcon(pixmap);
}
void ShapeCollectionDocker::removeCollection(const QString &id)
{
//TODO m_collectionsCombo->removeItem(m_collectionsCombo->findData(id));
if (m_modelMap.contains(id)) {
CollectionItemModel *model = m_modelMap[id];
QList<KoCollectionItem> list = model->shapeTemplateList();
Q_FOREACH (const KoCollectionItem &temp, list) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->get(temp.id);
KoShapeRegistry::instance()->remove(temp.id);
delete factory;
}
m_modelMap.remove(id);
delete model;
}
}
void ShapeCollectionDocker::removeCurrentCollection()
{
//TODO removeCollection(m_collectionsCombo->itemData(m_collectionsCombo->currentIndex()).toString());
}
diff --git a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h b/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h
index 80b461a62e..10c71b8e1e 100644
--- a/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h
+++ b/plugins/dockers/shapedockers/shapecollection/ShapeCollectionDocker.h
@@ -1,130 +1,124 @@
/* This file is part of the KDE project
* Copyright (C) 2008 Peter Simonsson <peter.simonsson@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOSHAPECOLLECTIONDOCKER_H
#define KOSHAPECOLLECTIONDOCKER_H
#include <QDockWidget>
#include <QModelIndex>
#include <QMap>
#include <QIcon>
#include <KoDockFactoryBase.h>
#include <KoCanvasObserverBase.h>
class ShapeCollectionDockerFactory : public KoDockFactoryBase
{
public:
ShapeCollectionDockerFactory();
virtual QString id() const;
virtual QDockWidget *createDockWidget();
DockPosition defaultDockPosition() const
{
return DockRight;
}
};
class CollectionItemModel;
class KoShape;
class QListView;
class QListWidget;
class QListWidgetItem;
class QToolButton;
class QMenu;
class QSpacerItem;
class QGridLayout;
class ShapeCollectionDocker : public QDockWidget, public KoCanvasObserverBase
{
Q_OBJECT
public:
explicit ShapeCollectionDocker(QWidget *parent = 0);
/// reimplemented
virtual void setCanvas(KoCanvasBase *canvas);
virtual void unsetCanvas();
protected Q_SLOTS:
/**
* Activates the shape creation tool when a shape is selected.
*/
void activateShapeCreationTool(const QModelIndex &index);
void activateShapeCreationToolFromQuick(const QModelIndex &index);
/**
* Changes the current shape collection
*/
void activateShapeCollection(QListWidgetItem *item);
/**
* Called when a collection is added from the add collection menu
*/
void loadCollection();
- /// Called when an error occurred while loading a collection
- void onLoadingFailed(const QString &reason);
-
- /// Called when loading of a collection is finished
- void onLoadingFinished();
-
/// Called when the close collection button is clicked
void removeCurrentCollection();
/// Called when the docker changes area
void locationChanged(Qt::DockWidgetArea area);
protected:
/**
* Load the default calligra shapes
*/
void loadDefaultShapes();
/**
* Add a collection to the docker
*/
bool addCollection(const QString &id, const QString &title, CollectionItemModel *model);
void removeCollection(const QString &id);
/**
* Builds the menu for the Add Collection Button
*/
void buildAddCollectionMenu();
/// Generate an icon from @p shape
QIcon generateShapeIcon(KoShape *shape);
private:
void scanCollectionDir(const QString &dirName, QMenu *menu);
private:
QListView *m_quickView;
QToolButton *m_moreShapes;
QMenu *m_moreShapesContainer;
QListWidget *m_collectionChooser;
QListView *m_collectionView;
QToolButton *m_closeCollectionButton;
QToolButton *m_addCollectionButton;
QSpacerItem *m_spacer;
QGridLayout *m_layout;
QMap<QString, CollectionItemModel *> m_modelMap;
};
#endif //KOSHAPECOLLECTIONDOCKER_H
diff --git a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp b/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp
deleted file mode 100644
index bcaff6a69d..0000000000
--- a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.cpp
+++ /dev/null
@@ -1,189 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#include "ShapePropertiesDocker.h"
-#include <KoShape.h>
-#include <KoPathShape.h>
-#include <KoShapeConfigWidgetBase.h>
-#include <KoShapeManager.h>
-#include <KoShapeFactoryBase.h>
-#include <KoShapeRegistry.h>
-#include <KoCanvasBase.h>
-#include <KoCanvasController.h>
-#include <KoSelection.h>
-#include <KoParameterShape.h>
-#include <KoUnit.h>
-#include <KoCanvasResourceManager.h>
-
-#include <klocalizedstring.h>
-
-#include <QStackedWidget>
-#include <QPointer>
-
-class ShapePropertiesDocker::Private
-{
-public:
- Private()
- : widgetStack(0)
- , currentShape(0)
- , currentPanel(0)
- , canvas(0)
- {
- }
-
- QStackedWidget *widgetStack;
- KoShape *currentShape;
- KoShapeConfigWidgetBase *currentPanel;
- QPointer<KoCanvasBase> canvas;
-};
-
-ShapePropertiesDocker::ShapePropertiesDocker(QWidget *parent)
- : QDockWidget(i18n("Shape Properties")
- , parent)
- , d(new Private())
-{
- d->widgetStack = new QStackedWidget();
- setWidget(d->widgetStack);
-}
-
-ShapePropertiesDocker::~ShapePropertiesDocker()
-{
- delete d;
-}
-
-void ShapePropertiesDocker::unsetCanvas()
-{
- setEnabled(false);
- d->canvas = 0;
-}
-
-void ShapePropertiesDocker::setCanvas(KoCanvasBase *canvas)
-{
- setEnabled(canvas != 0);
-
- if (d->canvas) {
- d->canvas->disconnectCanvasObserver(this); // "Every connection you make emits a signal, so duplicate connections emit two signals"
- }
-
- d->canvas = canvas;
-
- if (d->canvas) {
- connect(d->canvas->shapeManager(), SIGNAL(selectionChanged()),
- this, SLOT(selectionChanged()));
- connect(d->canvas->shapeManager(), SIGNAL(selectionContentChanged()),
- this, SLOT(selectionChanged()));
- connect(d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
- this, SLOT(canvasResourceChanged(int,QVariant)));
- }
-}
-
-void ShapePropertiesDocker::selectionChanged()
-{
- if (!d->canvas) {
- return;
- }
-
- KoSelection *selection = d->canvas->shapeManager()->selection();
- if (selection && selection->count() == 1) {
- addWidgetForShape(selection->firstSelectedShape());
- } else {
- addWidgetForShape(0);
- }
-}
-
-void ShapePropertiesDocker::addWidgetForShape(KoShape *shape)
-{
- // remove the config widget if a null shape is set, or the shape has changed
- if (!shape || shape != d->currentShape) {
- while (d->widgetStack->count()) {
- d->widgetStack->removeWidget(d->widgetStack->widget(0));
- }
- }
-
- if (!shape) {
- d->currentShape = 0;
- d->currentPanel = 0;
- return;
- } else if (shape != d->currentShape) {
- // when a shape is set and is differs from the previous one
- // get the config widget and insert it into the option widget
- d->currentShape = shape;
- if (!d->currentShape) {
- return;
- }
- QString shapeId = shape->shapeId();
- KoPathShape *path = dynamic_cast<KoPathShape *>(shape);
- if (path) {
- // use the path specific shape id if shape is a path, otherwise use the shape id
- shapeId = path->pathShapeId();
- // check if we have an edited parametric shape, then we use the path shape id
- KoParameterShape *paramShape = dynamic_cast<KoParameterShape *>(shape);
- if (paramShape && ! paramShape->isParametricShape()) {
- shapeId = shape->shapeId();
- }
- }
- KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shapeId);
- if (!factory) {
- return;
- }
- QList<KoShapeConfigWidgetBase *> panels = factory->createShapeOptionPanels();
- if (!panels.count()) {
- return;
- }
-
- d->currentPanel = 0;
- uint panelCount = panels.count();
- for (uint i = 0; i < panelCount; ++i) {
- if (panels[i]->showOnShapeSelect()) {
- d->currentPanel = panels[i];
- break;
- }
- }
- if (d->currentPanel) {
- if (d->canvas) {
- d->currentPanel->setUnit(d->canvas->unit());
- }
- d->widgetStack->insertWidget(0, d->currentPanel);
- connect(d->currentPanel, SIGNAL(propertyChanged()),
- this, SLOT(shapePropertyChanged()));
- }
- }
-
- if (d->currentPanel) {
- d->currentPanel->open(shape);
- }
-}
-
-void ShapePropertiesDocker::shapePropertyChanged()
-{
- if (d->canvas && d->currentPanel) {
- KUndo2Command *cmd = d->currentPanel->createCommand();
- if (!cmd) {
- return;
- }
- d->canvas->addCommand(cmd);
- }
-}
-
-void ShapePropertiesDocker::canvasResourceChanged(int key, const QVariant &variant)
-{
- if (key == KoCanvasResourceManager::Unit && d->currentPanel) {
- d->currentPanel->setUnit(variant.value<KoUnit>());
- }
-}
diff --git a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.h b/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.h
deleted file mode 100644
index a8cbe7c312..0000000000
--- a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDocker.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/* 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 SHAPEPROPERTIESDOCKER_H
-#define SHAPEPROPERTIESDOCKER_H
-
-#include <KoDockFactoryBase.h>
-#include <KoCanvasObserverBase.h>
-#include <QDockWidget>
-
-class KoShape;
-
-/// The shape properties docker show the properties
-/// of the currently selected shape
-class ShapePropertiesDocker : public QDockWidget, public KoCanvasObserverBase
-{
- Q_OBJECT
-public:
- explicit ShapePropertiesDocker(QWidget *parent = 0);
- ~ShapePropertiesDocker();
- QString observerName()
- {
- return "ShapePropertiesDocker";
- }
- /// reimplemented
- virtual void setCanvas(KoCanvasBase *canvas);
- virtual void unsetCanvas();
-
-private Q_SLOTS:
- void selectionChanged();
- void addWidgetForShape(KoShape *shape);
- void shapePropertyChanged();
- virtual void canvasResourceChanged(int key, const QVariant &res);
-private:
- class Private;
- Private *const d;
-};
-
-#endif // SHAPEPROPERTIESDOCKER_H
diff --git a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDockerFactory.cpp b/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDockerFactory.cpp
deleted file mode 100644
index 3da5778218..0000000000
--- a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDockerFactory.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/* This file is part of the KDE project
- Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public License
- along with this library; see the file COPYING.LIB. If not, write to
- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
-*/
-
-#include "ShapePropertiesDockerFactory.h"
-#include "ShapePropertiesDocker.h"
-
-ShapePropertiesDockerFactory::ShapePropertiesDockerFactory()
-{
-}
-
-QString ShapePropertiesDockerFactory::id() const
-{
- return QString("Shape Properties");
-}
-
-QDockWidget *ShapePropertiesDockerFactory::createDockWidget()
-{
- ShapePropertiesDocker *widget = new ShapePropertiesDocker();
- widget->setObjectName(id());
-
- return widget;
-}
-
-KoDockFactoryBase::DockPosition ShapePropertiesDockerFactory::defaultDockPosition() const
-{
- return DockMinimized;
-}
diff --git a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDockerFactory.h b/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDockerFactory.h
deleted file mode 100644
index ee4c4d7dae..0000000000
--- a/plugins/dockers/shapedockers/shapeproperties/ShapePropertiesDockerFactory.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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 SHAPEPROPERTIESDOCKERFACTORY_H
-#define SHAPEPROPERTIESDOCKERFACTORY_H
-
-#include <KoDockFactoryBase.h>
-#include <QDockWidget>
-
-/// the factory which creates the shape properties docker
-class ShapePropertiesDockerFactory : public KoDockFactoryBase
-{
-public:
- ShapePropertiesDockerFactory();
-
- virtual QString id() const;
- virtual QDockWidget *createDockWidget();
- virtual DockPosition defaultDockPosition() const;
-
-};
-
-#endif // SHAPEPROPERTIESDOCKERFACTORY_H
diff --git a/plugins/extensions/CMakeLists.txt b/plugins/extensions/CMakeLists.txt
index a54f7a5405..4c4dff7f5a 100644
--- a/plugins/extensions/CMakeLists.txt
+++ b/plugins/extensions/CMakeLists.txt
@@ -1,26 +1,28 @@
add_subdirectory( bigbrother )
add_subdirectory( imagesplit )
add_subdirectory( clonesarray )
add_subdirectory( colorrange )
add_subdirectory( colorspaceconversion )
add_subdirectory( histogram )
add_subdirectory( imagesize )
add_subdirectory( metadataeditor )
add_subdirectory( modify_selection )
add_subdirectory( offsetimage )
add_subdirectory( rotateimage )
add_subdirectory( separate_channels )
add_subdirectory( shearimage )
add_subdirectory( layergroupswitcher )
add_subdirectory( resourcemanager )
add_subdirectory( layersplit )
add_subdirectory( animationrenderer )
add_subdirectory( waveletdecompose )
# Allow to skip building GMIC plugin
option(WITH_GMIC "Build the G'Mic plugin" ON)
if(WITH_GMIC)
if (CMAKE_COMPILER_IS_GNUCC)
add_subdirectory( gmic )
endif()
endif()
+
+add_subdirectory( pykrita )
diff --git a/plugins/extensions/imagesize/dlg_canvassize.cc b/plugins/extensions/imagesize/dlg_canvassize.cc
index af146bd26b..b394a041d9 100644
--- a/plugins/extensions/imagesize/dlg_canvassize.cc
+++ b/plugins/extensions/imagesize/dlg_canvassize.cc
@@ -1,705 +1,469 @@
/*
*
* 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 <KoUnit.h>
#include <kis_icon.h>
#include <kis_size_group.h>
#include <klocalizedstring.h>
#include <kis_config.h>
+#include <kis_document_aware_spin_box_unit_manager.h>
+
+#include <QComboBox>
+
// used to extend KoUnit in comboboxes
static const QString percentStr(i18n("Percent (%)"));
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)
+ : 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");
- m_page->newWidth->setValue(width);
- m_page->newWidth->setFocus();
- m_page->newHeight->setValue(height);
+ _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
+ _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
+
+ _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->setVisible(false);
- m_page->newHeightDouble->setVisible(false);
+ m_page->newWidthDouble->setValue(width);
+ m_page->newWidthDouble->setFocus();
+ m_page->newHeightDouble->setValue(height);
- m_page->widthUnit->addItems(KoUnit::listOfUnitNameForUi());
- m_page->widthUnit->addItem(percentStr);
- m_page->heightUnit->addItems(KoUnit::listOfUnitNameForUi());
- m_page->heightUnit->addItem(percentStr);
+ m_page->widthUnit->setModel(_widthUnitManager);
+ m_page->heightUnit->setModel(_heightUnitManager);
- const int pixelUnitIndex = KoUnit(KoUnit::Pixel).indexInListForUi();
+ const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units.
m_page->widthUnit->setCurrentIndex(pixelUnitIndex);
m_page->heightUnit->setCurrentIndex(pixelUnitIndex);
- m_page->xOffsetDouble->setVisible(false);
- m_page->yOffsetDouble->setVisible(false);
+ _xOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
+ _yOffsetUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
- m_page->xOffUnit->addItems(KoUnit::listOfUnitNameForUi());
- m_page->xOffUnit->addItem(percentStr);
- m_page->yOffUnit->addItems(KoUnit::listOfUnitNameForUi());
- m_page->yOffUnit->addItem(percentStr);
+ _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->xOffUnit->setCurrentIndex(pixelUnitIndex);
m_page->yOffUnit->setCurrentIndex(pixelUnitIndex);
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->xOffsetDouble->changeValue(m_xOffset);
+ m_page->yOffsetDouble->changeValue(m_yOffset);
+
KisConfig cfg;
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->newWidth);
spinboxesGroup->addWidget(m_page->newWidthDouble);
- spinboxesGroup->addWidget(m_page->newHeight);
spinboxesGroup->addWidget(m_page->newHeightDouble);
- spinboxesGroup->addWidget(m_page->xOffset);
spinboxesGroup->addWidget(m_page->xOffsetDouble);
- spinboxesGroup->addWidget(m_page->yOffset);
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->newWidth, SIGNAL(valueChanged(int)), this, SLOT(slotWidthChanged(int)));
- connect(m_page->newHeight, SIGNAL(valueChanged(int)), this, SLOT(slotHeightChanged(int)));
- connect(m_page->newWidthDouble, SIGNAL(valueChanged(double)), this, SLOT(slotWidthChanged(double)));
- connect(m_page->newHeightDouble, SIGNAL(valueChanged(double)), this, SLOT(slotHeightChanged(double)));
- connect(m_page->widthUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWidthUnitChanged(int)));
- connect(m_page->heightUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotHeightUnitChanged(int)));
+ 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->xOffset, SIGNAL(valueChanged(int)), this, SLOT(slotXOffsetChanged(int)));
- connect(m_page->yOffset, SIGNAL(valueChanged(int)), this, SLOT(slotYOffsetChanged(int)));
- connect(m_page->xOffsetDouble, SIGNAL(valueChanged(double)), this, SLOT(slotXOffsetChanged(double)));
- connect(m_page->yOffsetDouble, SIGNAL(valueChanged(double)), this, SLOT(slotYOffsetChanged(double)));
- connect(m_page->xOffUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotXOffsetUnitChanged(int)));
- connect(m_page->yOffUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotYOffsetUnitChanged(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_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;
cfg.writeEntry<bool>("CanvasSize/KeepAspectRatio", m_page->aspectRatioBtn->keepAspectRatio());
cfg.writeEntry<bool>("CanvasSize/ConstrainProportions", m_page->constrainProportionsCkb->isChecked());
delete m_page;
}
qint32 DlgCanvasSize::width()
{
- return (qint32)m_newWidth;
+ return (qint32) m_newWidth;
}
qint32 DlgCanvasSize::height()
{
- return (qint32)m_newHeight;
+ return (qint32) m_newHeight;
}
qint32 DlgCanvasSize::xOffset()
{
- return (qint32)m_page->xOffset->value();
+ return (qint32) m_page->xOffsetDouble->value();
}
qint32 DlgCanvasSize::yOffset()
{
- return (qint32)m_page->yOffset->value();
+ return (qint32) m_page->yOffsetDouble->value();
}
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;
- updateWidthUIValue(m_newWidth);
- updateHeightUIValue(m_newHeight);
- updateXOffsetUIValue(m_xOffset);
- updateYOffsetUIValue(m_yOffset);
-
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(int v)
-{
- slotWidthChanged((double) v);
-}
-
-void DlgCanvasSize::slotHeightChanged(int v)
-{
- slotHeightChanged((double) v);
-}
-
void DlgCanvasSize::slotWidthChanged(double v)
{
- if (m_page->widthUnit->currentText() == percentStr) {
- m_newWidth = qRound((v * m_originalWidth) / 100.0);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->widthUnit->currentIndex());
- const double resValue = (selectedUnit == KoUnit(KoUnit::Pixel)) ? v : (v * m_resolution);
- m_newWidth = qRound(selectedUnit.fromUserValue(resValue));
- }
+ //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);
- updateHeightUIValue(m_newHeight);
+ 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)
{
- if (m_page->heightUnit->currentText() == percentStr) {
- m_newHeight = qRound((v * m_originalHeight) / 100.0);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->heightUnit->currentIndex());
- const double resValue = (selectedUnit == KoUnit(KoUnit::Pixel)) ? v : (v * m_resolution);
- m_newHeight = qRound(selectedUnit.fromUserValue(resValue));
- }
+ //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);
- updateWidthUIValue(m_newWidth);
+ 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::slotWidthUnitChanged(int index)
-{
- updateWidthUIValue(m_newWidth);
-
- if (m_page->widthUnit->currentText() == percentStr) {
- m_page->newWidth->setVisible(false);
- m_page->newWidthDouble->setVisible(true);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(index);
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newWidth->setVisible(false);
- m_page->newWidthDouble->setVisible(true);
- } else {
- m_page->newWidth->setVisible(true);
- m_page->newWidthDouble->setVisible(false);
- }
- }
-}
-
-void DlgCanvasSize::slotHeightUnitChanged(int index)
-{
- updateHeightUIValue(m_newHeight);
-
- if (m_page->heightUnit->currentText() == percentStr) {
- m_page->newHeight->setVisible(false);
- m_page->newHeightDouble->setVisible(true);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(index);
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newHeight->setVisible(false);
- m_page->newHeightDouble->setVisible(true);
- } else {
- m_page->newHeight->setVisible(true);
- m_page->newHeightDouble->setVisible(false);
- }
- }
-}
-
-void DlgCanvasSize::slotXOffsetChanged(int v)
-{
- slotXOffsetChanged((double) v);
-}
-
-void DlgCanvasSize::slotYOffsetChanged(int v)
-{
- slotYOffsetChanged((double) v);
-}
-
void DlgCanvasSize::slotXOffsetChanged(double v)
{
- if (m_page->xOffUnit->currentText() == percentStr) {
- m_xOffset = qRound((v * m_newWidth) / 100.0);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex());
- const double resValue = (selectedUnit == KoUnit(KoUnit::Pixel)) ? v : (v * m_resolution);
- m_xOffset = qRound(selectedUnit.fromUserValue(resValue));
- }
+ //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)
{
- if (m_page->yOffUnit->currentText() == percentStr) {
- m_yOffset = qRound((v * m_newHeight) / 100.0);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex());
- const double resValue = (selectedUnit == KoUnit(KoUnit::Pixel)) ? v : (v * m_resolution);
- m_yOffset = qRound(selectedUnit.fromUserValue(resValue));
- }
+ //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::slotXOffsetUnitChanged(int index)
-{
- updateXOffsetUIValue(m_xOffset);
-
- if (m_page->xOffUnit->currentText() == percentStr) {
- m_page->xOffset->setVisible(false);
- m_page->xOffsetDouble->setVisible(true);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(index);
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->xOffset->setVisible(false);
- m_page->xOffsetDouble->setVisible(true);
- } else {
- m_page->xOffset->setVisible(true);
- m_page->xOffsetDouble->setVisible(false);
- }
- }
-}
-
-void DlgCanvasSize::slotYOffsetUnitChanged(int index)
-{
- updateYOffsetUIValue(m_yOffset);
-
- if (m_page->yOffUnit->currentText() == percentStr) {
- m_page->yOffset->setVisible(false);
- m_page->yOffsetDouble->setVisible(true);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(index);
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->yOffset->setVisible(false);
- m_page->yOffsetDouble->setVisible(true);
- } else {
- m_page->yOffset->setVisible(true);
- m_page->yOffsetDouble->setVisible(false);
- }
- }
-}
-
void DlgCanvasSize::slotCanvasPreviewXOffsetChanged(int v)
{
- // Convert input value to selected x offset unit.
- // This will be undone later in slotXOffsetChanged (through spinboxes valueChanged signal).
- if (m_page->xOffUnit->currentText() == percentStr) {
- m_page->xOffsetDouble->setValue((v * 100.0) / m_newWidth);
- } else {
- const KoUnit pixelUnit(KoUnit::Pixel);
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex());
- //const double convertedValue = xOffsetUnit.convertFromUnitToUnit(v, pixelUnit, xOffsetUnit);
-
- if (selectedUnit != pixelUnit) {
- m_page->xOffsetDouble->setValue(selectedUnit.toUserValue(v / m_resolution));
- } else {
- m_page->xOffset->setValue(v);
- }
- }
+ double newVal = v / _xOffsetUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
+ m_page->xOffsetDouble->changeValue(newVal);
}
void DlgCanvasSize::slotCanvasPreviewYOffsetChanged(int v)
{
- // Convert input value to selected y offset unit.
- // This will be undone later in slotYOffsetChanged (through spinboxes valueChanged signal).
- if (m_page->yOffUnit->currentText() == percentStr) {
- m_page->yOffsetDouble->setValue((v * 100.0) / m_newHeight);
- } else {
- const KoUnit pixelUnit(KoUnit::Pixel);
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex());
- //const double convertedValue = yOffsetUnit.convertFromUnitToUnit(v, pixelUnit, yOffsetUnit);
- if (selectedUnit != pixelUnit) {
- m_page->yOffsetDouble->setValue(selectedUnit.toUserValue(v / m_resolution));
- } else {
- m_page->yOffset->setValue(v);
- }
- }
-}
-
-void DlgCanvasSize::updateWidthUIValue(double value)
-{
- if (m_page->widthUnit->currentText() == percentStr) {
- m_page->newWidthDouble->blockSignals(true);
- m_page->newWidthDouble->setValue((value * 100.0) / m_originalWidth);
- m_page->newWidthDouble->blockSignals(false);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->widthUnit->currentIndex());
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newWidthDouble->blockSignals(true);
- m_page->newWidthDouble->setValue(selectedUnit.toUserValue(value / m_resolution));
- m_page->newWidthDouble->blockSignals(false);
- } else {
- m_page->newWidth->blockSignals(true);
- m_page->newWidth->setValue(selectedUnit.toUserValue(value));
- m_page->newWidth->blockSignals(false);
- }
- }
-}
-
-void DlgCanvasSize::updateHeightUIValue(double value)
-{
- if (m_page->heightUnit->currentText() == percentStr) {
- m_page->newHeightDouble->blockSignals(true);
- m_page->newHeightDouble->setValue((value * 100.0) / m_originalHeight);
- m_page->newHeightDouble->blockSignals(false);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->heightUnit->currentIndex());
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newHeightDouble->blockSignals(true);
- m_page->newHeightDouble->setValue(selectedUnit.toUserValue(value / m_resolution));
- m_page->newHeightDouble->blockSignals(false);
- } else {
- m_page->newHeight->blockSignals(true);
- m_page->newHeight->setValue(selectedUnit.toUserValue(value));
- m_page->newHeight->blockSignals(false);
- }
- }
-}
-
-void DlgCanvasSize::updateXOffsetUIValue(double value)
-{
- if (m_page->xOffUnit->currentText() == percentStr) {
- m_page->xOffsetDouble->blockSignals(true);
- m_page->xOffsetDouble->setValue((value * 100.0) / m_newWidth);
- m_page->xOffsetDouble->blockSignals(false);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex());
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->xOffsetDouble->blockSignals(true);
- m_page->xOffsetDouble->setValue(selectedUnit.toUserValue(value / m_resolution));
- m_page->xOffsetDouble->blockSignals(false);
- } else {
- m_page->xOffset->blockSignals(true);
- m_page->xOffset->setValue(qRound(selectedUnit.toUserValue(value)));
- m_page->xOffset->blockSignals(false);
- }
- }
-}
-
-void DlgCanvasSize::updateYOffsetUIValue(double value)
-{
- if (m_page->yOffUnit->currentText() == percentStr) {
- m_page->yOffsetDouble->blockSignals(true);
- m_page->yOffsetDouble->setValue((value * 100.0) / m_newHeight);
- m_page->yOffsetDouble->blockSignals(false);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex());
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->yOffsetDouble->blockSignals(true);
- m_page->yOffsetDouble->setValue(selectedUnit.toUserValue(value / m_resolution));
- m_page->yOffsetDouble->blockSignals(false);
- } else {
- m_page->yOffset->blockSignals(true);
- m_page->yOffset->setValue(qRound(selectedUnit.toUserValue(value)));
- m_page->yOffset->blockSignals(false);
- }
- }
+ 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;
+ 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);
- const KoUnit pixelUnit(KoUnit::Pixel);
-
- // update spinbox value (other widgets will be autoupdated later through valueChanged signal)
- if (m_page->xOffUnit->currentText() == percentStr) {
- m_page->xOffsetDouble->setValue(xOffset);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex());
- if (pixelUnit != selectedUnit) {
- m_page->xOffsetDouble->setValue(xOffset);
- } else {
- m_page->xOffset->setValue(qRound(xOffset));
- }
- }
-
- // update spinbox value (other widgets will be autoupdated later through valueChanged signal)
- if (m_page->yOffUnit->currentText() == percentStr) {
- m_page->yOffsetDouble->setValue(yOffset);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex());
- if (pixelUnit != selectedUnit) {
- m_page->yOffsetDouble->setValue(yOffset);
- } else {
- m_page->yOffset->setValue(qRound(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.5;
const int xDiff = m_newWidth - m_originalWidth;
const int yDiff = m_newHeight - m_originalHeight;
- // use selected unit to convert expected values (the inverse will be do later)
- // so output values are now considered as if they were regular user input
- if (m_page->xOffUnit->currentText() == percentStr) {
- xOffset = (xDiff * xCoeff * 100.0) / m_newWidth;
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->xOffUnit->currentIndex());
- const double resXDiff = (selectedUnit != KoUnit(KoUnit::Pixel)) ? xDiff / m_resolution : xDiff;
- xOffset = selectedUnit.toUserValue(resXDiff * xCoeff);
- }
-
- if (m_page->yOffUnit->currentText() == percentStr) {
- yOffset = (yDiff * yCoeff * 100.0) / m_newHeight;
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->yOffUnit->currentIndex());
- const double resYDiff = (selectedUnit != KoUnit(KoUnit::Pixel)) ? yDiff / m_resolution : yDiff;
- yOffset = selectedUnit.toUserValue(resYDiff * yCoeff);
- }
+ //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/imagesize/dlg_canvassize.h b/plugins/extensions/imagesize/dlg_canvassize.h
index 9b2ea69641..91ea435bed 100644
--- a/plugins/extensions/imagesize/dlg_canvassize.h
+++ b/plugins/extensions/imagesize/dlg_canvassize.h
@@ -1,106 +1,100 @@
/*
*
* 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.
*/
#ifndef DLG_CANVASSIZE
#define DLG_CANVASSIZE
#include <KoDialog.h>
#include <QIcon>
#include "ui_wdg_canvassize.h"
+class KisDocumentAwareSpinBoxUnitManager;
+
class WdgCanvasSize : public QWidget, public Ui::WdgCanvasSize
{
Q_OBJECT
public:
WdgCanvasSize(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class DlgCanvasSize: public KoDialog
{
Q_OBJECT
public:
enum anchor { NORTH_WEST = 0, NORTH, NORTH_EAST, WEST, CENTER, EAST, SOUTH_WEST, SOUTH, SOUTH_EAST, NONE};
DlgCanvasSize(QWidget * parent, int width, int height, double resolution);
~DlgCanvasSize();
qint32 width();
qint32 height();
qint32 xOffset();
qint32 yOffset();
private Q_SLOTS:
void slotAspectChanged(bool keep);
void slotAnchorButtonClicked(int id);
- void slotWidthChanged(int v);
- void slotHeightChanged(int v);
void slotWidthChanged(double v);
void slotHeightChanged(double v);
- void slotWidthUnitChanged(int index);
- void slotHeightUnitChanged(int index);
-
- void slotXOffsetChanged(int v);
- void slotYOffsetChanged(int v);
void slotXOffsetChanged(double v);
void slotYOffsetChanged(double v);
- void slotXOffsetUnitChanged(int index);
- void slotYOffsetUnitChanged(int index);
-
void slotCanvasPreviewXOffsetChanged(int v);
void slotCanvasPreviewYOffsetChanged(int v);
private:
- void updateWidthUIValue(double value);
- void updateHeightUIValue(double value);
- void updateXOffsetUIValue(double value);
- void updateYOffsetUIValue(double value);
void loadAnchorIcons();
void updateAnchorIcons(int id);
void updateButtons(int forceId);
void updateOffset(int id);
void expectedOffset(int id, double &xOffset, double &yOffset);
bool m_keepAspect;
const double m_aspectRatio;
const double m_resolution;
const int m_originalWidth, m_originalHeight;
int m_newWidth, m_newHeight;
int m_xOffset, m_yOffset;
WdgCanvasSize * m_page;
QIcon m_anchorIcons[9];
QButtonGroup *m_group;
+
+ KisDocumentAwareSpinBoxUnitManager* _widthUnitManager;
+ KisDocumentAwareSpinBoxUnitManager* _heightUnitManager;
+
+ KisDocumentAwareSpinBoxUnitManager* _xOffsetUnitManager;
+ KisDocumentAwareSpinBoxUnitManager* _yOffsetUnitManager;
};
#endif // DLG_CANVASSIZE
diff --git a/plugins/extensions/imagesize/dlg_imagesize.cc b/plugins/extensions/imagesize/dlg_imagesize.cc
index 1d38c57bdc..846c7fa66b 100644
--- a/plugins/extensions/imagesize/dlg_imagesize.cc
+++ b/plugins/extensions/imagesize/dlg_imagesize.cc
@@ -1,493 +1,422 @@
/*
* dlg_imagesize.cc - part of KimageShop^WKrayon^WKrita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2009 C. Boemann <cbo@boemann.dk>
* 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_imagesize.h"
#include <QLocale>
#include <KoUnit.h>
#include <kis_size_group.h>
#include <klocalizedstring.h>
#include <kis_filter_strategy.h>
+#include "kis_double_parse_unit_spin_box.h"
+#include "kis_document_aware_spin_box_unit_manager.h"
+
static const QString pixelStr(KoUnit::unitDescription(KoUnit::Pixel));
static const QString percentStr(i18n("Percent (%)"));
static const QString pixelsInchStr(i18n("Pixels/Inch"));
static const QString pixelsCentimeterStr(i18n("Pixels/Centimeter"));
DlgImageSize::DlgImageSize(QWidget *parent, int width, int height, double resolution)
: KoDialog(parent)
, m_aspectRatio(((double) width) / height)
, m_originalWidth(width)
, m_originalHeight(height)
, m_width(width)
, m_height(height)
, m_printWidth(width / resolution)
, m_printHeight(height / resolution)
, m_originalResolution(resolution)
, m_resolution(resolution)
, m_keepAspect(true)
{
setCaption(i18n("Scale To New Size"));
setButtons(Ok | Cancel);
setDefaultButton(Ok);
m_page = new WdgImageSize(this);
Q_CHECK_PTR(m_page);
m_page->layout()->setMargin(0);
m_page->setObjectName("image_size");
- m_page->pixelWidth->setValue(width);
- m_page->pixelWidth->setFocus();
- m_page->pixelHeight->setValue(height);
+ _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
+ _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
+
+ //configure the unit to image length, default unit is pixel and printing units are forbiden.
+ _widthUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH);
+ _heightUnitManager->setUnitDimension(KisSpinBoxUnitManager::IMLENGTH);
+
+ m_page->pixelWidthDouble->setUnitManager(_widthUnitManager);
+ m_page->pixelHeightDouble->setUnitManager(_heightUnitManager);
+
+ m_page->pixelWidthDouble->changeValue(width);
+ m_page->pixelHeightDouble->changeValue(height);
+ m_page->pixelWidthDouble->setDecimals(2);
+ m_page->pixelHeightDouble->setDecimals(2);
+ m_page->pixelWidthDouble->setDisplayUnit(false);
+ m_page->pixelHeightDouble->setDisplayUnit(false);
- m_page->pixelWidthDouble->setVisible(false);
- m_page->pixelHeightDouble->setVisible(false);
+ m_page->pixelWidthUnit->setModel(_widthUnitManager);
+ m_page->pixelHeightUnit->setModel(_widthUnitManager);
+ m_page->pixelWidthUnit->setCurrentText("px");
+ m_page->pixelHeightUnit->setCurrentText("px");
m_page->pixelFilterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys());
m_page->pixelFilterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formatedDescriptions());
m_page->pixelFilterCmb->setCurrent("Bicubic");
- m_page->pixelWidthUnit->addItem(pixelStr);
- m_page->pixelWidthUnit->addItem(percentStr);
- m_page->pixelWidthUnit->setCurrentIndex(0);
+ _printWidthUnitManager = new KisSpinBoxUnitManager(this);
+ _printHeightUnitManager = new KisSpinBoxUnitManager(this);
- m_page->pixelHeightUnit->addItem(pixelStr);
- m_page->pixelHeightUnit->addItem(percentStr);
- m_page->pixelHeightUnit->setCurrentIndex(0);
+ m_page->printWidth->setUnitManager(_printWidthUnitManager);
+ m_page->printHeight->setUnitManager(_printHeightUnitManager);
+ m_page->printWidth->setDecimals(2);
+ m_page->printHeight->setDecimals(2);
+ m_page->printWidth->setDisplayUnit(false);
+ m_page->printHeight->setDisplayUnit(false);
+ m_page->printResolution->setDecimals(2);
+ m_page->printResolution->setAlignment(Qt::AlignRight);
- m_page->printWidthUnit->addItems(KoUnit::listOfUnitNameForUi(KoUnit::HidePixel));
- m_page->printWidthUnit->addItem(percentStr);
- m_page->printHeightUnit->addItems(KoUnit::listOfUnitNameForUi(KoUnit::HidePixel));
- m_page->printHeightUnit->addItem(percentStr);
+ m_page->printWidthUnit->setModel(_printWidthUnitManager);
+ m_page->printHeightUnit->setModel(_printHeightUnitManager);
+ m_page->printWidth->changeValue(m_printWidth);
+ m_page->printHeight->changeValue(m_printHeight);
+
+ //TODO: create a resolution dimension in the unit manager.
m_page->printResolutionUnit->addItem(pixelsInchStr);
m_page->printResolutionUnit->addItem(pixelsCentimeterStr);
- // pick selected print units from user locale
- if (QLocale().measurementSystem() == QLocale::MetricSystem) {
- const int unitIndex = KoUnit(KoUnit::Centimeter).indexInListForUi(KoUnit::HidePixel);
- m_page->printWidthUnit->setCurrentIndex(unitIndex);
- m_page->printHeightUnit->setCurrentIndex(unitIndex);
- m_page->printResolutionUnit->setCurrentIndex(0); // Pixels/Centimeter
- } else { // Imperial
- const int unitIndex = KoUnit(KoUnit::Inch).indexInListForUi(KoUnit::HidePixel);
- m_page->printWidthUnit->setCurrentIndex(unitIndex);
- m_page->printHeightUnit->setCurrentIndex(unitIndex);
- m_page->printResolutionUnit->setCurrentIndex(1); // Pixels/Inch
- }
- updatePrintWidthUIValue(m_printWidth);
- updatePrintHeightUIValue(m_printHeight);
- updatePrintResolutionUIValue(m_resolution);
-
m_page->pixelAspectRatioBtn->setKeepAspectRatio(true);
m_page->printAspectRatioBtn->setKeepAspectRatio(true);
m_page->constrainProportionsCkb->setChecked(true);
KisSizeGroup *labelsGroup = new KisSizeGroup(this);
labelsGroup->addWidget(m_page->lblPixelWidth);
labelsGroup->addWidget(m_page->lblPixelHeight);
labelsGroup->addWidget(m_page->lblPixelFilter);
labelsGroup->addWidget(m_page->lblPrintWidth);
labelsGroup->addWidget(m_page->lblPrintHeight);
labelsGroup->addWidget(m_page->lblResolution);
KisSizeGroup *spinboxesGroup = new KisSizeGroup(this);
- spinboxesGroup->addWidget(m_page->pixelWidth);
spinboxesGroup->addWidget(m_page->pixelWidthDouble);
- spinboxesGroup->addWidget(m_page->pixelHeight);
spinboxesGroup->addWidget(m_page->pixelHeightDouble);
spinboxesGroup->addWidget(m_page->printWidth);
spinboxesGroup->addWidget(m_page->printHeight);
spinboxesGroup->addWidget(m_page->printResolution);
KisSizeGroup *comboboxesGroup = new KisSizeGroup(this);
comboboxesGroup->addWidget(m_page->pixelWidthUnit);
comboboxesGroup->addWidget(m_page->pixelHeightUnit);
comboboxesGroup->addWidget(m_page->printWidthUnit);
comboboxesGroup->addWidget(m_page->printHeightUnit);
comboboxesGroup->addWidget(m_page->printResolutionUnit);
connect(this, SIGNAL(okClicked()), this, SLOT(accept()));
connect(m_page->pixelAspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool)));
connect(m_page->printAspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool)));
connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool)));
- connect(m_page->pixelWidth, SIGNAL(valueChanged(int)), this, SLOT(slotPixelWidthChanged(int)));
- connect(m_page->pixelHeight, SIGNAL(valueChanged(int)), this, SLOT(slotPixelHeightChanged(int)));
- connect(m_page->pixelWidthDouble, SIGNAL(valueChanged(double)), this, SLOT(slotPixelWidthChanged(double)));
- connect(m_page->pixelHeightDouble, SIGNAL(valueChanged(double)), this, SLOT(slotPixelHeightChanged(double)));
- connect(m_page->pixelWidthUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPixelWidthUnitChanged()));
- connect(m_page->pixelHeightUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPixelHeightUnitChanged()));
+ connect(m_page->pixelWidthDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotPixelWidthChanged(double)));
+ connect(m_page->pixelHeightDouble, SIGNAL(valueChangedPt(double)), this, SLOT(slotPixelHeightChanged(double)));
+ connect(m_page->pixelWidthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(m_page->pixelHeightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->pixelWidthUnit, SLOT(setCurrentIndex(int)));
+ connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->pixelHeightUnit, SLOT(setCurrentIndex(int)));
- connect(m_page->printWidth, SIGNAL(valueChanged(double)), this, SLOT(slotPrintWidthChanged(double)));
- connect(m_page->printHeight, SIGNAL(valueChanged(double)), this, SLOT(slotPrintHeightChanged(double)));
- connect(m_page->printWidthUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPrintWidthUnitChanged()));
- connect(m_page->printHeightUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPrintHeightUnitChanged()));
+ connect(m_page->printWidth, SIGNAL(valueChangedPt(double)), this, SLOT(slotPrintWidthChanged(double)));
+ connect(m_page->printHeight, SIGNAL(valueChangedPt(double)), this, SLOT(slotPrintHeightChanged(double)));
+ connect(m_page->printWidthUnit, SIGNAL(currentIndexChanged(int)), _printWidthUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(m_page->printHeightUnit, SIGNAL(currentIndexChanged(int)), _printHeightUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(_printWidthUnitManager, SIGNAL(unitChanged(int)), m_page->printWidthUnit, SLOT(setCurrentIndex(int)));
+ connect(_printHeightUnitManager, SIGNAL(unitChanged(int)), m_page->printHeightUnit, SLOT(setCurrentIndex(int)));
connect(m_page->printResolution, SIGNAL(valueChanged(double)), this, SLOT(slotPrintResolutionChanged(double)));
connect(m_page->printResolution, SIGNAL(editingFinished()), this, SLOT(slotPrintResolutionEditFinished()));
connect(m_page->printResolutionUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotPrintResolutionUnitChanged()));
+ // pick selected print units from user locale (after slots connection, so the spinbox will be updated too).
+ if (QLocale().measurementSystem() == QLocale::MetricSystem) {
+ m_page->printWidthUnit->setCurrentText("cm");
+ m_page->printHeightUnit->setCurrentText("cm");
+ m_page->printResolutionUnit->setCurrentIndex(0); // Pixels/Centimeter
+ slotPrintResolutionUnitChanged(); //ensure the resolution is updated, even if the index didn't changed.
+ } else { // Imperial
+ m_page->printWidthUnit->setCurrentText("in");
+ m_page->printHeightUnit->setCurrentText("in");
+ m_page->printResolutionUnit->setCurrentIndex(1); // Pixels/Inch
+ slotPrintResolutionUnitChanged(); //ensure the resolution is updated, even if the index didn't changed.
+ }
+
setMainWidget(m_page);
}
DlgImageSize::~DlgImageSize()
{
delete m_page;
}
qint32 DlgImageSize::width()
{
return (qint32)m_width;
}
qint32 DlgImageSize::height()
{
return (qint32)m_height;
}
double DlgImageSize::resolution()
{
return m_resolution;
}
KisFilterStrategy *DlgImageSize::filterType()
{
KoID filterID = m_page->pixelFilterCmb->currentItem();
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id());
return filter;
}
// SLOTS
-void DlgImageSize::slotPixelWidthChanged(int w)
-{
- slotPixelWidthChanged((double) w);
-}
-
-void DlgImageSize::slotPixelHeightChanged(int h)
-{
- slotPixelHeightChanged((double) h);
-}
-
void DlgImageSize::slotPixelWidthChanged(double w)
{
- if (m_page->pixelWidthUnit->currentText() == percentStr) {
- m_width = qRound((w * m_originalWidth) / 100.0);
- } else {
- m_width = w;
- }
+ m_width = w;
m_printWidth = m_width / m_resolution;
updatePrintWidthUIValue(m_printWidth);
if (m_keepAspect) {
m_height = qRound(m_width / m_aspectRatio);
updatePixelHeightUIValue(m_height);
m_printHeight = m_height / m_resolution;
updatePrintHeightUIValue(m_printHeight);
}
}
void DlgImageSize::slotPixelHeightChanged(double h)
{
- if (m_page->pixelHeightUnit->currentText() == percentStr) {
- m_height = qRound((h * m_originalHeight) / 100.0);
- } else {
- m_height = h;
- }
+ m_height = h;
m_printHeight = m_height / m_resolution;
updatePrintHeightUIValue(m_printHeight);
if (m_keepAspect) {
m_width = qRound(m_height * m_aspectRatio);
updatePixelWidthUIValue(m_width);
m_printWidth = m_width / m_resolution;
updatePrintWidthUIValue(m_printWidth);
}
}
-void DlgImageSize::slotPixelWidthUnitChanged()
-{
- updatePixelWidthUIValue(m_width);
-
- m_page->pixelWidth->setVisible(m_page->pixelWidthUnit->currentText() == pixelStr);
- m_page->pixelWidthDouble->setVisible(m_page->pixelWidthUnit->currentText() == percentStr);
-}
-
-void DlgImageSize::slotPixelHeightUnitChanged()
-{
- updatePixelHeightUIValue(m_height);
-
- m_page->pixelHeight->setVisible(m_page->pixelHeightUnit->currentText() == pixelStr);
- m_page->pixelHeightDouble->setVisible(m_page->pixelHeightUnit->currentText() == percentStr);
-}
-
void DlgImageSize::slotPrintWidthChanged(double w)
{
- if (m_page->printWidthUnit->currentText() == percentStr) {
- const double originalWidthPoint = m_originalWidth / m_originalResolution;
- m_printWidth = (w * originalWidthPoint) / 100.0;
- } else {
- KoUnit selectedUnit = KoUnit::fromListForUi(m_page->printWidthUnit->currentIndex(), KoUnit::HidePixel);
- m_printWidth = selectedUnit.fromUserValue(w);
- }
+ m_printWidth = w;
if (m_keepAspect) {
m_printHeight = m_printWidth / m_aspectRatio;
updatePrintHeightUIValue(m_printHeight);
}
if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) {
m_resolution = m_width / m_printWidth;
updatePrintResolutionUIValue(m_resolution);
if (!m_keepAspect) {
// compute and update a new image height value from the print size values
const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
m_height = qRound(printHeightInch * 72 * m_resolution);
updatePixelHeightUIValue(m_height);
}
} else {
const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
m_width = qRound(printWidthInch * 72 * m_resolution);
updatePixelWidthUIValue(m_width);
if (m_keepAspect) {
m_height = qRound(m_width / m_aspectRatio);
updatePixelHeightUIValue(m_height);
}
}
}
void DlgImageSize::slotPrintHeightChanged(double h)
{
- if (m_page->printHeightUnit->currentText() == percentStr) {
- const double originalHeightPoint = m_originalHeight / m_originalResolution;
- m_printHeight = (h * originalHeightPoint) / 100.0;
- } else {
- KoUnit selectedUnit = KoUnit::fromListForUi(m_page->printHeightUnit->currentIndex(), KoUnit::HidePixel);
- m_printHeight = selectedUnit.fromUserValue(h);
- }
+ m_printHeight = h;
if (m_keepAspect) {
m_printWidth = m_printHeight * m_aspectRatio;
updatePrintWidthUIValue(m_printWidth);
}
if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) {
m_resolution = m_height / m_printHeight;
updatePrintResolutionUIValue(m_resolution);
if (!m_keepAspect) {
// compute and update a new image width value from the print size values
const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
m_width = qRound(printWidthInch * 72 * m_resolution);
updatePixelWidthUIValue(m_width);
}
} else {
const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
m_height = qRound(printHeightInch * 72 * m_resolution);
updatePixelHeightUIValue(m_height);
if (m_keepAspect) {
m_width = qRound(m_height * m_aspectRatio);
updatePixelWidthUIValue(m_width);
}
}
}
-void DlgImageSize::slotPrintWidthUnitChanged()
-{
- updatePrintWidthUIValue(m_printWidth);
-}
-
-void DlgImageSize::slotPrintHeightUnitChanged()
-{
- updatePrintHeightUIValue(m_printHeight);
-}
-
void DlgImageSize::slotAspectChanged(bool keep)
{
m_page->pixelAspectRatioBtn->blockSignals(true);
m_page->printAspectRatioBtn->blockSignals(true);
m_page->constrainProportionsCkb->blockSignals(true);
m_page->pixelAspectRatioBtn->setKeepAspectRatio(keep);
m_page->printAspectRatioBtn->setKeepAspectRatio(keep);
m_page->constrainProportionsCkb->setChecked(keep);
m_page->pixelAspectRatioBtn->blockSignals(false);
m_page->printAspectRatioBtn->blockSignals(false);
m_page->constrainProportionsCkb->blockSignals(false);
m_keepAspect = keep;
if (keep) {
// values may be out of sync, so we need to reset it to defaults
m_width = m_originalWidth;
m_height = m_originalHeight;
m_printWidth = m_originalWidth / m_originalResolution;
m_printHeight = m_originalHeight / m_originalResolution;
m_resolution = m_originalResolution;
updatePixelWidthUIValue(m_width);
updatePixelHeightUIValue(m_height);
updatePrintWidthUIValue(m_printWidth);
updatePrintHeightUIValue(m_printHeight);
updatePrintResolutionUIValue(m_resolution);
}
}
void DlgImageSize::slotPrintResolutionChanged(double r)
{
if (m_page->printResolutionUnit->currentText() == pixelsInchStr)
m_resolution = KoUnit::convertFromUnitToUnit(r, KoUnit(KoUnit::Pixel), KoUnit(KoUnit::Inch));
else
m_resolution = KoUnit::convertFromUnitToUnit(r, KoUnit(KoUnit::Pixel), KoUnit(KoUnit::Centimeter));
if (m_page->adjustPrintSizeSeparatelyCkb->isChecked()) {
m_printWidth = m_width / m_resolution;
m_printHeight = m_height / m_resolution;
updatePrintWidthUIValue(m_printWidth);
updatePrintHeightUIValue(m_printHeight);
} else {
// Do not commit m_width and m_height values yet. This is done to avoid
// nasty results in image size values while the user is typing a resolution value
const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
const int width = qRound(printWidthInch * 72 * m_resolution);
const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
const int height = qRound(printHeightInch * 72 * m_resolution);
updatePixelWidthUIValue(width);
updatePixelHeightUIValue(height);
}
}
void DlgImageSize::slotPrintResolutionEditFinished()
{
if (!m_page->adjustPrintSizeSeparatelyCkb->isChecked()) {
const double printWidthInch = KoUnit::convertFromUnitToUnit(m_printWidth, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
const double printHeightInch = KoUnit::convertFromUnitToUnit(m_printHeight, KoUnit(KoUnit::Point), KoUnit(KoUnit::Inch));
// Commit width and height values
m_width = qRound(printWidthInch * 72 * m_resolution);
m_height = qRound(printHeightInch * 72 * m_resolution);
// Note that spinbox values should be up to date
// (updated through slotResolutionChanged())
}
}
void DlgImageSize::slotPrintResolutionUnitChanged()
{
updatePrintResolutionUIValue(m_resolution);
}
void DlgImageSize::updatePixelWidthUIValue(double value)
{
- if (m_page->pixelWidthUnit->currentText() == percentStr) {
- m_page->pixelWidthDouble->blockSignals(true);
- m_page->pixelWidthDouble->setValue((value * 100.0) / m_originalWidth);
- m_page->pixelWidthDouble->blockSignals(false);
- } else {
- m_page->pixelWidth->blockSignals(true);
- m_page->pixelWidth->setValue(value);
- m_page->pixelWidth->blockSignals(false);
- }
+ m_page->pixelWidthDouble->blockSignals(true);
+ m_page->pixelWidthDouble->changeValue(value);
+ m_page->pixelWidthDouble->blockSignals(false);
}
void DlgImageSize::updatePixelHeightUIValue(double value)
{
- if (m_page->pixelHeightUnit->currentText() == percentStr) {
- m_page->pixelHeightDouble->blockSignals(true);
- m_page->pixelHeightDouble->setValue((value * 100.0) / m_originalHeight);
- m_page->pixelHeightDouble->blockSignals(false);
- } else {
- m_page->pixelHeight->blockSignals(true);
- m_page->pixelHeight->setValue(value);
- m_page->pixelHeight->blockSignals(false);
- }
+ m_page->pixelHeightDouble->blockSignals(true);
+ m_page->pixelHeightDouble->changeValue(value);
+ m_page->pixelHeightDouble->blockSignals(false);
}
void DlgImageSize::updatePrintWidthUIValue(double value)
{
- double uiValue = 0.0;
- if (m_page->printWidthUnit->currentText() == percentStr) {
- // We need to compute percent in point unit because:
- // - originalWith is a value expressed in px (original resolution)
- // - value is expressed in point unit (current resolution)
- // - the percentage value should be based on the original print size
- const double originalWidthPoint = m_originalWidth / m_originalResolution;
- uiValue = (value * 100.0) / originalWidthPoint;
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->printWidthUnit->currentIndex());
- uiValue = selectedUnit.toUserValue(value);
- }
m_page->printWidth->blockSignals(true);
- m_page->printWidth->setValue(uiValue);
+ m_page->printWidth->changeValue(value);
m_page->printWidth->blockSignals(false);
}
void DlgImageSize::updatePrintHeightUIValue(double value)
{
- double uiValue = 0.0;
- if (m_page->printHeightUnit->currentText() == percentStr) {
- // We need to compute percent in point unit because:
- // - originalHeight is a value expressed in px (original resolution)
- // - value is expressed in point unit (current resolution)
- // - the percentage value should be based on the original print size
- const double originalHeightPoint = m_originalHeight / m_originalResolution;
- uiValue = (value * 100.0) / originalHeightPoint;
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->printHeightUnit->currentIndex());
- uiValue = selectedUnit.toUserValue(value);
- }
m_page->printHeight->blockSignals(true);
- m_page->printHeight->setValue(uiValue);
+ m_page->printHeight->changeValue(value);
m_page->printHeight->blockSignals(false);
}
void DlgImageSize::updatePrintResolutionUIValue(double value)
{
double uiValue = 0.0;
if (m_page->printResolutionUnit->currentText() == pixelsInchStr) {
// show the value in pixel/inch unit
uiValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel));
} else {
// show the value in pixel/centimeter unit
uiValue = KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Centimeter), KoUnit(KoUnit::Pixel));
}
m_page->printResolution->blockSignals(true);
m_page->printResolution->setValue(uiValue);
m_page->printResolution->blockSignals(false);
}
diff --git a/plugins/extensions/imagesize/dlg_imagesize.h b/plugins/extensions/imagesize/dlg_imagesize.h
index ba2d8c7667..77e4fbf527 100644
--- a/plugins/extensions/imagesize/dlg_imagesize.h
+++ b/plugins/extensions/imagesize/dlg_imagesize.h
@@ -1,89 +1,91 @@
/*
* dlg_imagesize.h -- part of KimageShop^WKrayon^WKrita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* 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.
*/
#ifndef DLG_IMAGESIZE
#define DLG_IMAGESIZE
#include <KoDialog.h>
class KisFilterStrategy;
class WdgImageSize;
+class KisDocumentAwareSpinBoxUnitManager;
+class KisSpinBoxUnitManager;
#include "ui_wdg_imagesize.h"
class WdgImageSize : public QWidget, public Ui::WdgImageSize
{
Q_OBJECT
public:
WdgImageSize(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class DlgImageSize: public KoDialog
{
Q_OBJECT
public:
DlgImageSize(QWidget * parent, int width, int height, double resolution);
~DlgImageSize();
qint32 width();
qint32 height();
double resolution();
KisFilterStrategy *filterType();
private Q_SLOTS:
- void slotPixelWidthChanged(int w);
- void slotPixelHeightChanged(int h);
void slotPixelWidthChanged(double w);
void slotPixelHeightChanged(double h);
- void slotPixelWidthUnitChanged();
- void slotPixelHeightUnitChanged();
void slotPrintWidthChanged(double w);
void slotPrintHeightChanged(double h);
- void slotPrintWidthUnitChanged();
- void slotPrintHeightUnitChanged();
void slotAspectChanged(bool keep);
void slotPrintResolutionChanged(double r);
void slotPrintResolutionEditFinished();
void slotPrintResolutionUnitChanged();
private:
void updatePixelWidthUIValue(double value);
void updatePixelHeightUIValue(double value);
void updatePrintWidthUIValue(double value);
void updatePrintHeightUIValue(double value);
void updatePrintResolutionUIValue(double value);
WdgImageSize *m_page;
const double m_aspectRatio;
const int m_originalWidth, m_originalHeight;
int m_width, m_height;
double m_printWidth, m_printHeight; // in points
const double m_originalResolution;
double m_resolution;
bool m_keepAspect;
+
+ KisDocumentAwareSpinBoxUnitManager* _widthUnitManager;
+ KisDocumentAwareSpinBoxUnitManager* _heightUnitManager;
+
+ KisSpinBoxUnitManager* _printWidthUnitManager;
+ KisSpinBoxUnitManager* _printHeightUnitManager;
};
#endif // DLG_IMAGESIZE
diff --git a/plugins/extensions/imagesize/dlg_layersize.cc b/plugins/extensions/imagesize/dlg_layersize.cc
index 429dcdd72c..621eaf24e3 100644
--- a/plugins/extensions/imagesize/dlg_layersize.cc
+++ b/plugins/extensions/imagesize/dlg_layersize.cc
@@ -1,260 +1,197 @@
/*
* dlg_layersize.cc - part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Sven Langkamp <sven.langkamp@gmail.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_layersize.h"
#include <KoUnit.h>
#include <klocalizedstring.h>
+#include <kis_document_aware_spin_box_unit_manager.h>
+
#include <kis_filter_strategy.h>// XXX: I'm really real bad at arithmetic, let alone math. Here
// be rounding errors. (Boudewijn)
static const QString pixelStr(KoUnit::unitDescription(KoUnit::Pixel));
static const QString percentStr(i18n("Percent (%)"));
DlgLayerSize::DlgLayerSize(QWidget * parent, const char * name,
int width, int height, double resolution)
- : KoDialog(parent)
- , m_aspectRatio(((double) width) / height)
- , m_originalWidth(width)
- , m_originalHeight(height)
- , m_width(width)
- , m_height(height)
- , m_resolution(resolution)
- , m_keepAspect(true)
+ : KoDialog(parent)
+ , m_aspectRatio(((double) width) / height)
+ , m_originalWidth(width)
+ , m_originalHeight(height)
+ , m_width(width)
+ , m_height(height)
+ , m_resolution(resolution)
+ , m_keepAspect(true)
{
setCaption(i18n("Layer Size"));
setObjectName(name);
setButtons(Ok | Cancel);
setDefaultButton(Ok);
m_page = new WdgLayerSize(this);
Q_CHECK_PTR(m_page);
m_page->layout()->setMargin(0);
m_page->setObjectName(name);
- m_page->newWidth->setValue(width);
- m_page->newWidth->setFocus();
- m_page->newHeight->setValue(height);
+ _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
+ _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
+
+ _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->setVisible(false);
- m_page->newHeightDouble->setVisible(false);
+ m_page->newWidthDouble->setValue(width);
+ m_page->newWidthDouble->setFocus();
+ m_page->newHeightDouble->setValue(height);
m_page->filterCmb->setIDList(KisFilterStrategyRegistry::instance()->listKeys());
m_page->filterCmb->setToolTip(KisFilterStrategyRegistry::instance()->formatedDescriptions());
m_page->filterCmb->setCurrent("Bicubic");
- m_page->newWidthUnit->addItems(KoUnit::listOfUnitNameForUi());
- m_page->newWidthUnit->addItem(percentStr);
+ m_page->newWidthUnit->setModel(_widthUnitManager);
+ m_page->newHeightUnit->setModel(_heightUnitManager);
- m_page->newHeightUnit->addItems(KoUnit::listOfUnitNameForUi());
- m_page->newHeightUnit->addItem(percentStr);
-
- const int pixelUnitIndex = KoUnit(KoUnit::Pixel).indexInListForUi();
+ const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units.
m_page->newWidthUnit->setCurrentIndex(pixelUnitIndex);
m_page->newHeightUnit->setCurrentIndex(pixelUnitIndex);
m_page->aspectRatioBtn->setKeepAspectRatio(true);
m_page->constrainProportionsCkb->setChecked(true);
setMainWidget(m_page);
connect(this, SIGNAL(okClicked()), this, SLOT(accept()));
connect(m_page->aspectRatioBtn, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(slotAspectChanged(bool)));
connect(m_page->constrainProportionsCkb, SIGNAL(toggled(bool)), this, SLOT(slotAspectChanged(bool)));
- connect(m_page->newWidth, SIGNAL(valueChanged(int)), this, SLOT(slotWidthChanged(int)));
- connect(m_page->newHeight, SIGNAL(valueChanged(int)), this, SLOT(slotHeightChanged(int)));
- connect(m_page->newWidthDouble, SIGNAL(valueChanged(double)), this, SLOT(slotWidthChanged(double)));
- connect(m_page->newHeightDouble, SIGNAL(valueChanged(double)), this, SLOT(slotHeightChanged(double)));
- connect(m_page->newWidthUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotWidthUnitChanged(int)));
- connect(m_page->newHeightUnit, SIGNAL(currentIndexChanged(int)), this, SLOT(slotHeightUnitChanged(int)));
+ 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->newWidthUnit, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(m_page->newHeightUnit, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->newWidthUnit, SLOT(setCurrentIndex(int)));
+ connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->newHeightUnit, SLOT(setCurrentIndex(int)));
}
DlgLayerSize::~DlgLayerSize()
{
delete m_page;
}
qint32 DlgLayerSize::width()
{
return (qint32)m_width;
}
qint32 DlgLayerSize::height()
{
return (qint32)m_height;
}
KisFilterStrategy *DlgLayerSize::filterType()
{
KoID filterID = m_page->filterCmb->currentItem();
KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value(filterID.id());
return filter;
}
// SLOTS
-void DlgLayerSize::slotWidthChanged(int w)
-{
- slotWidthChanged((double) w);
-}
-
-void DlgLayerSize::slotHeightChanged(int h)
-{
- slotHeightChanged((double) h);
-}
-
void DlgLayerSize::slotWidthChanged(double w)
{
- if (m_page->newWidthUnit->currentText() == percentStr) {
- m_width = qRound((w * m_originalWidth) / 100.0);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->newWidthUnit->currentIndex());
- const double resValue = (selectedUnit == KoUnit(KoUnit::Pixel)) ? w : (w * m_resolution);
- m_width = qRound(selectedUnit.fromUserValue(resValue));
- }
+
+ //this slot receiv values in pt from the unitspinbox, so just use the resolution.
+ const double resValue = w*_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
+ m_width = qRound(resValue);
if (m_keepAspect) {
m_height = qRound(m_width / m_aspectRatio);
- updateHeightUIValue(m_height);
+ m_page->newHeightDouble->blockSignals(true);
+ m_page->newHeightDouble->changeValue(w / m_aspectRatio);
+ m_page->newHeightDouble->blockSignals(false);
}
+
}
void DlgLayerSize::slotHeightChanged(double h)
{
- if (m_page->newHeightUnit->currentText() == percentStr) {
- m_height = qRound((h * m_originalHeight) / 100.0);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->newHeightUnit->currentIndex());
- const double resValue = (selectedUnit == KoUnit(KoUnit::Pixel)) ? h : (h * m_resolution);
- m_height = qRound(selectedUnit.fromUserValue(resValue));
- }
+
+ //this slot receiv values in pt from the unitspinbox, so just use the resolution.
+ const double resValue = h*_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
+ m_height = qRound(resValue);
if (m_keepAspect) {
m_width = qRound(m_height * m_aspectRatio);
- updateWidthUIValue(m_width);
- }
-}
-
-void DlgLayerSize::slotWidthUnitChanged(int index)
-{
- updateWidthUIValue(m_width);
-
- if (m_page->newWidthUnit->currentText() == percentStr) {
- m_page->newWidth->setVisible(false);
- m_page->newWidthDouble->setVisible(true);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(index);
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newWidth->setVisible(false);
- m_page->newWidthDouble->setVisible(true);
- } else {
- m_page->newWidth->setVisible(true);
- m_page->newWidthDouble->setVisible(false);
- }
- }
-}
-
-void DlgLayerSize::slotHeightUnitChanged(int index)
-{
- updateHeightUIValue(m_height);
-
- if (m_page->newHeightUnit->currentText() == percentStr) {
- m_page->newHeight->setVisible(false);
- m_page->newHeightDouble->setVisible(true);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(index);
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newHeight->setVisible(false);
- m_page->newHeightDouble->setVisible(true);
- } else {
- m_page->newHeight->setVisible(true);
- m_page->newHeightDouble->setVisible(false);
- }
+ m_page->newWidthDouble->blockSignals(true);
+ m_page->newWidthDouble->changeValue(h * m_aspectRatio);
+ m_page->newWidthDouble->blockSignals(false);
}
}
void DlgLayerSize::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) {
// values may be out of sync, so we need to reset it to defaults
m_width = m_originalWidth;
m_height = m_originalHeight;
updateWidthUIValue(m_width);
updateHeightUIValue(m_height);
}
}
void DlgLayerSize::updateWidthUIValue(double value)
{
- if (m_page->newWidthUnit->currentText() == percentStr) {
- m_page->newWidthDouble->blockSignals(true);
- m_page->newWidthDouble->setValue((value * 100.0) / m_originalWidth);
- m_page->newWidthDouble->blockSignals(false);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->newWidthUnit->currentIndex());
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newWidthDouble->blockSignals(true);
- m_page->newWidthDouble->setValue(selectedUnit.toUserValue(value / m_resolution));
- m_page->newWidthDouble->blockSignals(false);
- } else {
- m_page->newWidth->blockSignals(true);
- m_page->newWidth->setValue(selectedUnit.toUserValue(value));
- m_page->newWidth->blockSignals(false);
- }
- }
+ m_page->newWidthDouble->blockSignals(true);
+ const double resValue = value/_widthUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
+ m_page->newWidthDouble->changeValue(resValue);
+ m_page->newWidthDouble->blockSignals(false);
}
void DlgLayerSize::updateHeightUIValue(double value)
{
- if (m_page->newHeightUnit->currentText() == percentStr) {
- m_page->newHeightDouble->blockSignals(true);
- m_page->newHeightDouble->setValue((value * 100.0) / m_originalHeight);
- m_page->newHeightDouble->blockSignals(false);
- } else {
- const KoUnit selectedUnit = KoUnit::fromListForUi(m_page->newHeightUnit->currentIndex());
- if (selectedUnit != KoUnit(KoUnit::Pixel)) {
- m_page->newHeightDouble->blockSignals(true);
- m_page->newHeightDouble->setValue(selectedUnit.toUserValue(value / m_resolution));
- m_page->newHeightDouble->blockSignals(false);
- } else {
- m_page->newHeight->blockSignals(true);
- m_page->newHeight->setValue(selectedUnit.toUserValue(value));
- m_page->newHeight->blockSignals(false);
- }
- }
+ m_page->newHeightDouble->blockSignals(true);
+ const double resValue = value/_heightUnitManager->getConversionFactor(KisSpinBoxUnitManager::LENGTH, "px");
+ m_page->newHeightDouble->changeValue(resValue);
+ m_page->newHeightDouble->blockSignals(false);
}
diff --git a/plugins/extensions/imagesize/dlg_layersize.h b/plugins/extensions/imagesize/dlg_layersize.h
index 73c943b10a..53fccdf309 100644
--- a/plugins/extensions/imagesize/dlg_layersize.h
+++ b/plugins/extensions/imagesize/dlg_layersize.h
@@ -1,78 +1,79 @@
/*
* dlg_layersize.h -- part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2005 Sven Langkamp <sven.langkamp@gmail.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.
*/
#ifndef DLG_LAYERSIZE
#define DLG_LAYERSIZE
#include <KoDialog.h>
#include "ui_wdg_layersize.h"
+class KisDocumentAwareSpinBoxUnitManager;
+
class WdgLayerSize : public QWidget, public Ui::WdgLayerSize
{
Q_OBJECT
public:
WdgLayerSize(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class KisFilterStrategy;
class DlgLayerSize: public KoDialog
{
Q_OBJECT
public:
DlgLayerSize(QWidget * parent, const char* name,
int width, int height, double resolution);
~DlgLayerSize();
qint32 width();
qint32 height();
KisFilterStrategy *filterType();
private Q_SLOTS:
- void slotWidthChanged(int w);
- void slotHeightChanged(int h);
void slotWidthChanged(double w);
void slotHeightChanged(double h);
- void slotWidthUnitChanged(int index);
- void slotHeightUnitChanged(int index);
void slotAspectChanged(bool keep);
private:
void updateWidthUIValue(double value);
void updateHeightUIValue(double value);
WdgLayerSize * m_page;
const double m_aspectRatio;
const int m_originalWidth, m_originalHeight;
int m_width, m_height;
const double m_resolution;
bool m_keepAspect;
+
+ KisDocumentAwareSpinBoxUnitManager* _widthUnitManager;
+ KisDocumentAwareSpinBoxUnitManager* _heightUnitManager;
};
#endif // DLG_IMAGESIZE
diff --git a/plugins/extensions/imagesize/imagesize.cc b/plugins/extensions/imagesize/imagesize.cc
index ca21339e1a..83601304cf 100644
--- a/plugins/extensions/imagesize/imagesize.cc
+++ b/plugins/extensions/imagesize/imagesize.cc
@@ -1,166 +1,166 @@
/*
* imagesize.cc -- Part of Krita
*
* Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "imagesize.h"
#include <klocalizedstring.h>
#include <kis_debug.h>
#include <kpluginfactory.h>
#include <kis_image.h>
#include <kis_layer.h>
#include <kis_global.h>
#include <kis_types.h>
#include <KisViewManager.h>
#include <kis_image_manager.h>
#include <kis_node_manager.h>
#include <kis_group_layer.h>
#include <kis_selection_mask.h>
#include <kis_selection.h>
#include "dlg_imagesize.h"
#include "dlg_canvassize.h"
#include "dlg_layersize.h"
#include "kis_filter_strategy.h"
#include "kis_action.h"
#include "kis_action_manager.h"
K_PLUGIN_FACTORY_WITH_JSON(ImageSizeFactory, "kritaimagesize.json", registerPlugin<ImageSize>();)
ImageSize::ImageSize(QObject *parent, const QVariantList &)
- : KisViewPlugin(parent)
+ : KisViewPlugin(parent)
{
KisAction *action = createAction("imagesize");
connect(action, SIGNAL(triggered()), this, SLOT(slotImageSize()));
action = createAction("canvassize");
connect(action, SIGNAL(triggered()), this, SLOT(slotCanvasSize()));
action = createAction("layersize");
connect(action, SIGNAL(triggered()), this, SLOT(slotLayerSize()));
action = createAction("selectionscale");
connect(action, SIGNAL(triggered()), this, SLOT(slotSelectionScale()));
}
ImageSize::~ImageSize()
{
}
void ImageSize::slotImageSize()
{
KisImageSP image = m_view->image().toStrongRef();
if (!image) return;
DlgImageSize * dlgImageSize = new DlgImageSize(m_view->mainWindow(), image->width(), image->height(), image->yRes());
Q_CHECK_PTR(dlgImageSize);
dlgImageSize->setObjectName("ImageSize");
if (dlgImageSize->exec() == QDialog::Accepted) {
qint32 w = dlgImageSize->width();
qint32 h = dlgImageSize->height();
double res = dlgImageSize->resolution();
m_view->imageManager()->scaleCurrentImage(QSize(w, h), res, res, dlgImageSize->filterType());
}
delete dlgImageSize;
}
void ImageSize::slotCanvasSize()
{
KisImageWSP image = m_view->image();
if (!image) return;
DlgCanvasSize * dlgCanvasSize = new DlgCanvasSize(m_view->mainWindow(), image->width(), image->height(), image->yRes());
Q_CHECK_PTR(dlgCanvasSize);
if (dlgCanvasSize->exec() == QDialog::Accepted) {
qint32 width = dlgCanvasSize->width();
qint32 height = dlgCanvasSize->height();
qint32 xOffset = dlgCanvasSize->xOffset();
qint32 yOffset = dlgCanvasSize->yOffset();
m_view->imageManager()->resizeCurrentImage(width, height, xOffset, yOffset);
}
delete dlgCanvasSize;
}
void ImageSize::slotLayerSize()
{
KisImageWSP image = m_view->image();
if (!image) return;
KisPaintDeviceSP dev = m_view->activeLayer()->projection();
Q_ASSERT(dev);
QRect rc = dev->exactBounds();
DlgLayerSize * dlgLayerSize = new DlgLayerSize(m_view->mainWindow(), "LayerSize", rc.width(), rc.height(), image->yRes());
Q_CHECK_PTR(dlgLayerSize);
dlgLayerSize->setCaption(i18n("Resize Layer"));
if (dlgLayerSize->exec() == QDialog::Accepted) {
qint32 w = dlgLayerSize->width();
qint32 h = dlgLayerSize->height();
m_view->nodeManager()->scale((double)w / ((double)(rc.width())),
(double)h / ((double)(rc.height())),
dlgLayerSize->filterType());
}
delete dlgLayerSize;
}
void ImageSize::slotSelectionScale()
{
KisImageSP image = m_view->image();
if (!image) {
return;
}
KisLayerSP layer = m_view->activeLayer();
KIS_ASSERT_RECOVER_RETURN(image && layer);
KisSelectionMaskSP selectionMask = layer->selectionMask();
if (!selectionMask) {
selectionMask = image->rootLayer()->selectionMask();
}
KIS_ASSERT_RECOVER_RETURN(selectionMask);
QRect rc = selectionMask->selection()->selectedExactRect();
DlgLayerSize * dlgSize = new DlgLayerSize(m_view->mainWindow(), "SelectionScale", rc.width(), rc.height(), image->yRes());
dlgSize->setCaption(i18n("Scale Selection"));
if (dlgSize->exec() == QDialog::Accepted) {
qint32 w = dlgSize->width();
qint32 h = dlgSize->height();
image->scaleNode(selectionMask,
qreal(w) / rc.width(),
qreal(h) / rc.height(),
dlgSize->filterType());
}
delete dlgSize;
}
#include "imagesize.moc"
diff --git a/plugins/extensions/imagesize/wdg_canvassize.ui b/plugins/extensions/imagesize/wdg_canvassize.ui
index 858140b589..466f45e393 100644
--- a/plugins/extensions/imagesize/wdg_canvassize.ui
+++ b/plugins/extensions/imagesize/wdg_canvassize.ui
@@ -1,692 +1,643 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgCanvasSize</class>
<widget class="QWidget" name="WdgCanvasSize">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>395</width>
<height>387</height>
</rect>
</property>
<property name="windowTitle">
<string>Canvas Size</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>New Size</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_2">
- <item row="0" column="2">
- <widget class="KisIntParseSpinBox" name="newWidth">
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
<item row="1" column="1">
<widget class="QLabel" name="lblNewHeight">
<property name="text">
<string>Height:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="heightUnit"/>
</item>
<item row="0" column="4" rowspan="2">
<widget class="KoAspectButton" name="aspectRatioBtn" native="true"/>
</item>
- <item row="1" column="2">
- <widget class="KisIntParseSpinBox" name="newHeight">
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
<item row="0" column="1">
<widget class="QLabel" name="lblNewWidth">
<property name="text">
<string>Width:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
- <widget class="KisDoubleParseSpinBox" name="newWidthDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="newWidthDouble">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
- <widget class="KisDoubleParseSpinBox" name="newHeightDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="newHeightDouble">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>25</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="widthUnit"/>
</item>
<item row="1" column="5">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2" colspan="4">
<widget class="QCheckBox" name="constrainProportionsCkb">
<property name="toolTip">
<string>Constrain aspect ratio</string>
</property>
<property name="text">
<string>Constrain proportions</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Offset</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblXOff">
<property name="text">
<string>X:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
- <widget class="KisIntParseSpinBox" name="xOffset">
- <property name="minimum">
- <number>-100000</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="KisDoubleParseSpinBox" name="xOffsetDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="xOffsetDouble">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>25</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1">
<widget class="QLabel" name="lblAnchor">
<property name="text">
<string>Anchor:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lblYOff">
<property name="text">
<string>Y:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
- <widget class="KisIntParseSpinBox" name="yOffset">
- <property name="minimum">
- <number>-100000</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="KisDoubleParseSpinBox" name="yOffsetDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="yOffsetDouble">
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-10000.000000000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="yOffUnit"/>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="xOffUnit"/>
</item>
<item row="3" column="3">
<widget class="QFrame" name="previewCanvas">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="KCanvasPreview" name="canvasPreview" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="topLeft">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="middleLeft">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="bottomLeft">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="middleCenter">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="bottomCenter">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="middleRight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="topCenter">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="topRight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="bottomRight">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>15</width>
<height>15</height>
</size>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>16</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="4">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KoAspectButton</class>
<extends>QWidget</extends>
<header>KoAspectButton.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>KCanvasPreview</class>
<extends>QWidget</extends>
<header>kcanvaspreview.h</header>
<container>1</container>
</customwidget>
<customwidget>
- <class>KisDoubleParseSpinBox</class>
+ <class>KisDoubleParseUnitSpinBox</class>
<extends>QDoubleSpinBox</extends>
- <header>kis_double_parse_spin_box.h</header>
- </customwidget>
- <customwidget>
- <class>KisIntParseSpinBox</class>
- <extends>QSpinBox</extends>
- <header>kis_int_parse_spin_box.h</header>
+ <header>kis_double_parse_unit_spin_box.h</header>
</customwidget>
</customwidgets>
<tabstops>
- <tabstop>newWidth</tabstop>
<tabstop>newWidthDouble</tabstop>
<tabstop>widthUnit</tabstop>
- <tabstop>newHeight</tabstop>
<tabstop>newHeightDouble</tabstop>
<tabstop>heightUnit</tabstop>
<tabstop>constrainProportionsCkb</tabstop>
- <tabstop>xOffset</tabstop>
<tabstop>xOffsetDouble</tabstop>
<tabstop>xOffUnit</tabstop>
- <tabstop>yOffset</tabstop>
<tabstop>yOffsetDouble</tabstop>
<tabstop>yOffUnit</tabstop>
<tabstop>topLeft</tabstop>
<tabstop>topCenter</tabstop>
<tabstop>topRight</tabstop>
<tabstop>middleLeft</tabstop>
<tabstop>middleCenter</tabstop>
<tabstop>middleRight</tabstop>
<tabstop>bottomLeft</tabstop>
<tabstop>bottomCenter</tabstop>
<tabstop>bottomRight</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
diff --git a/plugins/extensions/imagesize/wdg_imagesize.ui b/plugins/extensions/imagesize/wdg_imagesize.ui
index 8a45fdf251..6edf3cafaf 100644
--- a/plugins/extensions/imagesize/wdg_imagesize.ui
+++ b/plugins/extensions/imagesize/wdg_imagesize.ui
@@ -1,447 +1,413 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgImageSize</class>
<widget class="QWidget" name="WdgImageSize">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>391</width>
<height>386</height>
</rect>
</property>
<property name="windowTitle">
<string>Scale To New Size</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupImageSize">
<property name="title">
<string>Pixel Dimensions</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="2">
- <widget class="KisDoubleParseSpinBox" name="pixelHeightDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="pixelHeightDouble">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
- <item row="1" column="2">
- <widget class="KisIntParseSpinBox" name="pixelHeight">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
<item row="0" column="3">
<widget class="QComboBox" name="pixelWidthUnit"/>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>25</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblPixelWidth">
<property name="text">
- <string>W&amp;idth:</string>
+ <string>Width:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
- <property name="buddy">
- <cstring>pixelWidth</cstring>
- </property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblPixelFilter">
<property name="text">
<string>&amp;Filter:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>printWidth</cstring>
</property>
</widget>
</item>
<item row="0" column="4" rowspan="2">
<widget class="KoAspectButton" name="pixelAspectRatioBtn" native="true">
<property name="text" stdset="0">
<string/>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="pixelHeightUnit"/>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lblPixelHeight">
<property name="text">
- <string>&amp;Height:</string>
+ <string>Height:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
- <property name="buddy">
- <cstring>pixelHeight</cstring>
- </property>
</widget>
</item>
<item row="0" column="2">
- <widget class="KisDoubleParseSpinBox" name="pixelWidthDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="pixelWidthDouble">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
- <item row="0" column="2">
- <widget class="KisIntParseSpinBox" name="pixelWidth">
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
<item row="1" column="5">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2" colspan="2">
<widget class="KisCmbIDList" name="pixelFilterCmb" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupPrintSize">
<property name="title">
<string>Print Size</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QLabel" name="lblPrintHeight">
<property name="text">
<string>Hei&amp;ght:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>printHeight</cstring>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="printHeightUnit"/>
</item>
<item row="0" column="2">
- <widget class="KisDoubleParseSpinBox" name="printWidth">
+ <widget class="KisDoubleParseUnitSpinBox" name="printWidth">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblPrintWidth">
<property name="text">
<string>Wid&amp;th:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>printWidth</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
- <widget class="KisDoubleParseSpinBox" name="printHeight">
+ <widget class="KisDoubleParseUnitSpinBox" name="printHeight">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="0" column="4" rowspan="2">
<widget class="KoAspectButton" name="printAspectRatioBtn" native="true">
<property name="text" stdset="0">
<string/>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="printWidthUnit"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblResolution">
<property name="text">
<string>Resolution:</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="KisDoubleParseSpinBox" name="printResolution">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>70</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QComboBox" name="printResolutionUnit"/>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>25</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="5">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>16</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="constrainProportionsCkb">
<property name="toolTip">
<string>Constrain aspect ratio</string>
</property>
<property name="text">
<string>Constrain proportions</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="adjustPrintSizeSeparatelyCkb">
<property name="text">
<string>Adjust print size separately</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisCmbIDList</class>
<extends></extends>
<header>widgets/kis_cmb_idlist.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>
<customwidget>
<class>KisDoubleParseSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>kis_double_parse_spin_box.h</header>
</customwidget>
+ <customwidget>
+ <class>KisDoubleParseUnitSpinBox</class>
+ <extends>QDoubleSpinBox</extends>
+ <header>kis_double_parse_unit_spin_box.h</header>
+ </customwidget>
</customwidgets>
<tabstops>
- <tabstop>pixelWidth</tabstop>
<tabstop>pixelWidthDouble</tabstop>
<tabstop>pixelWidthUnit</tabstop>
- <tabstop>pixelHeight</tabstop>
<tabstop>pixelHeightDouble</tabstop>
<tabstop>pixelHeightUnit</tabstop>
<tabstop>pixelFilterCmb</tabstop>
<tabstop>printWidth</tabstop>
<tabstop>printWidthUnit</tabstop>
<tabstop>printHeight</tabstop>
<tabstop>printHeightUnit</tabstop>
<tabstop>printResolution</tabstop>
<tabstop>printResolutionUnit</tabstop>
<tabstop>constrainProportionsCkb</tabstop>
<tabstop>adjustPrintSizeSeparatelyCkb</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
diff --git a/plugins/extensions/imagesize/wdg_layersize.ui b/plugins/extensions/imagesize/wdg_layersize.ui
index 58170c58ad..6a07d00756 100644
--- a/plugins/extensions/imagesize/wdg_layersize.ui
+++ b/plugins/extensions/imagesize/wdg_layersize.ui
@@ -1,248 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WdgLayerSize</class>
<widget class="QWidget" name="WdgLayerSize">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>366</width>
+ <width>374</width>
<height>201</height>
</rect>
</property>
<property name="windowTitle">
<string>Layer Size</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupNewSize">
<property name="title">
<string>New Size</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="4" rowspan="2">
<widget class="KoAspectButton" name="aspectRatioBtn" native="true">
<property name="text" stdset="0">
<string/>
</property>
</widget>
</item>
<item row="1" column="5">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="3">
<widget class="QComboBox" name="newWidthUnit"/>
</item>
<item row="1" column="3">
<widget class="QComboBox" name="newHeightUnit"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblNewWidth">
<property name="text">
- <string>W&amp;idth:</string>
+ <string>Width:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
- <property name="buddy">
- <cstring>newWidth</cstring>
- </property>
</widget>
</item>
<item row="0" column="2">
- <widget class="KisDoubleParseSpinBox" name="newWidthDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="newWidthDouble">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
- <widget class="KisDoubleParseSpinBox" name="newHeightDouble">
+ <widget class="KisDoubleParseUnitSpinBox" name="newHeightDouble">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.000100000000000</double>
</property>
<property name="maximum">
<double>10000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
- <item row="1" column="2">
- <widget class="KisIntParseSpinBox" name="newHeight">
- <property name="minimumSize">
- <size>
- <width>80</width>
- <height>0</height>
- </size>
- </property>
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="KisIntParseSpinBox" name="newWidth">
- <property name="minimum">
- <number>1</number>
- </property>
- <property name="maximum">
- <number>100000</number>
- </property>
- </widget>
- </item>
<item row="2" column="2" colspan="2">
<widget class="KisCmbIDList" name="filterCmb" native="true"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblFilter">
<property name="text">
<string>Filter:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="lblNewHeight">
<property name="text">
- <string>&amp;Height:</string>
+ <string>Height:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
- <property name="buddy">
- <cstring>newHeight</cstring>
- </property>
</widget>
</item>
<item row="3" column="2" colspan="4">
<widget class="QCheckBox" name="constrainProportionsCkb">
<property name="toolTip">
<string>Constrain aspect ratio</string>
</property>
<property name="text">
<string>Constrain proportions</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>25</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
- <customwidget>
- <class>KisCmbIDList</class>
- <extends></extends>
- <header>widgets/kis_cmb_idlist.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>
+ <class>KisDoubleParseUnitSpinBox</class>
+ <extends>QDoubleSpinBox</extends>
+ <header>kis_double_parse_unit_spin_box.h</header>
</customwidget>
<customwidget>
- <class>KisDoubleParseSpinBox</class>
- <extends>QDoubleSpinBox</extends>
- <header>kis_double_parse_spin_box.h</header>
+ <class>KisCmbIDList</class>
+ <extends></extends>
+ <header>widgets/kis_cmb_idlist.h</header>
</customwidget>
</customwidgets>
<tabstops>
- <tabstop>newWidth</tabstop>
<tabstop>newWidthDouble</tabstop>
<tabstop>newWidthUnit</tabstop>
- <tabstop>newHeight</tabstop>
<tabstop>newHeightDouble</tabstop>
<tabstop>newHeightUnit</tabstop>
<tabstop>filterCmb</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
diff --git a/plugins/extensions/offsetimage/dlg_offsetimage.cpp b/plugins/extensions/offsetimage/dlg_offsetimage.cpp
index 5c4094921c..481fd36d36 100644
--- a/plugins/extensions/offsetimage/dlg_offsetimage.cpp
+++ b/plugins/extensions/offsetimage/dlg_offsetimage.cpp
@@ -1,79 +1,109 @@
/*
* Copyright (c) 2013 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "dlg_offsetimage.h"
#include <klocalizedstring.h>
#include <kis_debug.h>
+#include "kis_document_aware_spin_box_unit_manager.h"
+
DlgOffsetImage::DlgOffsetImage(QWidget * parent, const char * name, QSize imageSize)
- : KoDialog(parent),
- m_offsetSize(imageSize)
+ : KoDialog(parent),
+ m_offsetSize(imageSize)
{
setCaption("BUG: No sane caption is set");
setButtons(Ok | Cancel);
setDefaultButton(Ok);
setObjectName(name);
m_lock = false;
m_page = new WdgOffsetImage(this);
Q_CHECK_PTR(m_page);
m_page->setObjectName("offset_image");
setMainWidget(m_page);
resize(m_page->sizeHint());
+ _widthUnitManager = new KisDocumentAwareSpinBoxUnitManager(this);
+ _heightUnitManager = new KisDocumentAwareSpinBoxUnitManager(this, KisDocumentAwareSpinBoxUnitManager::PIX_DIR_Y);
+
+ _widthUnitManager->setApparentUnitFromSymbol("px");
+ _heightUnitManager->setApparentUnitFromSymbol("px");
+
+ m_page->offsetXdoubleSpinBox->setUnitManager(_widthUnitManager);
+ m_page->offsetYdoubleSpinBox->setUnitManager(_heightUnitManager);
+ m_page->offsetXdoubleSpinBox->setDecimals(2);
+ m_page->offsetYdoubleSpinBox->setDecimals(2);
+ m_page->offsetXdoubleSpinBox->setDisplayUnit(false);
+ m_page->offsetYdoubleSpinBox->setDisplayUnit(false);
+
+ m_page->offsetXdoubleSpinBox->setReturnUnit("px");
+ m_page->offsetYdoubleSpinBox->setReturnUnit("px");
+
+ m_page->unitXComboBox->setModel(_widthUnitManager);
+ m_page->unitYComboBox->setModel(_heightUnitManager);
+
+ const int pixelUnitIndex = _widthUnitManager->getsUnitSymbolList().indexOf("px"); //TODO: have a better way to identify units.
+ m_page->unitXComboBox->setCurrentIndex(pixelUnitIndex);
+ m_page->unitYComboBox->setCurrentIndex(pixelUnitIndex);
+
connect(this, SIGNAL(okClicked()),this, SLOT(okClicked()));
connect(m_page->middleOffsetBtn, SIGNAL(clicked()), this, SLOT(slotMiddleOffset()));
- connect(m_page->offsetXspinBox, SIGNAL(valueChanged(int)), this, SLOT(slotOffsetXChanged(int)));
- connect(m_page->offsetYspinBox, SIGNAL(valueChanged(int)), this, SLOT(slotOffsetYChanged(int)));
+ connect(m_page->offsetXdoubleSpinBox, SIGNAL(valueChangedPt(double)), this, SLOT(slotOffsetXChanged(double)));
+ connect(m_page->offsetYdoubleSpinBox, SIGNAL(valueChangedPt(double)), this, SLOT(slotOffsetYChanged(double)));
+
+ connect(m_page->unitXComboBox, SIGNAL(currentIndexChanged(int)), _widthUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(m_page->unitYComboBox, SIGNAL(currentIndexChanged(int)), _heightUnitManager, SLOT(selectApparentUnitFromIndex(int)));
+ connect(_widthUnitManager, SIGNAL(unitChanged(int)), m_page->unitXComboBox, SLOT(setCurrentIndex(int)));
+ connect(_heightUnitManager, SIGNAL(unitChanged(int)), m_page->unitYComboBox, SLOT(setCurrentIndex(int)));
slotMiddleOffset();
}
DlgOffsetImage::~DlgOffsetImage()
{
delete m_page;
}
-void DlgOffsetImage::slotOffsetXChanged(int newOffsetX)
+void DlgOffsetImage::slotOffsetXChanged(double newOffsetX)
{
m_offsetX = newOffsetX;
}
-void DlgOffsetImage::slotOffsetYChanged(int newOffsetY)
+void DlgOffsetImage::slotOffsetYChanged(double newOffsetY)
{
m_offsetY = newOffsetY;
}
void DlgOffsetImage::slotMiddleOffset()
{
int offsetX = m_offsetSize.width() / 2;
int offsetY = m_offsetSize.height() / 2;
- m_page->offsetXspinBox->setValue(offsetX);
- m_page->offsetYspinBox->setValue(offsetY);
+ m_page->offsetXdoubleSpinBox->changeValue(offsetX);
+ m_page->offsetYdoubleSpinBox->changeValue(offsetY);
}
void DlgOffsetImage::okClicked()
{
accept();
}
diff --git a/plugins/extensions/offsetimage/dlg_offsetimage.h b/plugins/extensions/offsetimage/dlg_offsetimage.h
index 785d8bd2a3..5488fa6517 100644
--- a/plugins/extensions/offsetimage/dlg_offsetimage.h
+++ b/plugins/extensions/offsetimage/dlg_offsetimage.h
@@ -1,66 +1,70 @@
/*
* Copyright (c) 2013 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef DLG_OFFSETIMAGE
#define DLG_OFFSETIMAGE
#include <KoDialog.h>
#include <kis_global.h>
#include "ui_wdg_offsetimage.h"
+class KisDocumentAwareSpinBoxUnitManager;
class WdgOffsetImage : public QWidget, public Ui::WdgOffsetImage
{
Q_OBJECT
public:
WdgOffsetImage(QWidget *parent) : QWidget(parent) {
setupUi(this);
}
};
class DlgOffsetImage: public KoDialog
{
Q_OBJECT
public:
DlgOffsetImage(QWidget * parent = 0, const char* name = 0, QSize imageSize = QSize());
~DlgOffsetImage();
int offsetX() const { return m_offsetX;}
int offsetY() const { return m_offsetY;}
private Q_SLOTS:
void okClicked();
- void slotOffsetXChanged(int);
- void slotOffsetYChanged(int);
+ void slotOffsetXChanged(double);
+ void slotOffsetYChanged(double);
void slotMiddleOffset();
private:
WdgOffsetImage * m_page;
int m_offsetX;
int m_offsetY;
bool m_lock;
QSize m_offsetSize;
+ KisDocumentAwareSpinBoxUnitManager* _widthUnitManager;
+ KisDocumentAwareSpinBoxUnitManager* _heightUnitManager;
+
};
#endif // DLG_OFFSETIMAGE
diff --git a/plugins/extensions/offsetimage/wdg_offsetimage.ui b/plugins/extensions/offsetimage/wdg_offsetimage.ui
index dbcf84b6bd..8a1eedac33 100644
--- a/plugins/extensions/offsetimage/wdg_offsetimage.ui
+++ b/plugins/extensions/offsetimage/wdg_offsetimage.ui
@@ -1,126 +1,132 @@
<?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>203</width>
+ <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,1">
+ <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="KisIntParseSpinBox" name="offsetXspinBox">
- <property name="suffix">
- <string> px</string>
- </property>
- <property name="maximum">
- <number>999999999</number>
+ <widget class="KisDoubleParseUnitSpinBox" name="offsetXdoubleSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</property>
</widget>
</item>
+ <item>
+ <widget class="QComboBox" name="unitXComboBox"/>
+ </item>
</layout>
</item>
<item>
- <layout class="QHBoxLayout" name="offsetRowY" stretch="0,1">
+ <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="KisIntParseSpinBox" name="offsetYspinBox">
- <property name="suffix">
- <string> px</string>
- </property>
- <property name="maximum">
- <number>999999999</number>
+ <widget class="KisDoubleParseUnitSpinBox" name="offsetYdoubleSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
</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>KisIntParseSpinBox</class>
- <extends>QSpinBox</extends>
- <header>kis_int_parse_spin_box.h</header>
+ <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/CMakeLists.txt b/plugins/extensions/pykrita/CMakeLists.txt
new file mode 100644
index 0000000000..4994adb102
--- /dev/null
+++ b/plugins/extensions/pykrita/CMakeLists.txt
@@ -0,0 +1,34 @@
+find_package(PythonLibrary)
+set_package_properties(PythonLibrary PROPERTIES
+ DESCRIPTION "Python Library"
+ URL "http://www.python.org"
+ TYPE OPTIONAL
+ PURPOSE "Required by the Krita PyQt plugin")
+macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS)
+
+
+find_package(SIP "4.18.0")
+set_package_properties(SIP PROPERTIES
+ DESCRIPTION "Support for generating SIP Python bindings"
+ URL "https://www.riverbankcomputing.com/software/sip/download"
+ TYPE OPTIONAL
+ PURPOSE "Required by the Krita PyQt plugin")
+macro_bool_to_01(SIP_FOUND HAVE_SIP)
+
+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)
+
+if (HAVE_PYQT5 AND HAVE_SIP AND HAVE_PYTHONLIBS)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${SIP_INCLUDE_DIR} ${PYTHON_INCLUDE_PATH})
+
+add_subdirectory(sip)
+add_subdirectory(plugin)
+add_subdirectory(kritarunner)
+
+endif ()
diff --git a/plugins/extensions/pykrita/api.txt b/plugins/extensions/pykrita/api.txt
new file mode 100644
index 0000000000..cd0f6d5d0d
--- /dev/null
+++ b/plugins/extensions/pykrita/api.txt
@@ -0,0 +1,218 @@
+class Krita :
+ Properties:
+ Actions : QList<Action*>
+ ActiveDocument : Document*
+ Batchmode : bool
+ Documents : QList<Document*>
+ Filters : QList<Filter*>
+ Generators : QList<Generator*>
+ Notifier : Notifier*
+ Preferences : InfoObject*
+ Version : QString
+ Views : QList<View*>
+ Windows : QList<Window*>
+ Resources : QList<Resource*>
+ Slots:
+ addDockWidget(DockWidget *dockWidget) : void
+ addAction(Action *action) : void
+ closeApplication() : bool
+ createDocument() : Document*
+ openDocument() : Document*
+ openWindow() : Window*
+
+class Notifier :
+ Properties:
+ Active : bool
+ Signals:
+ applicationStarted()
+ applicationClosed()
+ imageCreated(Document *image)
+ imageLoaded(Document *image)
+ imageSaved(Document *image)
+ imageClosed(Document *image)
+ nodeCreated(Document *node)
+
+
+class Document :
+ Properties:
+ ActiveNode : Node*
+ ColorDepth : ColorDepth*
+ ColorManager : ColorManager*
+ ColorModel : ColorModel*
+ ColorProfile : ColorProfile*
+ DocumentInfo : InfoObject*
+ FileName : QString
+ Height : int
+ MetaData : InfoObject*
+ Name : QString
+ Resolution : int
+ RootNode : Node*
+ Selection : Selection*
+ Width : int
+ PixelData : QByteArray
+ Slots:
+ clone() : Document *
+ close() : bool
+ convert(const QString &colorModel, const ColorProfile *profile) : bool
+ crop(int x, int y, int w, int h) : void
+ Export(const InfoObject &exportConfiguration) : bool
+ Flatten() : void
+ ResizeImage(int w, int h) : void
+ Save(const QString &url) : bool
+ SaveAs(const QString &url) : bool
+ OpenView() : void
+ CreateNode(const QString &name, const QString &nodeType) : Node*
+class Node :
+ Properties:
+ AlphaLocked : bool
+ BlendingMode : QString
+ Channels : QList<Channel*>
+ ChildNodes : QList<Node*>
+ ColorDepth : ColorDepth*
+ ColorLabel : QString
+ ColorModel : ColorModel*
+ ColorProfile : ColorProfile*
+ InheritAlpha : bool
+ Locked : bool
+ Name : QString
+ Opacity : int
+ ParentNode : Node*
+ Type : QString
+ Visible : bool
+ MetaDataInfo : InfoObject*
+ Generator : Generator*
+ Filter : Filter*
+ Transformation : Transformation*
+ Selection : Selection*
+ FileName : QString
+ PixelData : QByteArray
+ Slots:
+ move(int x, int y) : void
+ moveToParent(Node *parent) : void
+ remove() : void
+ duplicate() : Node*
+
+class Channel :
+ Properties:
+ visible : bool
+
+class Filter :
+ Properties:
+ Configuration : InfoObject*
+ Slots:
+ Apply(int x, int y, int w, int h) : void
+
+class Generator :
+ Properties:
+ Configuration : InfoObject*
+ Slots:
+ CreateNode() : Node*
+
+class Action :
+ Properties:
+ Name : QString
+ Menu : QString
+ Checkable : bool
+ Checked : bool
+ Shortcut : QString
+ Visible : bool
+ Enabled : bool
+ Slots:
+ Trigger() : void
+ Toggle(bool toggle) : void
+ Signals:
+ Toggled(bool toggle)
+ Triggered()
+
+class Canvas :
+ Properties:
+ Document : Document*
+ ZoomLevel : int
+ Rotation : int
+ Mirror : bool
+ ColorManager : ColorManager*
+
+class ColorManager :
+ Properties:
+ Type : QString
+ OcioSettings : InfoObject*
+
+class View :
+ Properties:
+ Window : Window*
+ Document : Document*
+ Visible : bool
+ Canvas : Canvas*
+ Slots:
+ close(bool confirm) : void
+
+#class Window :
+# Properties:
+# Views : QList<View*>
+# ViewMode : QString
+# Slots:
+# close(bool confirm) : void
+
+class DockWidget :
+ Properties:
+ Canvas : Canvas*
+ Slots:
+ canvasChanged(Canvas *canvas) : void
+
+class ColorDepth :
+ Properties:
+ depth : int
+
+class ColorModel :
+ Properties:
+ colorModelID : QString
+
+class ColorProfile :
+ Properties:
+ name : QString
+
+class InfoObject :
+ Properties:
+ properties : QMap<QString, QVariant>
+ Slots:
+ setProperty(const QString &key, QVariant value) : void
+ property(const QString &key) : QVariant
+
+
+class Selection :
+ Properties:
+ Width : int
+ Height : int
+ X : int
+ Y : int
+ Type : QString
+ Slots:
+ clear() : void
+ contract(int value) : void
+ copy(int x, int y, int w, int h) : Selection*
+ cut(Node* node) : void
+ deselect() : void
+ expand(int value) : void
+ feather(int value) : void
+ fill(Node* node) : void
+ grow(int value) : void
+ invert() : void
+ resize(int w, int h) : void
+ rotate(int degrees) : void
+ select(int x, int y, int w, int h, int value) : void
+ selectAll(Node *node) : void
+
+class Resource :
+ Properties:
+ Type : QString
+ Name : QString
+ Filename : QString
+
+class Transformation :
+ Properties:
+ Definition : QString
+
+
+class Dummy :
+$$$$$ END $$$$$
+
diff --git a/plugins/extensions/pykrita/apigen.py b/plugins/extensions/pykrita/apigen.py
new file mode 100644
index 0000000000..efb310a758
--- /dev/null
+++ b/plugins/extensions/pykrita/apigen.py
@@ -0,0 +1,345 @@
+#
+# Simple script to take the template header/c++ file
+#
+import sys
+from string import Template
+
+property_declaration_r = Template(""" Q_PROPERTY(${TYPE} ${PROPERTY} READ ${GETTER})
+""")
+
+property_declaration_rw = Template(""" Q_PROPERTY(${TYPE} ${PROPERTY} READ ${GETTER} WRITE ${SETTER})
+""")
+
+getter_declaration = Template(""" ${TYPE} ${GETTER}() const;
+""")
+
+setter_declaration = Template(""" void ${SETTER}(${TYPE} value);
+""")
+
+slot_declaration = Template(""" ${TYPE} ${SLOT};
+""")
+
+signal_declaration = Template(""" void ${SIGNAL};
+""")
+
+getter = Template("""${TYPE} ${CLASSNAME}::${GETTER}() const
+{
+ return ${RETURN_VALUE};
+}
+
+""")
+
+setter = Template("""void ${CLASSNAME}::${SETTER}(${TYPE} value)
+{
+}
+
+""")
+
+slot_return = Template("""${TYPE} ${CLASSNAME}::${SLOT}
+{
+ return ${RETURN_VALUE};
+}
+
+""")
+
+slot_no_return = Template("""${TYPE} ${CLASSNAME}::${SLOT}
+{
+}
+
+""")
+
+
+header = Template("""/*
+ * 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_${HEADER_GUARD}_H
+#define LIBKIS_${HEADER_GUARD}_H
+
+#include <QObject>
+
+#include "kritalibkis_export.h"
+#include "libkis.h"
+
+/**
+ * ${CLASSNAME}
+ */
+class KRITALIBKIS_EXPORT ${CLASSNAME} : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(${CLASSNAME})
+
+${PROPERTIES}
+public:
+ explicit ${CLASSNAME}(QObject *parent = 0);
+ virtual ~${CLASSNAME}();
+
+${GETTER_SETTER_DECLARATIONS}
+
+public Q_SLOTS:
+
+${SLOT_DECLARATIONS}
+
+Q_SIGNALS:
+
+${SIGNAL_DECLARATIONS}
+
+private:
+ struct Private;
+ const Private *const d;
+
+};
+
+Q_DECLARE_METATYPE(${CLASSNAME}*)
+
+#endif // LIBKIS_${HEADER_GUARD}_H
+""")
+
+source = Template("""/*
+ * 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 "${CLASSNAME}.h"
+
+struct ${CLASSNAME}::Private {
+ Private() {}
+};
+
+${CLASSNAME}::${CLASSNAME}(QObject *parent)
+ : QObject(parent)
+ , d(new Private)
+{
+}
+
+${CLASSNAME}::~${CLASSNAME}()
+{
+ delete d;
+}
+
+${GETTER_SETTERS}
+
+${SLOTS}
+
+""")
+
+
+sip = Template("""%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+%Import QtWidgets/QtWidgetsmod.sip
+
+class ${CLASSNAME} : public QObject
+{
+%TypeHeaderCode
+#include "${CLASSNAME}.h"
+%End
+
+public:
+ explicit ${CLASSNAME}(QObject *parent /TransferThis/ = 0);
+};
+""")
+
+
+def main(args):
+ if len(args) != 4:
+ print("Usage: apigen.py api.txt libdir sipdir")
+ sys.exit(0)
+
+ api = open(args[1], 'r').readlines()
+
+ libdir = args[2]
+ sipdir = args[3]
+
+ class_definition = {"PROPERTIES" : [],
+ "SLOTS" : [],
+ "SIGNALS" : []
+ }
+ class_name = ""
+
+ inProperties = False
+ inSlots = False
+ inSignals = False
+
+ classes = {}
+
+ line_number = 0;
+
+ for line in api:
+ line_number += 1
+
+ if line.isspace() or line.startswith("#"):
+ continue
+
+ elif line.startswith("class"):
+
+ if class_name != "":
+ classes[class_name] = class_definition
+
+ class_definition = {"PROPERTIES" : [],
+ "SLOTS" : [],
+ "SIGNALS" : []
+ }
+
+ inProperties = False
+ inSlots = False
+ inSignals = False
+
+ class_name = line.split(' ')[1]
+
+ if class_name == "Dummy":
+ break
+
+ elif line.lstrip().startswith("Properties"):
+ inProperties = True
+ inSlots = False
+ inSignals = False
+
+ elif line.lstrip().startswith("Slots"):
+ inProperties = False
+ inSlots = True
+ inSignals = False
+
+ elif line.lstrip().startswith("Signals"):
+ inProperties = False
+ inSlots = False
+ inSignals = True
+
+ elif line.startswith("$$$$$ END $$$$$"):
+ break
+
+ else:
+ if inProperties:
+ property_definition = line.split(" : ")
+ if len(property_definition) != 2:
+ print("Could not parse property" + line + "," + str(line_number))
+ continue
+ property_name = property_definition[0].lstrip().rstrip()
+ property_type = property_definition[1].lstrip().rstrip()
+
+ d = {"TYPE" : property_type ,
+ "PROPERTY" : property_name,
+ "GETTER" : property_name[0].lower() + property_name[1:],
+ "SETTER" : "set" + property_name,
+ "CLASSNAME" : class_name
+ }
+ if property_type != "void":
+ if property_type == "QString":
+ d["RETURN_VALUE"] = "QString()"
+ if property_type == "QByteArray":
+ d["RETURN_VALUE"] = "QByteArray()"
+ elif property_type == "int" or property_type.endswith("*"):
+ d["RETURN_VALUE"] = "0"
+ elif property_type.startswith("QList") or property_type.startswith("QMap"):
+ d["RETURN_VALUE"] = property_type + "()"
+ elif property_type == "bool":
+ d["RETURN_VALUE"] = "false"
+ elif property_type == "QVariant":
+ d["RETURN_VALUE"] = "QVariant()"
+
+ class_definition["PROPERTIES"].append(d)
+ elif inSlots:
+
+ slot_definition = line.split(" : ")
+ if len(slot_definition) != 2:
+ print("Could not parse slot:" + line + "," + str(line_number))
+ continue
+ slot_name = slot_definition[0].lstrip().rstrip()
+ slot_type = slot_definition[1].lstrip().rstrip()
+ d = {"TYPE" : slot_type ,
+ "SLOT" : slot_name,
+ "CLASSNAME" : class_name
+ }
+
+ if slot_type != "void":
+ if slot_type == "QString":
+ d["RETURN_VALUE"] = "QString()"
+ if slot_type == "QByteArray":
+ d["RETURN_VALUE"] = "QByteArray()"
+ elif slot_type == "int" or slot_type.endswith("*"):
+ d["RETURN_VALUE"] = "0"
+ elif slot_type.startswith("QList") or slot_type.startswith("QMap"):
+ d["RETURN_VALUE"] = slot_type + "()"
+ elif slot_type == "bool":
+ d["RETURN_VALUE"] = "false"
+ elif slot_type == "QVariant":
+ d["RETURN_VALUE"] = "QVariant()"
+ class_definition["SLOTS"].append(d)
+
+
+ elif inSignals:
+ class_definition["SIGNALS"].append({"SIGNAL": line.lstrip().rstrip(),
+ "CLASSNAME" : class_name})
+
+ for class_name in classes:
+ unpacked_definition = {
+ "CLASSNAME" : class_name,
+ "HEADER_GUARD" : class_name.upper(),
+ "PROPERTIES" : "",
+ "SLOT_DECLARATIONS" : "",
+ "SIGNAL_DECLARATIONS" : "",
+ "GETTER_SETTER_DECLARATIONS" : "",
+ "SLOTS" : "",
+ "GETTER_SETTERS" : ""
+ }
+ for p in classes[class_name]["PROPERTIES"]:
+ unpacked_definition["PROPERTIES"] += property_declaration_rw.safe_substitute(p)
+ unpacked_definition["GETTER_SETTER_DECLARATIONS"] += getter_declaration.safe_substitute(p)
+ unpacked_definition["GETTER_SETTER_DECLARATIONS"] += setter_declaration.safe_substitute(p)
+ unpacked_definition["GETTER_SETTER_DECLARATIONS"] += "\n"
+ unpacked_definition["GETTER_SETTERS"] += getter.safe_substitute(p)
+ unpacked_definition["GETTER_SETTERS"] += setter.safe_substitute(p)
+ unpacked_definition["GETTER_SETTERS"] += "\n"
+
+ for s in classes[class_name]["SLOTS"]:
+ if "RETURN_VALUE" in s:
+ unpacked_definition["SLOT_DECLARATIONS"] += slot_declaration.safe_substitute(s)
+ unpacked_definition["SLOT_DECLARATIONS"] += "\n"
+ unpacked_definition["SLOTS"] += slot_return.safe_substitute(s)
+ else:
+ unpacked_definition["SLOT_DECLARATIONS"] += slot_declaration.safe_substitute(s)
+ unpacked_definition["SLOT_DECLARATIONS"] += "\n"
+ unpacked_definition["SLOTS"] += slot_no_return.safe_substitute(s)
+
+ for s in classes[class_name]["SIGNALS"]:
+ unpacked_definition["SIGNAL_DECLARATIONS"] += signal_declaration.safe_substitute(s)
+
+ f = open(libdir + "/" + class_name + ".h", 'w')
+ f.write(header.safe_substitute(unpacked_definition))
+ f.close()
+
+ f = open(libdir + "/" + class_name + ".cpp", 'w')
+ f.write(source.safe_substitute(unpacked_definition))
+ f.close()
+
+ f = open(sipdir + class_name + ".sip", 'w')
+ f.write(sip.safe_substitute(unpacked_definition))
+ f.close()
+
+
+if __name__ == "__main__":
+ main(sys.argv)
diff --git a/plugins/extensions/pykrita/kritarunner/CMakeLists.txt b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
new file mode 100644
index 0000000000..2e3443d3ff
--- /dev/null
+++ b/plugins/extensions/pykrita/kritarunner/CMakeLists.txt
@@ -0,0 +1,30 @@
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../plugin
+ ${CMAKE_CURRENT_BINARY_DIR}/../plugin
+ ${CMAKE_CURRENT_SOURCE_DIR}/../libkis
+ ${CMAKE_CURRENT_BINARY_DIR}/../libkis
+)
+
+set(kritarunner_SRCS main.cpp
+ ../plugin/engine.cpp
+ ../plugin/plugin.cpp
+ ../plugin/pyqtpluginsettings.cpp
+ ../plugin/utilities.cpp
+)
+
+add_executable(kritarunner ${kritarunner_SRCS})
+target_link_libraries(kritarunner
+ PRIVATE
+ ${PYTHON_LIBRARY}
+ kritaui
+ kritalibkis
+ Qt5::Core
+ Qt5::Gui
+ Qt5::Widgets
+ Qt5::Xml
+ Qt5::Network
+ Qt5::PrintSupport
+ Qt5::Svg
+ Qt5::Concurrent)
+
+install(TARGETS kritarunner ${INSTALL_TARGETS_DEFAULT_ARGS})
+
diff --git a/plugins/extensions/pykrita/kritarunner/main.cpp b/plugins/extensions/pykrita/kritarunner/main.cpp
new file mode 100644
index 0000000000..caf5eabd1d
--- /dev/null
+++ b/plugins/extensions/pykrita/kritarunner/main.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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 <stdlib.h>
+
+#include <QString>
+#include <QCommandLineParser>
+#include <QCommandLineOption>
+
+#include <KisApplication.h>
+#include <KoGlobal.h>
+#include <resources/KoHashGeneratorProvider.h>
+#include "kis_md5_generator.h"
+#include <opengl/kis_opengl.h>
+
+#include <engine.h>
+#include <utilities.h>
+
+extern "C" int main(int argc, char **argv)
+{
+ // The global initialization of the random generator
+ qsrand(time(0));
+ KLocalizedString::setApplicationDomain("kritarunner");
+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
+ KisOpenGL::setDefaultFormat();
+
+ QLoggingCategory::setFilterRules("krita*.debug=false\n"
+ "krita*.warning=false\n"
+ "krita.tabletlog=false");
+
+
+ // first create the application so we can create a pixmap
+ KisApplication app("kritarunner", argc, argv);
+ app.setApplicationDisplayName("Krita Script Runner");
+ app.setApplicationName("kritarunner");
+ app.setOrganizationDomain("krita.org");
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription("kritarunner executes one python script and then returns.");
+ parser.addVersionOption();
+ parser.addHelpOption();
+
+ QCommandLineOption scriptOption(QStringList() << "s" << "script", "The script to run. Do not append the .py extension.", "script");
+ parser.addOption(scriptOption);
+
+ QCommandLineOption functionOption(QStringList() << "f" << "function",
+ "The function to call (by default __main__ is called).", "function", "__main__");
+ parser.addOption(functionOption);
+
+ parser.addPositionalArgument("[argument(s)]", "The argumetns for the script");
+ parser.process(app);
+
+ if (!parser.isSet(scriptOption)) {
+ qDebug("No script given, aborting.");
+ return 1;
+ }
+
+ qDebug() << "running:" << parser.value(scriptOption) << parser.value(functionOption);
+ qDebug() << parser.positionalArguments();
+
+ KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator());
+ KoGlobal::initialize();
+ app.addResourceTypes();
+ app.loadResources();
+ app.loadPlugins();
+
+
+ QByteArray pythonPath = qgetenv("PYTHONPATH");
+ qDebug() << "\tPython path:" << pythonPath;
+
+ qDebug() << "Creating engine";
+ PyKrita::Engine engine;
+ QString r = engine.tryInitializeGetFailureReason();
+
+ if (!r.isEmpty()) {
+ qDebug("Could not initialize the Python engine");
+ return 1;
+ }
+
+ qDebug() << "Try to import the pykrita module";
+ PyKrita::Python py = PyKrita::Python();
+ PyObject* pykritaPackage = py.moduleImport("pykrita");
+ pykritaPackage = py.moduleImport("krita");
+
+ if (!pykritaPackage) {
+ qDebug("Cannot load the PyKrita module, aborting");
+ return 1;
+ }
+
+ PyObject *argsList = PyList_New(0);
+ Q_FOREACH(const QString arg, parser.positionalArguments()) {
+ PyObject* const u = py.unicode(arg);
+ PyList_Append(argsList, u);
+ Py_DECREF(u);
+ }
+
+ PyObject *args = PyTuple_New(1);
+ PyTuple_SetItem(args, 0, argsList);
+
+ py.functionCall(parser.value(functionOption).toUtf8().constData(), parser.value(scriptOption).toUtf8().constData(), args);
+
+ Py_DECREF(argsList);
+ Py_DECREF(args);
+
+ app.quit();
+ return 0;
+}
+
diff --git a/plugins/extensions/pykrita/overview.txt b/plugins/extensions/pykrita/overview.txt
new file mode 100644
index 0000000000..86f61af74a
--- /dev/null
+++ b/plugins/extensions/pykrita/overview.txt
@@ -0,0 +1,23 @@
+* Action: a QAction that can trigger something, be embedded in the menu or a toolbar
+* Application: the central application object, the root of the whole tree
+* Canvas: displays a given image at a certain zoom level, rotation and mirroring
+* Channel:
+* ColorDepth
+* ColorManager: icc or ocio
+* ColorModel
+* ColorProfile
+* DockWidget: can be created by a factory on startup and can handle Canvas objects
+* Document: document/image
+* Exporter: can export a given document
+* Filter: can apply itself to a node
+* Generator: can generate a new node with the given generator and configuration
+* Importer: can open a given document
+* InfoObject
+* Node
+* Notifier: sends on out signals when documents are opened or closed etc.
+* PreferenceManager: allows reading and changing the settings
+* Resource
+* Selection
+* Transformation
+* View: contains an image
+* Window: contains one or more views
diff --git a/plugins/extensions/pykrita/plugin/CMakeLists.txt b/plugins/extensions/pykrita/plugin/CMakeLists.txt
new file mode 100644
index 0000000000..7ca32184ac
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/CMakeLists.txt
@@ -0,0 +1,38 @@
+# NOTE Disable trivial Qt keywords due conflicts w/ some Python.h header
+# (at least version 3.3 of it has a member PyType_Spec::slots)
+add_definitions(-DQT_NO_KEYWORDS)
+configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+
+set(SOURCES
+ plugin.cpp
+ pyqtpluginsettings.cpp
+ utilities.cpp
+ engine.cpp
+)
+
+ki18n_wrap_ui(SOURCES
+ info.ui
+ manager.ui
+)
+
+add_library(kritapykrita MODULE ${SOURCES})
+
+target_link_libraries(
+ kritapykrita
+ ${PYTHON_LIBRARY}
+ kritaui
+ kritalibkis
+ )
+
+install(TARGETS kritapykrita DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
+
+# Install "built-in" api
+install(
+ DIRECTORY krita
+ DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
+ FILES_MATCHING PATTERN "*.py"
+)
+
+add_subdirectory(plugins)
+#add_subdirectory(test)
+
diff --git a/plugins/extensions/pykrita/plugin/config.h.cmake b/plugins/extensions/pykrita/plugin/config.h.cmake
new file mode 100644
index 0000000000..ec07c47e7a
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/config.h.cmake
@@ -0,0 +1,21 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2006 Paul Giannaros <paul@giannaros.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) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB. If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#define PYKRITA_PYTHON_LIBRARY "${PYTHON_LIBRARY}"
+#define PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR "${PYTHON_SITE_PACKAGES_INSTALL_DIR}"
diff --git a/plugins/extensions/pykrita/plugin/engine.cpp b/plugins/extensions/pykrita/plugin/engine.cpp
new file mode 100644
index 0000000000..2da507f4f6
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/engine.cpp
@@ -0,0 +1,798 @@
+// 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>
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU 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/>.
+//
+
+#include "engine.h"
+
+// config.h defines PYKRITA_PYTHON_LIBRARY, the path to libpython.so
+// on the build system
+
+#include "config.h"
+#include "utilities.h"
+
+#include <cmath>
+#include <Python.h>
+
+#include <QSettings>
+#include <QLibrary>
+#include <QFileInfo>
+
+#include <kconfig.h>
+#include <klocalizedstring.h>
+#include <kcolorscheme.h>
+
+//#include <KoServiceLocator.h>
+#include <KoResourcePaths.h>
+
+#include <kis_debug.h>
+
+/// Name of the file where per-plugin configuration is stored.
+#define CONFIG_FILE "kritapykritarc"
+
+#if PY_MAJOR_VERSION < 3
+# define PYKRITA_INIT initpykrita
+#else
+# define PYKRITA_INIT PyInit_pykrita
+#endif
+
+PyMODINIT_FUNC PYKRITA_INIT(); // fwd decl
+
+/// \note Namespace name written in uppercase intentionally!
+/// It will appear in debug output from Python plugins...
+namespace PYKRITA
+{
+PyObject* debug(PyObject* /*self*/, PyObject* args)
+{
+ const char* text;
+
+ if (PyArg_ParseTuple(args, "s", &text))
+ dbgScript << text;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+} // namespace PYKRITA
+
+namespace
+{
+PyObject* s_pykrita;
+/**
+ * \attention Krita has embedded Python, so init function \b never will be called
+ * automatically! We can use this fact to initialize a pointer to an instance
+ * of the \c Engine class (which is a part of the \c Plugin), so exported
+ * functions will know it (yep, from Python's side they should be static).
+ */
+PyKrita::Engine* s_engine_instance = 0;
+
+/**
+ * Wrapper function, called explicitly from \c Engine::Engine
+ * to initialize pointer to the only (by design) instance of the engine,
+ * so exported (to Python) functions get know it... Then invoke
+ * a real initialization sequence...
+ */
+void pythonInitwrapper(PyKrita::Engine* const engine)
+{
+ Q_ASSERT("Sanity check" && !s_engine_instance);
+ s_engine_instance = engine;
+ // Call initialize explicitly to initialize embedded interpreter.
+ PYKRITA_INIT();
+}
+
+/**
+ * Functions for the Python module called pykrita.
+ * \todo Does it \b REALLY needed? Configuration data will be flushed
+ * on exit anyway! Why to write it (and even allow to plugins to call this)
+ * \b before krita really going to exit? It would be better to \b deprecate
+ * this (harmful) function!
+ */
+PyObject* pykritaSaveConfiguration(PyObject* /*self*/, PyObject* /*unused*/)
+{
+ if (s_engine_instance)
+ s_engine_instance->saveGlobalPluginsConfiguration();
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyMethodDef pykritaMethods[] = {
+ {
+ "saveConfiguration"
+ , &pykritaSaveConfiguration
+ , METH_NOARGS
+ , "Save the configuration of the plugin into " CONFIG_FILE
+ }
+ , {
+ "qDebug"
+ , &PYKRITA::debug
+ , METH_VARARGS
+ , "True KDE way to show debug info"
+ }
+ , { 0, 0, 0, 0 }
+};
+
+} // anonymous namespace
+
+//BEGIN Python module registration
+PyMODINIT_FUNC PYKRITA_INIT()
+{
+#if PY_MAJOR_VERSION < 3
+ s_pykrita = Py_InitModule3("pykrita", pykritaMethods, "The pykrita module");
+ PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
+#else
+ static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT
+ , "pykrita"
+ , "The pykrita module"
+ , -1
+ , pykritaMethods
+ , 0
+ , 0
+ , 0
+ , 0
+ };
+ s_pykrita = PyModule_Create(&moduledef);
+ PyModule_AddStringConstant(s_pykrita, "__file__", __FILE__);
+ return s_pykrita;
+#endif
+}
+//END Python module registration
+
+
+//BEGIN PyKrita::Engine::PluginState
+PyKrita::Engine::PluginState::PluginState()
+ : m_enabled(false)
+ , m_broken(false)
+ , m_unstable(false)
+ , m_isDir(false)
+{
+}
+//END PyKrita::Engine::PluginState
+
+
+/**
+ * Just initialize some members. The second (most important) part
+ * is to call \c Engine::tryInitializeGetFailureReason()!
+ * W/o that call instance is invalid and using it lead to UB!
+ */
+PyKrita::Engine::Engine()
+ : m_configuration(0)
+ , m_sessionConfiguration(0)
+ , m_engineIsUsable(false)
+{
+}
+
+/// \todo More accurate shutdown required:
+/// need to keep track what exactly was broken on
+/// initialize attempt...
+PyKrita::Engine::~Engine()
+{
+ dbgScript << "Going to destroy the Python engine";
+
+ // Notify Python that engine going to die
+ {
+ Python py = Python();
+ py.functionCall("_pykritaUnloading");
+ }
+ unloadAllModules();
+
+ // Clean internal configuration dicts
+ // NOTE Do not need to save anything! It's already done!
+ if (m_configuration) {
+ Py_DECREF(m_configuration);
+ }
+ if (m_sessionConfiguration) {
+ Py_DECREF(m_sessionConfiguration);
+ }
+
+ Python::libraryUnload();
+ s_engine_instance = 0;
+}
+
+void PyKrita::Engine::unloadAllModules()
+{
+ // Unload all modules
+ for (int i = 0; i < m_plugins.size(); ++i) {
+ if (m_plugins[i].isEnabled() && !m_plugins[i].isBroken()) {
+ unloadModule(i);
+ }
+ }
+}
+
+/**
+ * \todo Make sure noone tries to use uninitialized engine!
+ * (Or enable exceptions for this module, so this case wouldn't even araise?)
+ */
+QString PyKrita::Engine::tryInitializeGetFailureReason()
+{
+ dbgScript << "Construct the Python engine for Python" << PY_MAJOR_VERSION << "," << PY_MINOR_VERSION;
+ if (0 != PyImport_AppendInittab(Python::PYKRITA_ENGINE, PYKRITA_INIT)) {
+ return i18nc("@info:tooltip ", "Cannot load built-in <icode>pykrita</icode> module");
+ }
+
+ Python::libraryLoad();
+ Python py = Python();
+
+ // Update PYTHONPATH
+ // 0) custom plugin directories (prefer local dir over systems')
+ // 1) shipped krita module's dir
+ // 2) w/ site_packages/ dir of the Python
+ QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
+ pluginDirectories << KoResourcePaths::locate("appdata", "plugins/pykrita/")
+ << QLatin1String(PYKRITA_PYTHON_SITE_PACKAGES_INSTALL_DIR)
+ ;
+ dbgScript << "Plugin Directories: " << pluginDirectories;
+ if (!py.prependPythonPaths(pluginDirectories)) {
+ return i18nc("@info:tooltip ", "Cannot update Python paths");
+ }
+
+ PyRun_SimpleString(
+ "import sip\n"
+ "sip.setapi('QDate', 2)\n"
+ "sip.setapi('QTime', 2)\n"
+ "sip.setapi('QDateTime', 2)\n"
+ "sip.setapi('QUrl', 2)\n"
+ "sip.setapi('QTextStream', 2)\n"
+ "sip.setapi('QString', 2)\n"
+ "sip.setapi('QVariant', 2)\n"
+ );
+
+ // Initialize our built-in module.
+ pythonInitwrapper(this);
+ if (!s_pykrita) {
+ return i18nc("@info:tooltip ", "No <icode>pykrita</icode> built-in module");
+ }
+
+ // Setup global configuration
+ m_configuration = PyDict_New();
+ /// \todo Check \c m_configuration ?
+ // Host the configuration dictionary.
+ py.itemStringSet("configuration", m_configuration);
+
+ // Setup per session configuration
+ m_sessionConfiguration = PyDict_New();
+ py.itemStringSet("sessionConfiguration", m_sessionConfiguration);
+
+ // Initialize 'plugins' dict of module 'pykrita'
+ PyObject* plugins = PyDict_New();
+ py.itemStringSet("plugins", plugins);
+
+ // Get plugins available
+ scanPlugins();
+
+ // NOTE Empty failure reson string indicates success!
+ m_engineIsUsable = true;
+ return QString();
+}
+
+int PyKrita::Engine::columnCount(const QModelIndex&) const
+{
+ return Column::LAST__;
+}
+
+int PyKrita::Engine::rowCount(const QModelIndex&) const
+{
+ return m_plugins.size();
+}
+
+QModelIndex PyKrita::Engine::index(const int row, const int column, const QModelIndex& parent) const
+{
+ if (!parent.isValid() && row < m_plugins.size() && column < Column::LAST__)
+ return createIndex(row, column);
+ return QModelIndex();
+}
+
+QModelIndex PyKrita::Engine::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+QVariant PyKrita::Engine::headerData(
+ const int section
+ , const Qt::Orientation orientation
+ , const int role
+) const
+{
+ if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+ switch (section) {
+ case Column::NAME:
+ return i18nc("@title:column", "Name");
+ case Column::COMMENT:
+ return i18nc("@title:column", "Comment");
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant PyKrita::Engine::data(const QModelIndex& index, const int role) const
+{
+ Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
+ Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (index.column()) {
+ case Column::NAME:
+ return m_plugins[index.row()].m_pythonPlugin.name();
+ case Column::COMMENT:
+ return m_plugins[index.row()].m_pythonPlugin.comment();
+ default:
+ break;
+ }
+ break;
+ case Qt::CheckStateRole: {
+ if (index.column() == Column::NAME) {
+ const bool checked = m_plugins[index.row()].isEnabled();
+ return checked ? Qt::Checked : Qt::Unchecked;
+ }
+ break;
+ }
+ case Qt::ToolTipRole:
+ if (!m_plugins[index.row()].m_errorReason.isEmpty())
+ return m_plugins[index.row()].m_errorReason;
+ break;
+ case Qt::ForegroundRole:
+ if (m_plugins[index.row()].isUnstable()) {
+ KColorScheme scheme(QPalette::Inactive, KColorScheme::View);
+ return scheme.foreground(KColorScheme::NegativeText).color();
+ }
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags PyKrita::Engine::flags(const QModelIndex& index) const
+{
+ Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
+ Q_ASSERT("Sanity check" && index.column() < Column::LAST__);
+
+ int result = Qt::ItemIsSelectable;
+ if (index.column() == Column::NAME)
+ result |= Qt::ItemIsUserCheckable;
+ // Disable to select/check broken modules
+ if (!m_plugins[index.row()].isBroken())
+ result |= Qt::ItemIsEnabled;
+ return static_cast<Qt::ItemFlag>(result);
+}
+
+bool PyKrita::Engine::setData(const QModelIndex& index, const QVariant& value, const int role)
+{
+ Q_ASSERT("Sanity check" && index.row() < m_plugins.size());
+
+ if (role == Qt::CheckStateRole) {
+ Q_ASSERT("Sanity check" && !m_plugins[index.row()].isBroken());
+
+ const bool enabled = value.toBool();
+ m_plugins[index.row()].m_enabled = enabled;
+ if (enabled)
+ loadModule(index.row());
+ else
+ unloadModule(index.row());
+ }
+ return true;
+}
+
+QStringList PyKrita::Engine::enabledPlugins() const
+{
+ /// \todo \c std::transform + lambda or even better to use
+ /// filtered and transformed view from boost
+ QStringList result;
+ Q_FOREACH(const PluginState & plugin, m_plugins)
+ if (plugin.isEnabled()) {
+ result.append(plugin.m_pythonPlugin.name());
+ }
+ return result;
+}
+
+void PyKrita::Engine::readGlobalPluginsConfiguration()
+{
+ Python py = Python();
+ PyDict_Clear(m_configuration);
+ KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
+ config.sync();
+ py.updateDictionaryFromConfiguration(m_configuration, &config);
+}
+
+void PyKrita::Engine::saveGlobalPluginsConfiguration()
+{
+ Python py = Python();
+ KConfig config(CONFIG_FILE, KConfig::SimpleConfig);
+ py.updateConfigurationFromDictionary(&config, m_configuration);
+ config.sync();
+}
+
+bool PyKrita::Engine::isPythonPluginUsable(const PyPlugin *pythonPlugin)
+{
+ dbgScript << "Got Krita/PythonPlugin: " << pythonPlugin->name()
+ << ", module-path=" << pythonPlugin->library()
+ ;
+ // Make sure mandatory properties are here
+ if (pythonPlugin->name().isEmpty()) {
+ dbgScript << "Ignore desktop file w/o a name";
+ return false;
+ }
+ if (pythonPlugin->library().isEmpty()) {
+ dbgScript << "Ignore desktop file w/o a module to import";
+ return false;
+ }
+ return true;
+}
+
+bool PyKrita::Engine::setModuleProperties(PluginState& plugin)
+{
+ // Find the module:
+ // 0) try to locate directory based plugin first
+ QString rel_path = QString(Python::PYKRITA_ENGINE);
+ dbgScript << rel_path;
+ rel_path = rel_path + "/" + plugin.moduleFilePathPart();
+ dbgScript << rel_path;
+ rel_path = rel_path + "/" + "__init__.py";
+ dbgScript << rel_path;
+
+ QString module_path = KoResourcePaths::findResource("appdata", rel_path);
+
+ dbgScript << module_path;
+
+ if (module_path.isEmpty()) {
+ // 1) Nothing found, then try file based plugin
+ rel_path = QString(Python::PYKRITA_ENGINE) + "/" + plugin.moduleFilePathPart() + ".py";
+ module_path = KoResourcePaths::findResource("appdata", rel_path);
+ } else {
+ plugin.m_isDir = true;
+ }
+
+ // Is anything found at all?
+ if (module_path.isEmpty()) {
+ plugin.m_broken = true;
+ plugin.m_errorReason = i18nc(
+ "@info:tooltip"
+ , "Unable to find the module specified <application>%1</application>"
+ , plugin.m_pythonPlugin.library()
+ );
+ dbgScript << "Plugin is " << plugin.m_errorReason;
+ return false;
+ }
+ dbgScript << "Found module path:" << module_path;
+ return true;
+}
+
+QPair<QString, PyKrita::version_checker> PyKrita::Engine::parseDependency(const QString& d)
+{
+ // Check if dependency has package info attached
+ const int pnfo = d.indexOf('(');
+ if (pnfo != -1) {
+ QString dependency = d.mid(0, pnfo);
+ QString version_str = d.mid(pnfo + 1, d.size() - pnfo - 2).trimmed();
+ dbgScript << "Desired version spec [" << dependency << "]:" << version_str;
+ version_checker checker = version_checker::fromString(version_str);
+ if (!(checker.isValid() && d.endsWith(')'))) {
+ dbgScript << "Invalid version spec " << d;
+ QString reason = i18nc(
+ "@info:tooltip"
+ , "<p>Specified version has invalid format for dependency <application>%1</application>: "
+ "<icode>%2</icode>. Skipped</p>"
+ , dependency
+ , version_str
+ );
+ return qMakePair(reason, version_checker());
+ }
+ return qMakePair(dependency, checker);
+ }
+ return qMakePair(d, version_checker(version_checker::undefined));
+}
+
+PyKrita::version PyKrita::Engine::tryObtainVersionFromTuple(PyObject* version_obj)
+{
+ Q_ASSERT("Sanity check" && version_obj);
+
+ if (PyTuple_Check(version_obj) == 0)
+ return version::invalid();
+
+ int version_info[3] = {0, 0, 0};
+ for (unsigned i = 0; i < PyTuple_Size(version_obj); ++i) {
+ PyObject* v = PyTuple_GetItem(version_obj, i);
+ if (v && PyLong_Check(v))
+ version_info[i] = PyLong_AsLong(v);
+ else
+ version_info[i] = -1;
+ }
+ if (version_info[0] != -1 && version_info[1] != -1 && version_info[2] != -1)
+ return version(version_info[0], version_info[1], version_info[2]);
+
+ return version::invalid();
+}
+
+/**
+ * Try to parse version string as a simple triplet X.Y.Z.
+ *
+ * \todo Some modules has letters in a version string...
+ * For example current \c pytz version is \e "2013d".
+ */
+PyKrita::version PyKrita::Engine::tryObtainVersionFromString(PyObject* version_obj)
+{
+ Q_ASSERT("Sanity check" && version_obj);
+
+ if (!Python::isUnicode(version_obj))
+ return version::invalid();
+
+ QString version_str = Python::unicode(version_obj);
+ if (version_str.isEmpty())
+ return version::invalid();
+
+ return version::fromString(version_str);
+}
+
+/**
+ * Collect dependencies and check them. To do it
+ * just try to import a module... when unload it ;)
+ *
+ * \c X-Python-Dependencies property of \c .desktop file has the following format:
+ * <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 PyKrita::Engine::verifyDependenciesSetStatus(PluginState& plugin)
+{
+ QStringList dependencies = plugin.m_pythonPlugin.property("X-Python-Dependencies").toStringList();
+#if PY_MAJOR_VERSION < 3
+ {
+ // Try to get Py2 only dependencies
+ QStringList py2_dependencies = plugin.m_service->property("X-Python-2-Dependencies").toStringList();
+ dependencies.append(py2_dependencies);
+ }
+#endif
+
+ Python py = Python();
+ QString reason = i18nc("@info:tooltip", "<title>Dependency check</title>");
+ Q_FOREACH(const QString & d, dependencies) {
+ QPair<QString, version_checker> info_pair = parseDependency(d);
+ version_checker& checker = info_pair.second;
+ if (!checker.isValid()) {
+ plugin.m_broken = true;
+ reason += info_pair.first;
+ continue;
+ }
+
+ dbgScript << "Try to import dependency module/package:" << d;
+
+ // Try to import a module
+ const QString& dependency = info_pair.first;
+ PyObject* module = py.moduleImport(PQ(dependency));
+ if (module) {
+ if (checker.isEmpty()) { // Need to check smth?
+ dbgScript << "No version to check, just make sure it's loaded:" << dependency;
+ Py_DECREF(module);
+ continue;
+ }
+ // Try to get __version__ from module
+ // See PEP396: http://www.python.org/dev/peps/pep-0396/
+ PyObject* version_obj = py.itemString("__version__", PQ(dependency));
+ if (!version_obj) {
+ dbgScript << "No __version__ for " << dependency
+ << "[" << plugin.m_pythonPlugin.name() << "]:\n" << py.lastTraceback()
+ ;
+ plugin.m_unstable = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<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
+ );
+ }
+ // PEP396 require __version__ to tuple of integers... try it!
+ version dep_version = tryObtainVersionFromTuple(version_obj);
+ if (!dep_version.isValid())
+ // Second attempt: some "bad" modules have it as a string
+ dep_version = tryObtainVersionFromString(version_obj);
+
+ // Did we get it?
+ if (!dep_version.isValid()) {
+ // Dunno what is this... Giving up!
+ dbgScript << "***: Can't parse module version for" << dependency;
+ plugin.m_unstable = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<p><application>%1</application>: Unexpected module's version format"
+ , dependency
+ );
+ } else if (!checker(dep_version)) {
+ dbgScript << "Version requirement check failed ["
+ << plugin.m_pythonPlugin.name() << "] for "
+ << dependency << ": wanted " << checker.operationToString()
+ << QString(checker.required())
+ << ", but found" << QString(dep_version)
+ ;
+ plugin.m_broken = true;
+ reason += i18nc(
+ "@info:tooltip"
+ , "<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.m_pythonPlugin.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 PyKrita::Engine::scanPlugins()
+{
+ m_plugins.clear(); // Clear current state.
+
+ QStringList desktopFiles = KoResourcePaths::findAllResources("data", "pykrita/*desktop");
+ qDebug() << desktopFiles;
+
+ Q_FOREACH(const QString &desktopFile, desktopFiles) {
+
+ QSettings s(desktopFile, QSettings::IniFormat);
+ s.beginGroup("Desktop Entry");
+ if (s.value("ServiceTypes").toString() == "Krita/PythonPlugin") {
+ PyPlugin pyplugin;
+ pyplugin.m_comment = s.value("Comment").toString();
+ pyplugin.m_name = s.value("Name").toString();
+ pyplugin.m_libraryPath = s.value("X-KDE-Library").toString();
+ pyplugin.m_properties["X-Python-2-Compatible"] = s.value("X-Python-2-Compatible", false).toBool();
+
+ if (!isPythonPluginUsable(&pyplugin)) {
+ dbgScript << pyplugin.name() << "is not usable";
+ continue;
+ }
+
+ PluginState pluginState;
+ pluginState.m_pythonPlugin = pyplugin;
+
+ if (!setModuleProperties(pluginState)) {
+ dbgScript << "Cannot load" << pyplugin.name() << ": broken"
+ << pluginState.isBroken()
+ << "because:" << pluginState.errorReason();
+ continue;
+ }
+
+ verifyDependenciesSetStatus(pluginState);
+ m_plugins.append(pluginState);
+ }
+ }
+}
+
+void PyKrita::Engine::setEnabledPlugins(const QStringList& enabled_plugins)
+{
+ for (int i = 0; i < m_plugins.size(); ++i) {
+ m_plugins[i].m_enabled = enabled_plugins.indexOf(m_plugins[i].m_pythonPlugin.name()) != -1;
+ }
+}
+
+void PyKrita::Engine::tryLoadEnabledPlugins()
+{
+ for (int i = 0; i < m_plugins.size(); ++i) {
+ dbgScript << "Trying to load plugin" << m_plugins[i].pythonModuleName()
+ << ". Enabled:" << m_plugins[i].isEnabled()
+ << ". Broken: " << m_plugins[i].isBroken();
+ if (!m_plugins[i].isBroken()) {
+ m_plugins[i].m_enabled = true;
+ loadModule(i);
+ }
+ }
+}
+
+void PyKrita::Engine::loadModule(const int idx)
+{
+ Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
+ PluginState& plugin = m_plugins[idx];
+ Q_ASSERT(
+ "Why to call loadModule() for disabled/broken plugin?"
+ && plugin.isEnabled()
+ && !plugin.isBroken()
+ );
+
+ QString module_name = plugin.pythonModuleName();
+ dbgScript << "Loading module: " << module_name;
+
+ Python py = Python();
+
+ // Get 'plugins' key from 'pykrita' module dictionary.
+ // Every entry has a module name as a key and 2 elements tuple as a value
+ PyObject* plugins = py.itemString("plugins");
+ Q_ASSERT(
+ "'plugins' dict expected to be alive, otherwise code review required!"
+ && plugins
+ );
+
+ PyObject* module = py.moduleImport(PQ(module_name));
+ if (module) {
+ // Move just loaded module to the dict
+ const int ins_result = PyDict_SetItemString(plugins, PQ(module_name), module);
+ Q_ASSERT("expected successful insertion" && ins_result == 0);
+ Py_DECREF(module);
+ // Handle failure in release mode.
+ if (ins_result == 0) {
+ // Initialize the module from Python's side
+ PyObject* const args = Py_BuildValue("(s)", PQ(module_name));
+ PyObject* result = py.functionCall("_pluginLoaded", Python::PYKRITA_ENGINE, args);
+ Py_DECREF(args);
+ if (result) {
+ dbgScript << "\t" << "success!";
+ return;
+ }
+ }
+ plugin.m_errorReason = i18nc("@info:tooltip", "Internal engine failure");
+ } else {
+ plugin.m_errorReason = i18nc(
+ "@info:tooltip"
+ , "Module not loaded:<nl/>%1"
+ , py.lastTraceback()
+ );
+ }
+ plugin.m_broken = true;
+ warnScript << "Error loading plugin" << module_name;
+}
+
+void PyKrita::Engine::unloadModule(int idx)
+{
+ Q_ASSERT("Plugin index is out of range!" && 0 <= idx && idx < m_plugins.size());
+ PluginState& plugin = m_plugins[idx];
+ Q_ASSERT("Why to call unloadModule() for broken plugin?" && !plugin.isBroken());
+
+ dbgScript << "Unloading module: " << plugin.pythonModuleName();
+
+ Python py = Python();
+
+ // Get 'plugins' key from 'pykrita' module dictionary
+ PyObject* plugins = py.itemString("plugins");
+ Q_ASSERT(
+ "'plugins' dict expected to be alive, otherwise code review required!"
+ && plugins
+ );
+
+ PyObject* const args = Py_BuildValue("(s)", PQ(plugin.pythonModuleName()));
+ py.functionCall("_pluginUnloading", Python::PYKRITA_ENGINE, args);
+ Py_DECREF(args);
+
+ // This will just decrement a reference count for module instance
+ PyDict_DelItemString(plugins, PQ(plugin.pythonModuleName()));
+
+ // Remove the module also from 'sys.modules' dict to really unload it,
+ // so if reloaded all @init actions will work again!
+ PyObject* sys_modules = py.itemString("modules", "sys");
+ Q_ASSERT("Sanity check" && sys_modules);
+ PyDict_DelItemString(sys_modules, PQ(plugin.pythonModuleName()));
+}
+
+// krita: space-indent on; indent-width 4;
+#undef PYKRITA_INIT
diff --git a/plugins/extensions/pykrita/plugin/engine.h b/plugins/extensions/pykrita/plugin/engine.h
new file mode 100644
index 0000000000..8575f47a8c
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/engine.h
@@ -0,0 +1,234 @@
+/*
+ * 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>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __PYKRITA_ENGINE_H__
+# define __PYKRITA_ENGINE_H__
+
+#include <cmath>
+#include <Python.h>
+
+#include "version_checker.h"
+
+#include <QAbstractItemModel>
+#include <QList>
+#include <QStringList>
+
+namespace PyKrita
+{
+
+/**
+ * @brief The PyPlugin class describes a plugin written in Python and loaded into the system
+ */
+class PyPlugin {
+
+public:
+
+ PyPlugin()
+ {
+ m_properties["X-Python-Dependencies"] = QStringList();
+ m_properties["X-Python-2-Dependencies"] = QStringList();
+ }
+
+ QString name() const
+ {
+ return m_name;
+ }
+
+ QString library() const
+ {
+ return m_libraryPath;
+ }
+
+ QVariant property(const QString &name) const
+ {
+ return m_properties.value(name, "");
+ }
+
+ QString comment() const
+ {
+ return m_comment;
+ }
+
+ QString m_name;
+ QString m_libraryPath;
+ QMap<QString, QVariant> m_properties;
+ QString m_comment;
+};
+
+class Python; // fwd decl
+
+/**
+ * The Engine class hosts the Python interpreter, loading
+ * it into memory within Krita, and then with finding and
+ * loading all of the PyKrita plugins.
+ *
+ * \attention Qt/KDE does not use exceptions (unfortunately),
+ * so this class must be initialized in two steps:
+ * - create an instance (via constructor)
+ * - try to initialize the rest (via \c Engine::tryInitializeGetFailureReason())
+ * If latter returns a non empty (failure reason) string, the only member
+ * can be called is conversion to boolean! (which is implemented as safe-bool idiom [1])
+ * Calling others leads to UB!
+ *
+ * \sa [1] http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool
+ */
+class Engine : public QAbstractItemModel
+{
+ Q_OBJECT
+
+ typedef void (Engine::*bool_type)() const;
+ void unspecified_true_bool_type() const {}
+
+public:
+ /// \todo Turn into a class w/ accessors
+ class PluginState
+ {
+ public:
+ /// \name Immutable accessors
+ //@{
+ QString pythonModuleName() const;
+ const QString& errorReason() const;
+ bool isEnabled() const;
+ bool isBroken() const;
+ bool isUnstable() const;
+ //@}
+
+ private:
+ friend class Engine;
+
+ PluginState();
+ /// Transfort Python module name into a file path part
+ QString moduleFilePathPart() const;
+
+ PyPlugin m_pythonPlugin;
+ QString m_pythonModule;
+ QString m_errorReason;
+ bool m_enabled;
+ bool m_broken;
+ bool m_unstable;
+ bool m_isDir;
+ };
+
+ /// Default constructor: initialize Python interpreter
+ Engine();
+ /// Cleanup everything on unload
+ ~Engine();
+
+ //BEGIN QAbstractItemModel interface
+ virtual int columnCount(const QModelIndex&) const /*override*/;
+ virtual int rowCount(const QModelIndex&) const /*override*/;
+ virtual QModelIndex index(int, int, const QModelIndex&) const /*override*/;
+ virtual QModelIndex parent(const QModelIndex&) const /*override*/;
+ virtual QVariant headerData(int, Qt::Orientation, int) const /*override*/;
+ virtual QVariant data(const QModelIndex&, int) const /*override*/;
+ virtual Qt::ItemFlags flags(const QModelIndex&) const /*override*/;
+ virtual bool setData(const QModelIndex&, const QVariant&, int) /*override*/;
+ //END QAbstractItemModel interface
+
+ void setEnabledPlugins(const QStringList&); ///< Set enabled plugins to the model
+ void tryLoadEnabledPlugins(); ///< Try to load enabled plugins
+ QStringList enabledPlugins() const; ///< Form a list of enabled plugins
+ const QList<PluginState>& plugins() const; ///< Provide immutable access to found plugins
+ QString tryInitializeGetFailureReason(); ///< Try to initialize Python interpreter
+ operator bool_type() const; ///< Check if instance is usable
+ void setBroken(); ///< Make it broken by some external reason
+
+public Q_SLOTS:
+ void readGlobalPluginsConfiguration(); ///< Load plugins' configuration.
+ void saveGlobalPluginsConfiguration(); ///< Write out plugins' configuration.
+ void unloadAllModules();
+
+protected:
+ void scanPlugins(); ///< Search for available plugins
+ void loadModule(int); ///< Load module by index in \c m_plugins
+ void unloadModule(int); ///< Unload module by index in \c m_plugins
+
+private:
+ // Simulate strong typed enums from C++11
+ struct Column {
+ enum type {
+ NAME
+ , COMMENT
+ , LAST__
+ };
+ };
+
+ static bool isPythonPluginUsable(const PyPlugin *pyPlugin); ///< Make sure that service is usable
+ static bool setModuleProperties(PluginState&);
+ static void verifyDependenciesSetStatus(PluginState&);
+ static QPair<QString, version_checker> parseDependency(const QString&);
+ static version tryObtainVersionFromTuple(PyObject*);
+ static version tryObtainVersionFromString(PyObject*);
+
+ PyObject* m_configuration; ///< Application-wide configuration data
+ PyObject* m_sessionConfiguration; ///< Session-wide configuration data
+ QList<PluginState> m_plugins; ///< List of available plugins
+ bool m_engineIsUsable; ///< Is engine loaded Ok?
+};
+
+inline QString Engine::PluginState::pythonModuleName() const
+{
+ return m_pythonPlugin.library();
+}
+
+inline QString PyKrita::Engine::PluginState::moduleFilePathPart() const
+{
+ return m_pythonPlugin.library().replace(".", "/");
+}
+
+inline const QString& Engine::PluginState::errorReason() const
+{
+ return m_errorReason;
+}
+
+inline bool Engine::PluginState::isEnabled() const
+{
+ return m_enabled;
+}
+
+inline bool Engine::PluginState::isBroken() const
+{
+ return m_broken;
+}
+
+inline bool Engine::PluginState::isUnstable() const
+{
+ return m_unstable;
+}
+
+inline const QList<Engine::PluginState>& Engine::plugins() const
+{
+ return m_plugins;
+}
+
+inline Engine::operator bool_type() const
+{
+ return m_engineIsUsable ? &Engine::unspecified_true_bool_type : 0;
+}
+
+inline void Engine::setBroken()
+{
+ m_engineIsUsable = false;
+}
+
+} // namespace PyKrita
+#endif // __PYKRITA_ENGINE_H__
diff --git a/plugins/extensions/pykrita/plugin/info.ui b/plugins/extensions/pykrita/plugin/info.ui
new file mode 100644
index 0000000000..521323d622
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/info.ui
@@ -0,0 +1,298 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InfoPage</class>
+ <widget class="QWidget" name="InfoPage">
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="0" colspan="2">
+ <widget class="KTabWidget" name="tabs">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab2">
+ <attribute name="title">
+ <string>Actions</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="tab2lo">
+ <item>
+ <layout class="QGridLayout" name="tab2_1lo">
+ <item row="1" column="3" rowspan="8">
+ <widget class="QIconButton" name="actionIcon">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="whatsThis">
+ <string>The icon associated with this action. It is shown alongside text in the menu bar and in toolbars as required. A string to use KDE's image loading system, or a custom QPixmap or QIcon, or None.</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" rowspan="5">
+ <widget class="QLabel" name="shortcut">
+ <property name="whatsThis">
+ <string>The shortcut to fire this action, such as 'Ctrl+1', or a QKeySequence instance, or None.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <widget class="QLabel" name="labelTopics">
+ <property name="text">
+ <string>Description:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>topics</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="1" colspan="3">
+ <widget class="QTextEdit" name="description">
+ <property name="textInteractionFlags">
+ <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelText">
+ <property name="text">
+ <string>Menu Item:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="text">
+ <property name="whatsThis">
+ <string>The text associated with the action (used as the menu item label, etc), or None.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" rowspan="2">
+ <widget class="QLabel" name="menu">
+ <property name="whatsThis">
+ <string>The menu under which to place this item, such as 'tools' or 'settings', or None.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" rowspan="5">
+ <widget class="QLabel" name="labelShortcut">
+ <property name="text">
+ <string>Shortcut:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>fullName</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" rowspan="2">
+ <widget class="QLabel" name="labelMenu">
+ <property name="text">
+ <string>Menu:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>menu</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2" rowspan="8">
+ <widget class="QLabel" name="labelActionIcon">
+ <property name="text">
+ <string>Icon:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>configPageIcon</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="3">
+ <widget class="KComboBox" name="actions"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Action:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="tab3">
+ <attribute name="title">
+ <string>Configuration Pages</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="tab3lo">
+ <item>
+ <layout class="QGridLayout" name="tab3_1lo">
+ <item row="1" column="1" colspan="3">
+ <widget class="QLabel" name="fullName">
+ <property name="whatsThis">
+ <string>The shortcut to fire this action such as 'Ctrl+1', or a QKeySequence instance, or None.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Title:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2" rowspan="5">
+ <widget class="QLabel" name="labelPageIcon">
+ <property name="text">
+ <string>Icon:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>configPageIcon</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="3" rowspan="5">
+ <widget class="QIconButton" name="configPageIcon">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="whatsThis">
+ <string>The icon associated with this action. It is shown alongside text in the menu bar and in toolbars as required. A string to use KDE's image loading system, or a custom QPixmap or QIcon, or None.</string>
+ </property>
+ <property name="flat">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" rowspan="5">
+ <widget class="QLabel" name="labelName">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" rowspan="5">
+ <widget class="QLabel" name="name">
+ <property name="whatsThis">
+ <string>The text associated with the action (used as the menu item label, etc), or None.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="3">
+ <widget class="KComboBox" name="configPages"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Page:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="KComboBox" name="topics">
+ <property name="whatsThis">
+ <string>Select a Plugin or Built-in Module</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QIconButton</class>
+ <extends>QPushButton</extends>
+ <header>kicondialog.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KComboBox</class>
+ <extends>QComboBox</extends>
+ <header>kcombobox.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KTabWidget</class>
+ <extends>QTabWidget</extends>
+ <header>ktabwidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>topics</tabstop>
+ <tabstop>tabs</tabstop>
+ <tabstop>actions</tabstop>
+ <tabstop>configPages</tabstop>
+ <tabstop>fullName</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/extensions/pykrita/plugin/krita/__init__.py b/plugins/extensions/pykrita/plugin/krita/__init__.py
new file mode 100644
index 0000000000..00104e7070
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/__init__.py
@@ -0,0 +1,69 @@
+import pykrita
+import os
+import sys
+
+from .api import *
+from .decorators import *
+from .dockwidgetfactory import *
+from PyKrita import krita
+
+krita_path = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, krita_path)
+print("%s added to PYTHONPATH" % krita_path, file=sys.stderr)
+
+# Look for PyQt
+try:
+ from PyQt5 import QtCore
+except ImportError:
+ print("Python cannot find the Qt5 bindings.", file=sys.stderr)
+ print("Please make sure, that the needed packages are installed.", file=sys.stderr)
+ raise
+
+# Shows nice looking error dialog if an unhandled exception occures.
+import excepthook
+excepthook.install()
+
+import builtins
+builtins.i18n = lambda s: unicode(QCoreApplication.translate("PyKrita", s))
+builtins.Scripter = Krita.instance()
+builtins.Application = Krita.instance()
+builtins.Scripter = Krita.instance()
+
+def qDebug(text):
+ '''Use KDE way to show debug info
+
+ TODO Add a way to control debug output from partucular plugins (?)
+ '''
+ plugin = sys._getframe(1).f_globals['__name__']
+ pykrita.qDebug('{}: {}'.format(plugin, text))
+
+
+@pykritaEventHandler('_pluginLoaded')
+def on_load(plugin):
+ if plugin in init.functions:
+ # Call registered init functions for the plugin
+ init.fire(plugin=plugin)
+ del init.functions[plugin]
+ return True
+
+
+@pykritaEventHandler('_pluginUnloading')
+def on_unload(plugin):
+ if plugin in unload.functions:
+ # Deinitialize plugin
+ unload.fire(plugin=plugin)
+ del unload.functions[plugin]
+ return True
+
+
+@pykritaEventHandler('_pykritaLoaded')
+def on_pykrita_loaded():
+ qDebug('PYKRITA LOADED')
+ return True
+
+
+@pykritaEventHandler('_pykritaUnloading')
+def on_pykrita_unloading():
+ qDebug('UNLOADING PYKRITA')
+ return True
+
diff --git a/plugins/extensions/pykrita/plugin/krita/api.py b/plugins/extensions/pykrita/plugin/krita/api.py
new file mode 100644
index 0000000000..66e07e5feb
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/api.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+# Copyright (C) 2013 Shaheed Haque <srhaque@theiet.org>
+# Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+# Copyright (C) 2014-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) version 3.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public License
+# along with this library; see the file COPYING.LIB. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+'''Provide shortcuts to access krita internals from plugins'''
+
+import contextlib
+import os
+import sys
+
+from PyKrita.krita import *
+
+import pykrita
+
+def objectIsAlive(obj):
+ ''' Test whether an object is alive; that is, whether the pointer
+ to the object still exists. '''
+ import sip
+ try:
+ sip.unwrapinstance(obj)
+ except RuntimeError:
+ return False
+ return True
+
+
+def qDebug(text):
+ '''Use KDE way to show debug info
+
+ TODO Add a way to control debug output from partucular plugins (?)
+ '''
+ plugin = sys._getframe(1).f_globals['__name__']
+ pykrita.qDebug('{}: {}'.format(plugin, text))
+
diff --git a/plugins/extensions/pykrita/plugin/krita/attic/mikro.py b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py
new file mode 100644
index 0000000000..eb1b3cec23
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/attic/mikro.py
@@ -0,0 +1,438 @@
+# -*- coding: utf-8 -*-
+"""
+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.
+"""
+
+"""
+Mini Kross - a scripting solution inspired by Kross (http://kross.dipe.org/)
+
+Technically this is one of the most important modules in Scripter.
+Via the Qt meta object system it provides access to unwrapped objects.
+This code uses a lot of metaprogramming magic. To fully understand it,
+you have to know about metaclasses in Python
+"""
+
+import sys
+import sip
+from PyQt5.QtCore import QVariant, QMetaObject, Q_RETURN_ARG, Q_ARG, QObject, Qt, QMetaMethod, pyqtSignal
+from PyQt5.QtGui import QBrush, QFont, QImage, QPalette, QPixmap
+from PyQt5.QtWidgets import qApp
+
+
+variant_converter = {
+ "QVariantList": lambda v: v.toList(v),
+ "QVariantMap": lambda v: toPyObject(v),
+ "QPoint": lambda v: v.toPoint(),
+ "str": lambda v: v.toString(),
+ "int": lambda v: v.toInt()[0],
+ "double": lambda v: v.toDouble()[0],
+ "char": lambda v: v.toChar(),
+ "QByteArray": lambda v: v.toByteArray(),
+ "QPoint": lambda v: v.toPoint(),
+ "QPointF": lambda v: v.toPointF(),
+ "QSize": lambda v: v.toSize(),
+ "QLine": lambda v: v.toLine(),
+ "QStringList": lambda v: v.toStringList(),
+ "QTime": lambda v: v.toTime(),
+ "QDateTime": lambda v: v.toDateTime(),
+ "QDate": lambda v: v.toDate(),
+ "QLocale": lambda v: v.toLocale(),
+ "QUrl": lambda v: v.toUrl(),
+ "QRect": lambda v: v.toRect(),
+ "QBrush": lambda v: QBrush(v),
+ "QFont": lambda v: QFont(v),
+ "QPalette": lambda v: QPalette(v),
+ "QPixmap": lambda v: QPixmap(v),
+ "QImage": lambda v: QImage(v),
+ "bool": lambda v: v.toBool(),
+ "QObject*": lambda v: wrap_variant_object(v),
+ "QWidget*": lambda v: wrap_variant_object(v),
+ "ActionMap": lambda v: int(v.count())
+}
+
+def wrap_variant_object(variant):
+ """
+ convert a QObject or a QWidget to its wrapped superclass
+ """
+ o = Krita.fromVariant(variant)
+ return wrap(o, True)
+
+def from_variant(variant):
+ """
+ convert a QVariant to a Python value
+ """
+ # Check whether it's really a QVariant
+ if hasattr(variant, '__type__') and not (variant is None or variant.type() is None):
+ typeName = variant.typeName()
+ convert = variant_converter.get(typeName)
+ if not convert:
+ raise ValueError("Could not convert value to %s" % typeName)
+ else:
+ v = convert(variant)
+ return v
+
+ # Give up and return
+ return variant
+
+def convert_value(value):
+ """
+ Convert a given value, upcasting to the highest QObject-based class if possible,
+ unpacking lists and dicts.
+ """
+
+ # Check whether it's a dict: if so, convert the keys/values
+ if hasattr(value, '__class__') and issubclass(value.__class__, dict) and len(value) > 0:
+ return {convert_value(k): convert_value(v) for k, v in value.items()}
+
+ # Check whether it's a list: if so, convert the values
+ if hasattr(value, '__class__') and issubclass(value.__class__, list) and len(value) > 0:
+ return [convert_value(v) for v in value]
+
+ if isinstance(value, str):
+ # prefer Python strings
+ return str(value)
+
+ elif isinstance(value, PyQtClass):
+ # already wrapped
+ return value
+
+ # Check whether it's a QObject
+ if hasattr(value, '__class__') and issubclass(value.__class__, QObject):
+ return wrap(value, True)
+
+ if hasattr(value, '__type__') and not (value is None or value.type() is None) :
+ return from_variant(value);
+
+ return value
+
+qtclasses = {}
+
+def wrap(obj, force=False):
+ """
+ If a class is not known by PyQt it will be automatically
+ casted to a known wrapped super class.
+
+ But that limits access to methods and propperties of this super class.
+ So instead this functions returns a wrapper class (PyQtClass)
+ which queries the metaObject and provides access to
+ all slots and all properties.
+ """
+ if isinstance(obj, str):
+ # prefer Python strings
+ return str(obj)
+ elif isinstance(obj, PyQtClass):
+ # already wrapped
+ return obj
+ elif obj and isinstance(obj, QObject):
+ if force or obj.__class__.__name__ != obj.metaObject().className():
+ # Ah this is an unwrapped class
+ obj = create_pyqt_object(obj)
+ return obj
+
+def unwrap(obj):
+ """
+ if wrapped returns the wrapped object
+ """
+ if hasattr(obj, "qt"):
+ obj = obj.qt
+ return obj
+
+
+
+def is_qobject(obj):
+ """
+ checks if class or wrapped class is a subclass of QObject
+ """
+ if hasattr(obj, "__bases__") and issubclass(unwrap(obj), QObject):
+ return True
+ else:
+ return False
+
+
+def is_scripter_child(qobj):
+ """
+ walk up the object tree until Scripter or the root is found
+ """
+ found = False
+ p = qobj.parent()
+ while p and not found:
+ if str(p.objectName()) == "Krita":
+ found = True
+ break
+ else:
+ p = p.parent()
+ return found
+
+
+
+class Error(Exception):
+ """
+ Base error classed. Catch this to handle exceptions comming from C++
+ """
+
+
+
+class PyQtClass(object):
+ """
+ Base class
+ """
+
+ def __init__(self, instance):
+ self._instance = instance
+
+
+ def __del__(self):
+ """
+ If this object is deleted it should also delete the wrapped object
+ if it was created explicitly for this use.
+ """
+ qobj = self._instance
+ if is_scripter_child(qobj):
+ if len(qobj.children()):
+ print("Cannot delete", qobj, "because it has child objects")
+ sip.delete(qobj)
+
+
+ def setProperty(self, name, value):
+ self._instance.setProperty(name, value)
+
+
+ def getProperty(self, name):
+ return wrap(self._instance.property(name))
+
+
+ def propertyNames(self):
+ return list(self.__class__.__properties__.keys())
+
+
+ def dynamicPropertyNames(self):
+ return self._instance.dynamicPropertyNames()
+
+
+ def metaObject(self):
+ return self._instance.metaObject()
+
+
+ def connect(self, signal, slot):
+ getattr(self._instance, signal).connect(slot)
+
+
+ def disconnect(self, signal, slot):
+ getattr(self._instance, signal).disconnect(slot)
+
+
+ def parent(self):
+ return wrap(self._instance.parent())
+
+
+ def children(self):
+ return [wrap(c) for c in self._instance.children()]
+
+
+ @property
+ def qt(self):
+ return self._instance
+
+
+ def __getitem__(self, key):
+ if isinstance(key, int):
+ length = getattr(self, "length", None)
+ if length is not None:
+ # array protocol
+ try:
+ return getattr(self, str(key))
+ except AttributeError as e:
+ raise IndexError(key)
+ else:
+ return self.children()[key]
+ else:
+ return getattr(self, key)
+
+
+ def __getattr__(self, name):
+ # Make named child objects available as attributes like QtQml
+ # Check whether the object is in the QObject hierarchy
+ for child in self._instance.children():
+ if str(child.objectName()) == name:
+ obj = wrap(child)
+ # Save found object for faster lookup
+ setattr(self, name, obj)
+ return obj
+
+ # Check whether it's a property
+ v = self._instance.property(name)
+ return convert_value(v)
+
+ @property
+ def __members__(self):
+ """
+ This method is for introspection.
+ Using dir(thispyqtclass_object) returns a list of
+ all children, methods, properties and dynamic properties.
+ """
+ names = list(self.__dict__.keys())
+ for c in self._instance.children():
+ child_name = str(c.objectName())
+ if child_name:
+ names.append(child_name)
+ for pn in self._instance.dynamicPropertyNames():
+ names.append(str(pn))
+ return names
+
+
+ def __enter__(self):
+ print("__enter__", self)
+
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ print("__exit__", self, exc_type, exc_value, traceback)
+
+
+
+
+class PyQtProperty(object):
+
+ # slots for more speed
+ __slots__ = ["meta_property", "name", "__doc__", "read_only"]
+
+
+ def __init__(self, meta_property):
+ self.meta_property = meta_property
+ self.name = meta_property.name()
+ self.read_only = not meta_property.isWritable()
+ self.__doc__ = "%s is a %s%s" % (
+ self.name, meta_property.typeName(),
+ self.read_only and " (read-only)" or ""
+ )
+
+
+ def get(self, obj):
+ return convert_value(self.meta_property.read(obj._instance))
+
+
+ def set(self, obj, value):
+ self.meta_property.write(obj._instance, value)
+
+
+
+
+class PyQtMethod(object):
+
+ __slots__ = ["meta_method", "name", "args", "returnType", "__doc__"]
+
+
+ def __init__(self, meta_method):
+ self.meta_method = meta_method
+ self.name, args = str(meta_method.methodSignature(), encoding="utf-8").split("(", 1)
+ self.args = args[:-1].split(",")
+ self.returnType = str(meta_method.typeName())
+
+ types = [str(t, encoding="utf-8") for t in meta_method.parameterTypes()]
+ names = [str(n, encoding="utf-8") or "arg%i" % (i+1) \
+ for i, n in enumerate(meta_method.parameterNames())]
+ params = ", ".join("%s %s" % (t, n) for n, t in zip(types, names))
+
+ self.__doc__ = "%s(%s)%s" % (
+ self.name, params,
+ self.returnType and (" -> %s" % self.returnType) or ""
+ )
+
+ def instancemethod(self):
+ def wrapper(obj, *args):
+ qargs = [Q_ARG(t, v) for t, v in zip(self.args, args)]
+ invoke_args = [obj._instance, self.name]
+ invoke_args.append(Qt.DirectConnection)
+ rtype = self.returnType
+ if rtype:
+ invoke_args.append(Q_RETURN_ARG(rtype))
+ invoke_args.extend(qargs)
+ try:
+ result = QMetaObject.invokeMethod(*invoke_args)
+ except RuntimeError as e:
+ raise TypeError(
+ "%s.%s(%r) call failed: %s" % (obj, self.name, args, e))
+ return wrap(result)
+ wrapper.__doc__ = self.__doc__
+ return wrapper
+
+
+
+
+# Cache on-the-fly-created classes for better speed
+pyqt_classes = {}
+
+def create_pyqt_class(metaobject):
+ class_name = str(metaobject.className())
+ cls = pyqt_classes.get(class_name)
+ if cls:
+ return cls
+ attrs = {}
+
+ properties = attrs["__properties__"] = {}
+ for i in range(metaobject.propertyCount()):
+ prop = PyQtProperty(metaobject.property(i))
+ prop_name = str(prop.name)
+ if prop.read_only:
+ properties[prop_name] = attrs[prop_name] = property(prop.get, doc=prop.__doc__)
+ else:
+ properties[prop_name] = attrs[prop_name] = property(
+ prop.get, prop.set, doc=prop.__doc__)
+
+ methods = attrs["__methods__"] = {}
+ signals = attrs["__signals__"] = {}
+ for i in range(metaobject.methodCount()):
+ meta_method = metaobject.method(i)
+ if meta_method.methodType() != QMetaMethod.Signal :
+ method = PyQtMethod(meta_method)
+ method_name = method.name
+ if method_name in attrs:
+ # There is already a property with this name
+ # So append an underscore
+ method_name += "_"
+ instance_method = method.instancemethod()
+ instance_method.__doc__ = method.__doc__
+ methods[method_name] = attrs[method_name] = instance_method
+ else :
+ method_name = meta_method.name()
+ signal_attrs = []
+ properties[bytes(method_name).decode('ascii')] = pyqtSignal(meta_method.parameterTypes())
+
+ # Dynamically create a class with a base class and a dictionary
+ cls = type(class_name, (PyQtClass,), attrs)
+ pyqt_classes[class_name] = cls
+ return cls
+
+
+
+def create_pyqt_object(obj):
+ """
+ Wrap a QObject and make all slots and properties dynamically available.
+ @type obj: QObject
+ @param obj: an unwrapped QObject
+ @rtype: PyQtClass object
+ @return: dynamically created object with all available properties and slots
+
+ This is probably the only function you need from this module.
+ Everything else are helper functions and classes.
+ """
+ cls = create_pyqt_class(obj.metaObject())
+ return cls(obj)
+
+
+
+
+
diff --git a/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py
new file mode 100644
index 0000000000..b052bd2bbc
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/attic/scripter_hooks.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+"""
+This module will be a collection of functions to hook into the GUI of Scribus.
+
+Currently it only provides functions to add items to a menubar.
+Support for the toolbar, statusbar and dockarea have still to be implemented.
+I have to think about how to provide this stuff to QtQml.
+"""
+
+from PyQt5.QtWidgets import QApplication, QMenu
+
+import mikro
+
+
+class MenuHooks(object):
+ """
+ This class lets extension-scripts hook into the main menu of Scribus.
+ """
+
+
+ def __init__(self, window=None):
+ self.window = window or Scripter.dialogs.mainWindow.qt
+ self.menubar = self.window.menuBar()
+ self.menus = []
+
+
+ def createMenu(self, title):
+ m = QMenu(title)
+ self.menus.append(m)
+ self.menubar.addMenu(m)
+ return m
+
+
+ def iter_menus(self):
+ for action in self.menubar.actions():
+ menu = action.menu()
+ if menu:
+ yield menu
+
+ def iter_inner_menus(self, menu):
+ for action in menu.actions():
+ menu = action.menu()
+ if menu:
+ yield menu
+
+
+ def findMenu(self, title):
+ """
+ find a menu with a given title
+
+ @type title: string
+ @param title: English title of the menu
+ @rtype: QMenu
+ @return: None if no menu was found, else the menu with title
+ """
+ # See also http://pyqt.sourceforge.net/Docs/PyQt5/i18n.html#differences-between-pyqt5-and-qt
+ title = QApplication.translate(mikro.classname(self.window), title)
+ for menu in self.iter_menus():
+ if menu.title() == title:
+ return menu
+ for innerMenu in self.iter_inner_menus(menu):
+ if innerMenu.title() == title:
+ return innerMenu
+
+
+ def actionForMenu(self, menu):
+ for action in self.menubar.actions():
+ if action.menu() == menu:
+ return action
+
+
+ def insertMenuBefore(self, before_menu, new_menu):
+ """
+ Insert a menu after another menu in the menubar
+
+ @type: before_menu QMenu instance or title string of menu
+ @param before_menu: menu which should be after the newly inserted menu
+ @rtype: QAction instance
+ @return: action for inserted menu
+ """
+ if isinstance(before_menu, basestring):
+ before_menu = self.findMenu(before_menu)
+ before_action = self.actionForMenu(before_menu)
+ # I have no clue why QMenuBar::insertMenu only allows
+ # to insert before another menu and not after a menu...
+ new_action = self.menubar.insertMenu(before_action, new_menu)
+ return new_action
+
+
+ def menuAfter(self, menu):
+ # This method is needed for insertMenuAfter because
+ # QMenuBar.insertMenu can only insert before another menu
+ previous = None
+ for m in self.iter_menus():
+ if previous and previous == menu:
+ return m
+ previous = m
+
+
+ def appendMenu(self, menu):
+ """
+ Probably not that usefull
+ because it will add a menu after the help menu
+ """
+ action = self.menubar.addMenu(menu)
+ return action
+
+
+ def insertMenuAfter(self, after_menu, new_menu):
+ """
+ Insert a menu before another menu in the menubar
+ """
+ if isinstance(after_menu, basestring):
+ after_menu = self.findMenu(after_menu)
+ after_after_menu = self.menuAfter(after_menu)
+ if after_after_menu:
+ return self.insertMenuBefore(after_after_menu, new_menu)
+ else:
+ return self.appendMenu(new_menu)
+
+
+ def appendItem(self, menu, item, *extra_args):
+ if isinstance(menu, basestring):
+ title = menu
+ menu = self.findMenu(title)
+ if not menu:
+ raise ValueError("Menu %r not found" % title)
+ if isinstance(item, QMenu):
+ action = menu.addMenu(item)
+ else:
+ action = menu.addAction(item, *extra_args)
+ return action
+
+
+ def appendSeparator(self, menu):
+ if isinstance(menu, basestring):
+ menu = self.findMenu(menu)
+ menu.addSeparator()
diff --git a/plugins/extensions/pykrita/plugin/krita/decorators.py b/plugins/extensions/pykrita/plugin/krita/decorators.py
new file mode 100644
index 0000000000..b6c0789da7
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/decorators.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006 Paul Giannaros <paul@giannaros.org>
+# Copyright (C) 2013 Shaheed Haque <srhaque@theiet.org>
+# Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+# Copyright (C) 2014-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) version 3.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public License
+# along with this library; see the file COPYING.LIB. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA 02110-1301, USA.
+
+'''Decorators used in plugins'''
+
+import functools
+import inspect
+import sys
+import traceback
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+import pykrita
+
+from .api import *
+
+#
+# initialization related stuff
+#
+
+def pykritaEventHandler(event):
+ def _decorator(func):
+ setattr(pykrita, event, func)
+ del func
+ return _decorator
+
+
+def _callAll(plugin, functions, *args, **kwargs):
+ if plugin in functions:
+ for f in functions[plugin]:
+ try:
+ f(*args, **kwargs)
+ except:
+ traceback.print_exc()
+ sys.stderr.write('\n')
+ # TODO Return smth to a caller, so in case of
+ # failed initialization it may report smth to the
+ # C++ level and latter can show an error to the user...
+ continue
+
+
+def _simpleEventListener(func):
+ # automates the most common decorator pattern: calling a bunch
+ # of functions when an event has occurred
+ func.functions = dict()
+ func.fire = functools.partial(_callAll, functions=func.functions)
+ func.clear = func.functions.clear
+ return func
+
+
+def _registerCallback(plugin, event, func):
+ if plugin not in event.functions:
+ event.functions[plugin] = set()
+
+ event.functions[plugin].add(func)
+ return func
+
+
+@_simpleEventListener
+def init(func):
+ ''' The function will be called when particular plugin has loaded
+ and the configuration has been initiated
+ '''
+ plugin = sys._getframe(1).f_globals['__name__']
+ qDebug('@init: {}/{}'.format(plugin, func.__name__))
+ return _registerCallback(plugin, init, func)
+
+
+@_simpleEventListener
+def unload(func):
+ ''' The function will be called when particular plugin is being
+ unloaded from memory. Clean up any widgets that you have added
+ to the interface (toolviews etc).
+
+ ATTENTION Be really careful trying to access any window, view
+ or document from the @unload handler: in case of application
+ quit everything is dead already!
+ '''
+ plugin = sys._getframe(1).f_globals['__name__']
+ qDebug('@unload: {}/{}'.format(plugin, func.__name__))
+ def _module_cleaner():
+ qDebug('@unload/cleaner: {}/{}'.format(plugin, func.__name__))
+ if plugin in init.functions:
+ qDebug('@unload/init-cleaner: {}/{}'.format(plugin, func.__name__))
+ del init.functions[plugin]
+
+ func()
+
+ return _registerCallback(plugin, unload, _module_cleaner)
+
+
diff --git a/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py
new file mode 100644
index 0000000000..205d51b171
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/dockwidgetfactory.py
@@ -0,0 +1,13 @@
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from PyKrita.krita import *
+
+class DockWidgetFactory(DockWidgetFactoryBase):
+
+ def __init__(self, _id, _dockPosition, _klass):
+ super().__init__(_id, _dockPosition)
+ self.klass = _klass
+
+ def createDockWidget(self):
+ return self.klass()
diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook.py b/plugins/extensions/pykrita/plugin/krita/excepthook.py
new file mode 100644
index 0000000000..e83d84cbb0
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/excepthook.py
@@ -0,0 +1,85 @@
+"""
+Exception hook
+If some unexpected error occures it can be shown in a nice looking dialog.
+Especially useful is the traceback view.
+
+Things to extend: Clicking on the filename should open an editor.
+Things to consider: Mail exceptions, copy to clipboard or send to bug tracker.
+"""
+import sys
+import cgitb
+import atexit
+
+from PyQt5.QtCore import pyqtSlot, Qt
+from PyQt5.QtWidgets import QApplication, QDialog
+
+from excepthook_ui import Ui_ExceptHookDialog
+
+
+
+def on_error(exc_type, exc_obj, exc_tb):
+ """
+ This is the callback function for sys.excepthook
+ """
+ dlg = ExceptHookDialog(exc_type, exc_obj, exc_tb)
+ dlg.show()
+ dlg.exec_()
+
+
+
+def show_current_error(title=None):
+ """
+ Call this function to show the current error.
+ It can be used inside an except-block.
+ """
+ dlg = ExceptHookDialog(sys.exc_type, sys.exc_value, sys.exc_traceback, title)
+ dlg.show()
+ dlg.exec_()
+
+
+def install():
+ "activates the error handler"
+ sys.excepthook = on_error
+
+
+
+def uninstall():
+ "removes the error handler"
+ sys.excepthook = sys.__excepthook__
+
+atexit.register(uninstall)
+
+
+class ExceptHookDialog(QDialog):
+
+
+ def __init__(self, exc_type, exc_obj, exc_tb, title=None):
+ QDialog.__init__(self)
+ self.ui = Ui_ExceptHookDialog()
+ self.ui.setupUi(self)
+ if title:
+ self.setWindowTitle(self.windowTitle() + ": " + title)
+ msg = "%s: %s" % (exc_type.__name__, exc_obj)
+ self.ui.exceptionLabel.setText(msg)
+ html = cgitb.text((exc_type, exc_obj, exc_tb))
+ self.ui.tracebackBrowser.setText(html)
+ self.resize(self.sizeHint())
+
+ @pyqtSlot()
+ def on_closeButton_clicked(self):
+ self.close()
+
+
+if __name__ == "__main__":
+ # Some tests:
+ app = QApplication(sys.argv)
+ install()
+ print("Triggering error 1")
+ try:
+ fail = 1 / 0
+ except:
+ show_current_error("Using inside except")
+ print("Triggering error 2")
+ fail2 = 1 / 0
+ print("This will never be reached because excepthook")
+ print("complains about fail2")
diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook.ui b/plugins/extensions/pykrita/plugin/krita/excepthook.ui
new file mode 100644
index 0000000000..ec6a90170f
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/excepthook.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExceptHookDialog</class>
+ <widget class="QDialog" name="ExceptHookDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>542</width>
+ <height>290</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Script error</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>10</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>An exception occurred while running the script.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="exceptionLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Exception</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="tracebackBrowser">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>200</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeButton">
+ <property name="text">
+ <string>&amp;Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py
new file mode 100644
index 0000000000..16a80bc65f
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/excepthook_ui.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'excepthook.ui'
+#
+# Created by: PyQt5 UI code generator 5.6
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_ExceptHookDialog(object):
+ def setupUi(self, ExceptHookDialog):
+ ExceptHookDialog.setObjectName("ExceptHookDialog")
+ ExceptHookDialog.resize(542, 290)
+ self.verticalLayout = QtWidgets.QVBoxLayout(ExceptHookDialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.gridLayout = QtWidgets.QGridLayout()
+ self.gridLayout.setSpacing(10)
+ self.gridLayout.setObjectName("gridLayout")
+ self.label = QtWidgets.QLabel(ExceptHookDialog)
+ self.label.setObjectName("label")
+ self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
+ self.exceptionLabel = QtWidgets.QLabel(ExceptHookDialog)
+ font = QtGui.QFont()
+ font.setBold(True)
+ font.setWeight(75)
+ self.exceptionLabel.setFont(font)
+ self.exceptionLabel.setObjectName("exceptionLabel")
+ self.gridLayout.addWidget(self.exceptionLabel, 1, 0, 1, 1)
+ self.verticalLayout.addLayout(self.gridLayout)
+ self.tracebackBrowser = QtWidgets.QTextBrowser(ExceptHookDialog)
+ self.tracebackBrowser.setMinimumSize(QtCore.QSize(0, 200))
+ self.tracebackBrowser.setObjectName("tracebackBrowser")
+ self.verticalLayout.addWidget(self.tracebackBrowser)
+ self.closeButton = QtWidgets.QPushButton(ExceptHookDialog)
+ self.closeButton.setObjectName("closeButton")
+ self.verticalLayout.addWidget(self.closeButton)
+
+ self.retranslateUi(ExceptHookDialog)
+ QtCore.QMetaObject.connectSlotsByName(ExceptHookDialog)
+
+ def retranslateUi(self, ExceptHookDialog):
+ _translate = QtCore.QCoreApplication.translate
+ ExceptHookDialog.setWindowTitle(_translate("ExceptHookDialog", "Script error"))
+ self.label.setText(_translate("ExceptHookDialog", "An exception occurred while running the script."))
+ self.exceptionLabel.setText(_translate("ExceptHookDialog", "Exception"))
+ self.closeButton.setText(_translate("ExceptHookDialog", "&Close"))
+
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/THANKS b/plugins/extensions/pykrita/plugin/krita/sceditor/THANKS
new file mode 100644
index 0000000000..1718c3b44f
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/THANKS
@@ -0,0 +1,2 @@
+rope.zip and indenter.py by Ali Gholami Rudi, Rope, GPL
+sceditor/highlighter.py by David Boddie, included as demo in PyQt, GPL2 or later
\ No newline at end of file
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py
new file mode 100644
index 0000000000..48c0e362a6
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/__init__.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+
+
+editor_main_window = None
+
+
+def launch(parent=None):
+ global editor_main_window
+ if not editor_main_window:
+ from sceditor.mainwindow import EditorMainWindow
+ editor_main_window = EditorMainWindow(parent)
+ editor_main_window.resize(640,480)
+ editor_main_window.show()
+
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/arrow-down.png b/plugins/extensions/pykrita/plugin/krita/sceditor/arrow-down.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/arrow-right.png b/plugins/extensions/pykrita/plugin/krita/sceditor/arrow-right.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py
new file mode 100644
index 0000000000..e524bcbbd6
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/assist.py
@@ -0,0 +1,155 @@
+from PyQt5.QtCore import QTimer, Qt
+from PyQt5.QtWidgets import (qApp, QListWidget, QListWidgetItem, QTextBrowser,
+ QVBoxLayout, QWidget)
+
+
+class PopupWidget(QWidget):
+
+
+ def __init__(self, textedit):
+ flags = Qt.ToolTip
+ flags = Qt.Window | Qt.FramelessWindowHint | \
+ Qt.CustomizeWindowHint | Qt.X11BypassWindowManagerHint
+ QWidget.__init__(self, None, flags)
+ self.textedit = textedit
+ self.vlayout = QVBoxLayout(self)
+ self.vlayout.setContentsMargins(0, 0, 0, 0)
+ self.init_popup()
+ self.show()
+ self.hide()
+ self.active = False
+
+
+ def show(self, timeout=0, above=False):
+ self.cursor_start_col = self.textedit.textCursor().columnNumber()
+ desktop = qApp.desktop()
+ screen = desktop.screen(desktop.screenNumber(self))
+ screen_width = screen.width()
+ screen_height = screen.height()
+ win_width = self.width()
+ win_height = self.height()
+ cursorRect = self.textedit.cursorRect()
+ if above:
+ pos = self.textedit.mapToGlobal(cursorRect.topLeft())
+ pos.setY(pos.y() - win_height)
+ else:
+ pos = self.textedit.mapToGlobal(cursorRect.bottomLeft())
+ if pos.y() < 0:
+ pos = self.textedit.mapToGlobal(cursorRect.bottomLeft())
+ if pos.y() + win_height > screen_height:
+ pos = self.textedit.mapToGlobal(cursorRect.topLeft())
+ pos.setY(pos.y() - win_height)
+ if pos.x() + win_width > screen_width:
+ pos.setX(screen_width - win_width)
+
+ self.move(pos)
+ QWidget.show(self)
+ self.active = True
+ if timeout:
+ QTimer.singleShot(timeout * 1000, self.hide)
+
+
+ def hide(self):
+ self.active = False
+ QWidget.hide(self)
+
+
+
+
+
+
+class CallTip(PopupWidget):
+
+
+ def init_popup(self):
+ self.browser = QTextBrowser(self)
+ self.layout().addWidget(self.browser)
+
+
+
+
+class AutoCompleteItem(QListWidgetItem):
+
+ def __init__(self, item):
+ QListWidgetItem.__init__(self)
+ value = item.name
+ self.setText(value)
+ self.value = value
+ self.kind = item.kind
+
+
+
+class AutoComplete(PopupWidget):
+
+
+ def init_popup(self):
+ self.list = QListWidget(self)
+ self.list.itemClicked.connect(self.insertItem)
+ self.layout().addWidget(self.list)
+ self.items = []
+
+
+ def insertItem(self, item):
+ self.insert()
+
+
+ def insert(self):
+ completition = self.items[self.list.currentRow()].value
+ cursor = self.textedit.textCursor()
+ col = cursor.columnNumber()
+ line = unicode(cursor.block().text())
+ i = self.cursor_start_col
+ while i > 0:
+ #print(`line[i:col]`)
+ if completition.startswith(line[i:col]):
+ #print("break")
+ break
+ i -= 1
+ #print(col,i)
+ cursor.insertText(completition[col-i:])
+ self.hide()
+
+
+ def setItems(self, proposals):
+ proposals = sorted(proposals, cmp=lambda p1,p2:cmp(p1.name,p2.name))
+ del self.items[:]
+ self.list.clear()
+ for entry in proposals:
+ i = AutoCompleteItem(entry)
+ self.list.addItem(i)
+ self.items.append(i)
+
+
+ def keyPressEvent(self, event):
+ self.list.keyPressEvent(event)
+ key = event.key()
+ text = event.text()
+ if key in [Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]:
+ text = ""
+ cursor = self.textedit.textCursor()
+ line = unicode(cursor.block().text())
+ col = cursor.columnNumber()
+ prefix = line[self.cursor_start_col:col] + unicode(text)
+
+ found = False
+ for row, item in enumerate(self.items):
+ if item.value.startswith(prefix):
+ current = self.items[self.list.currentRow()].value
+ if not current.startswith(prefix):
+ self.list.setCurrentRow(row)
+ found = True
+ break
+ if not found:
+ self.hide()
+ return
+
+ if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown]:
+ return True
+ elif key in [Qt.Key_Tab, Qt.Key_Right, Qt.Key_Enter, Qt.Key_Return]:
+ self.insert()
+ return True
+ elif not text:
+ self.hide()
+
+
+
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/console.py b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py
new file mode 100644
index 0000000000..7b4010aad2
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/console.py
@@ -0,0 +1,524 @@
+from __future__ import print_function
+
+import sys
+import traceback
+import re
+
+from PyQt5.QtCore import QObject, Qt
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import qApp, QApplication, QPlainTextEdit
+
+
+from highlighter import PythonHighlighter, QtQmlHighlighter
+
+
+
+
+from PyQt5.QtQml import (
+ QScriptEngine, QScriptValue, QScriptValueIterator)
+
+
+class OutputWidget(QPlainTextEdit):
+
+
+ def __init__(self, parent=None, readonly=True, max_rows=1000, echo=True):
+ QPlainTextEdit.__init__(self, parent)
+ self.echo = echo
+ self.setReadOnly(readonly)
+ self.document().setMaximumBlockCount(max_rows)
+ self.attach()
+
+
+ def attach(self):
+ sys.stdout = sys.stderr = self
+
+
+ def __del__(self):
+ self.detach()
+
+
+ def detach(self):
+ sys.stdout = sys.__stdout__
+ sys.stderr = sys.__stderr__
+
+
+ def write(self, s):
+ if self.echo:
+ sys.__stdout__.write(s)
+ doc = self.document()
+ cursor = QTextCursor(doc)
+ cursor.clearSelection()
+ cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
+ cursor.insertText(s)
+ cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor)
+ cursor.clearSelection()
+ self.ensureCursorVisible()
+ qApp.processEvents()
+
+
+ def writelines(self, lines):
+ self.write("\n".join(lines))
+
+
+
+class ConsoleWidget(OutputWidget):
+
+
+ def __init__(self, parent=None, ps1="?", ps2=">"):
+ OutputWidget.__init__(self, parent, readonly=False)
+ self.setTabChangesFocus(False)
+ self.ps1 = ps1
+ self.ps2 = ps2
+ self.history_index = 0
+ self.history = [""]
+ self.tab_state = -1
+ print(self.ps1, end='')
+
+
+ def focusInEvent(self, event):
+ self.attach()
+ OutputWidget.focusInEvent(self, event)
+
+
+ def mousePressEvent(self, event):
+ self.setFocus()
+
+
+ def push(self, line):
+ return True
+
+
+ def keyPressEvent(self, event):
+ def remove_line():
+ cursor = self.textCursor()
+ cursor.select(QTextCursor.BlockUnderCursor)
+ cursor.removeSelectedText()
+ key = event.key()
+ modifiers = event.modifiers()
+ l = len(self.ps1)
+ line = unicode(self.document().end().previous().text())
+ ps1orps2, line = line[:l-1], line[l:]
+
+
+ if not key in [Qt.Key_Tab, Qt.Key_Backtab] and \
+ len(event.text()):
+ self.tab_state = -1
+ if key == Qt.Key_Up:
+ if self.history_index + 1 < len(self.history):
+ self.history_index += 1
+ remove_line()
+ print()
+ print(ps1orps2, self.history[self.history_index], end='')
+ elif key == Qt.Key_Down:
+ if self.history_index > 0:
+ self.history_index -= 1
+ remove_line()
+ print()
+ print(ps1orps2, self.history[self.history_index], end='')
+ elif key == Qt.Key_Tab:
+ if modifiers & Qt.ControlModifier:
+ print(" " * 4, end='')
+ else:
+ self.tab_state += 1
+ remove_line()
+ print()
+ print(ps1orps2, end='')
+ print(self.completer.complete(line, self.tab_state) or line, end='')
+ elif key == Qt.Key_Backtab:
+ if self.tab_state >= 0:
+ self.tab_state -= 1
+ remove_line()
+ print()
+ print(ps1orps2, end='')
+ print(self.completer.complete(line, self.tab_state) or line, end='')
+ elif key in [Qt.Key_Backspace, Qt.Key_Left]:
+ if self.textCursor().columnNumber() > len(ps1orps2) + 1:
+ return OutputWidget.keyPressEvent(self, event)
+ elif key == Qt.Key_Return:
+ self.moveCursor(QTextCursor.EndOfLine, QTextCursor.MoveAnchor)
+ print()
+ if self.push(line):
+ print(self.ps2, end='')
+ else:
+ print(self.ps1, end='')
+ if line and line != self.history[self.history_index]:
+ self.history.insert(1, line)
+ self.history_index = 0
+ else:
+ return OutputWidget.keyPressEvent(self, event)
+
+
+
+class PythonInterpreter(object):
+
+
+ def __init__(self, name="<pyqtshell>", locals=None):
+ self.name = name
+ self.locals = locals or {}
+ self.locals["__name__"] = self.name
+ self.lines = []
+
+
+ def run(self, source, locals=None):
+ if locals == None:
+ locals = self.locals
+ code = compile(source, self.name, "exec")
+ try:
+ exec code in locals
+ except:
+ self.showtraceback()
+ try:
+ Scripter.activeWindow.redraw = True
+ Scripter.activeWindow.update()
+ except: pass
+
+
+ def push(self, line):
+ if self.lines:
+ if line:
+ self.lines.append(line)
+ return 1 # want more!
+ else:
+ line = "\n".join(self.lines) + "\n"
+ else:
+ if not line:
+ return 0
+ try:
+ code = compile(line, self.name, "single")
+ self.lines = []
+ except SyntaxError as why:
+ if why[0] == "unexpected EOF while parsing":
+ self.lines.append(line)
+ return 1 # want more!
+ else:
+ self.showtraceback()
+ except:
+ self.showtraceback()
+ else:
+ try:
+ exec code in self.locals
+ except:
+ self.showtraceback()
+ try:
+ Scripter.activeWindow.redraw = True
+ Scripter.activeWindow.update()
+ except: pass
+ return 0
+
+
+ def showtraceback(self):
+ self.lines = []
+ if sys.exc_type == SyntaxError: # and len(sys.exc_value) == 2:
+ print(" File \"%s\", line %d" % (self.name, sys.exc_value[1][1]))
+ print(" " * (sys.exc_value[1][2] + 2) + "^")
+ print(str(sys.exc_type) + ":", sys.exc_value[0])
+ else:
+ traceback.print_tb(sys.exc_traceback, None)
+ print(sys.exc_type.__name__ + ":", sys.exc_value)
+
+
+
+
+class PythonCompleter(object):
+
+
+ def __init__(self, namespace):
+ self.namespace = namespace
+
+
+ def complete(self, text, state):
+ if state == 0:
+ if "." in text:
+ self.matches = self.attr_matches(text)
+ else:
+ self.matches = self.global_matches(text)
+ try:
+ return self.matches[state]
+ except IndexError:
+ return None
+
+
+ def global_matches(self, text):
+ import keyword, __builtin__
+ matches = []
+ n = len(text)
+ for list in [keyword.kwlist,
+ __builtin__.__dict__,
+ self.namespace]:
+ for word in list:
+ if word[:n] == text and word != "__builtins__":
+ matches.append(word)
+ return matches
+
+
+ def attr_matches(self, text):
+ def get_class_members(cls):
+ ret = dir(cls)
+ if hasattr(cls,'__bases__'):
+ for base in cls.__bases__:
+ ret = ret + get_class_members(base)
+ return ret
+ import re
+ m = re.match(r"(\w+(\.\w+)*)\.(\w*)", text)
+ if not m:
+ return
+ expr, attr = m.group(1, 3)
+ object = eval(expr, self.namespace)
+ words = dir(object)
+ if hasattr(object,'__class__'):
+ words.append('__class__')
+ words = words + get_class_members(object.__class__)
+ matches = []
+ n = len(attr)
+ for word in words:
+ if word[:n] == attr and word != "__builtins__":
+ matches.append("%s.%s" % (expr, word))
+ return matches
+
+
+
+
+
+
+
+class PythonConsole(ConsoleWidget):
+
+
+ def __init__(self, parent=None, namespace=None):
+ ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ")
+ self.highlighter = PythonHighlighter(self)
+ self.inter = PythonInterpreter(locals=namespace)
+ self.namespace = self.inter.locals
+ self.completer = PythonCompleter(self.namespace)
+ #print("Python", sys.version)
+ #print("Autocomplete with (Shift+)Tab, insert spaces with Ctrl+Tab")
+ self.push("pass")
+
+
+ def push(self, line):
+ return self.inter.push(line)
+
+
+ def clear(self):
+ doc = self.document()
+ doc.setPlainText(self.ps1)
+
+
+
+
+class QtQmlInterpreter(object):
+
+
+ def __init__(self, locals):
+ self.locals = locals
+ self.engine = self.newEngine()
+ self.code = ""
+ self.state = 0
+
+
+ def newEngine(self):
+ engine = QScriptEngine()
+ ns = engine.globalObject()
+ for name, value in self.locals.items():
+ if isinstance(value, QObject):
+ value = engine.newQObject(value)
+ elif callable(value):
+ value = engine.newFunction(value)
+ ns.setProperty(name, value)
+ return engine
+
+
+ def execute(self, code):
+ self.execute_code(code, self.engine)
+
+
+ def execute_code(self, code, engine=None):
+ engine = engine or self.newEngine()
+ result = engine.evaluate(code)
+ try:
+ Scripter.activeWindow.redraw = True
+ Scripter.activeWindow.update()
+ except: pass
+ if engine.hasUncaughtException():
+ bt = engine.uncaughtExceptionBacktrace()
+ print("Traceback:")
+ print("\n".join([" %s" % l for l in list(bt)]))
+ print(engine.uncaughtException().toString())
+ else:
+ if not result.isUndefined():
+ print(result.toString())
+
+
+ def push(self, line):
+ if not line.strip():
+ return self.state
+ self.code = self.code + line + "\n"
+ if self.engine.canEvaluate(self.code):
+ self.execute(self.code)
+ self.code = ""
+ self.state = 0
+ else:
+ self.state = 1
+ return self.state
+
+
+js_words = [
+ 'break',
+ 'for',
+ 'throw',
+ 'case',
+ 'function',
+ 'try',
+ 'catch',
+ 'if',
+ 'typeof',
+ 'continue',
+ 'in',
+ 'var',
+ 'default',
+ 'instanceof',
+ 'void',
+ 'delete',
+ 'new',
+ 'undefined',
+ 'do',
+ 'return',
+ 'while',
+ 'else',
+ 'switch',
+ 'with',
+ 'finally',
+ 'this',
+ 'NaN',
+ 'Infinity',
+ 'undefined',
+ 'print',
+ 'parseInt',
+ 'parseFloat',
+ 'isNaN',
+ 'isFinite',
+ 'decodeURI',
+ 'decodeURIComponent',
+ 'encodeURI',
+ 'encodeURIComponent',
+ 'escape',
+ 'unescape',
+ 'version',
+ 'gc',
+ 'Object',
+ 'Function',
+ 'Number',
+ 'Boolean',
+ 'String',
+ 'Date',
+ 'Array',
+ 'RegExp',
+ 'Error',
+ 'EvalError',
+ 'RangeError',
+ 'ReferenceError',
+ 'SyntaxError',
+ 'TypeError',
+ 'URIError',
+ 'eval',
+ 'Math',
+ 'Enumeration',
+ 'Variant',
+ 'QObject',
+ 'QMetaObject']
+
+
+
+class QtQmlCompleter(object):
+
+
+ def __init__(self, engine):
+ self.engine = engine
+
+
+ def complete(self, text, state):
+ if state == 0:
+ if "." in text:
+ self.matches = self.attr_matches(text)
+ else:
+ self.matches = self.global_matches(text)
+ try:
+ return self.matches[state]
+ except IndexError:
+ return None
+
+
+
+ def attr_matches(self, text):
+ return []
+
+
+
+ def iter_obj(self, obj):
+ it = QScriptValueIterator(self.engine.globalObject())
+ while it.hasNext():
+ yield str(it.name())
+ it.next()
+
+
+ def global_matches(self, text):
+ words = list(self.iter_obj(self.engine.globalObject()))
+ words.extend(js_words)
+ l = []
+ n = len(text)
+ for w in words:
+ if w[:n] == text:
+ l.append(w)
+ return l
+
+
+
+
+
+class QtQmlConsole(ConsoleWidget):
+
+
+ def __init__(self, parent=None, namespace=None):
+ ConsoleWidget.__init__(self, parent, ps1=">>> ", ps2="... ")
+ self.highlighter = QtQmlHighlighter(self)
+ namespace = namespace or {}
+ def console_print(context, engine):
+ for i in range(context.argumentCount()):
+ print(context.argument(i).toString(), end='')
+ print()
+ return QScriptValue()
+ def dir_context(context, engine):
+ if context.argumentCount() == 0:
+ obj = context.thisObject()
+ else:
+ obj = context.argument(0)
+ l = []
+ it = QScriptValueIterator(obj)
+ while it.hasNext():
+ it.next()
+ l.append(str(it.name()))
+ return QScriptValue(engine, repr(l))
+ namespace["print"] = console_print
+ namespace["dir"] = dir_context
+ namespace["Application"] = qApp
+ try:
+ namespace["Scripter"] = Scripter.qt
+ except: pass
+ self.inter = QtQmlInterpreter(namespace)
+ self.completer = QtQmlCompleter(self.inter.engine)
+
+
+
+ def push(self, line):
+ return self.inter.push(line)
+
+
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ o = QtQmlConsole()
+ #o = PythonConsole()
+ o.resize(640,480)
+ o.attach()
+ o.show()
+ app.exec_()
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py
new file mode 100644
index 0000000000..2134fd08e3
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget.py
@@ -0,0 +1,428 @@
+# Ported from KoDockWidgetTitleBar.cpp which is part of KOffice
+# Copyright (c) 2007 Marijn Kruisselbrink <m.kruisselbrink@student.tue.nl>
+# Copyright (C) 2007 Thomas Zander <zander@kde.org>
+# The code is distributed under GPL 2 or any later version
+import os
+
+from PyQt5.QtCore import QPoint, QSize, Qt, QRect, QTimer
+from PyQt5.QtGui import (QIcon, QPainter)
+from PyQt5.QtWidgets import (QAbstractButton, QApplication, QComboBox,
+ QDockWidget, QHBoxLayout, QLayout, QMainWindow,
+ QPushButton, QStyle, QStyleOptionDockWidget,
+ QStyleOptionToolButton, QStylePainter, QWidget)
+
+
+import dockwidget_icons
+
+
+def hasFeature(dockwidget, feature):
+ return dockwidget.features() & feature == feature
+
+
+
+class DockWidgetTitleBarButton(QAbstractButton):
+
+
+ def __init__(self, titlebar):
+ QAbstractButton.__init__(self, titlebar)
+ self.setFocusPolicy(Qt.NoFocus)
+
+
+ def sizeHint(self):
+ self.ensurePolished()
+ margin = self.style().pixelMetric(QStyle.PM_DockWidgetTitleBarButtonMargin, None, self)
+ if self.icon().isNull():
+ return QSize(margin, margin)
+ iconSize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self)
+ pm = self.icon().pixmap(iconSize)
+ return QSize(pm.width() + margin, pm.height() + margin)
+
+
+ def enterEvent(self, event):
+ if self.isEnabled():
+ self.update()
+ QAbstractButton.enterEvent(self, event)
+
+
+ def leaveEvent(self, event):
+ if self.isEnabled():
+ self.update()
+ QAbstractButton.leaveEvent(self, event)
+
+
+
+ def paintEvent(self, event):
+ p = QPainter(self)
+ r = self.rect()
+ opt = QStyleOptionToolButton()
+ opt.init(self)
+ opt.state |= QStyle.State_AutoRaise
+ if self.isEnabled() and self.underMouse() and \
+ not self.isChecked() and not self.isDown():
+ opt.state |= QStyle.State_Raised
+ if self.isChecked():
+ opt.state |= QStyle.State_On
+ if self.isDown():
+ opt.state |= QStyle.State_Sunken
+ self.style().drawPrimitive(
+ QStyle.PE_PanelButtonTool, opt, p, self)
+ opt.icon = self.icon()
+ opt.subControls = QStyle.SubControls()
+ opt.activeSubControls = QStyle.SubControls()
+ opt.features = QStyleOptionToolButton.None
+ opt.arrowType = Qt.NoArrow
+ size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self)
+ opt.iconSize = QSize(size, size)
+ self.style().drawComplexControl(QStyle.CC_ToolButton, opt, p, self)
+
+
+
+
+class DockWidgetTitleBar(QWidget):
+ # XXX: support QDockWidget.DockWidgetVerticalTitleBar feature
+
+
+ def __init__(self, dockWidget):
+ QWidget.__init__(self, dockWidget)
+ self.openIcon = QIcon(":arrow-down.png")
+ self.closeIcon = QIcon(":arrow-right.png")
+ self.pinIcon = QIcon(":pin.png")
+ q = dockWidget
+ self.floatButton = DockWidgetTitleBarButton(self)
+ self.floatButton.setIcon(q.style().standardIcon(
+ QStyle.SP_TitleBarNormalButton, None, q))
+ self.floatButton.clicked.connect(self.toggleFloating)
+ self.floatButton.setVisible(True)
+ self.closeButton = DockWidgetTitleBarButton(self)
+ self.closeButton.setIcon(q.style().standardIcon(
+ QStyle.SP_TitleBarCloseButton, None, q))
+ self.closeButton.clicked.connect(dockWidget.close)
+ self.closeButton.setVisible(True)
+ self.collapseButton = DockWidgetTitleBarButton(self)
+ self.collapseButton.setIcon(self.openIcon)
+ self.collapseButton.clicked.connect(self.toggleCollapsed)
+ self.collapseButton.setVisible(True)
+ self.pinButton = DockWidgetTitleBarButton(self)
+ self.pinButton.setIcon(self.pinIcon)
+ self.pinButton.setCheckable(True)
+ self.pinButton.setChecked(True)
+ self.pinButton.clicked.connect(self.togglePinned)
+ self.pinButton.setVisible(True)
+ dockWidget.featuresChanged.connect(self.featuresChanged)
+ self.featuresChanged(0)
+
+
+ def minimumSizeHint(self):
+ return self.sizeHint()
+
+
+ def sizeHint(self):
+ q = self.parentWidget()
+ mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q)
+ fw = q.style().pixelMetric(QStyle.PM_DockWidgetFrameWidth, None, q)
+ closeSize = QSize(0, 0)
+ if self.closeButton:
+ closeSize = self.closeButton.sizeHint()
+ floatSize = QSize(0, 0)
+ if self.floatButton:
+ floatSize = self.floatButton.sizeHint()
+ hideSize = QSize(0, 0)
+ if self.collapseButton:
+ hideSize = self.collapseButton.sizeHint()
+ pinSize = QSize(0, 0)
+ if self.pinButton:
+ pinSize = self.pinButton.sizeHint()
+ buttonHeight = max(max(closeSize.height(), floatSize.height()),
+ hideSize.height(), pinSize.height()) + 2
+ buttonWidth = closeSize.width() + floatSize.width() + hideSize.width() + pinSize.width()
+ titleFontMetrics = q.fontMetrics()
+ fontHeight = titleFontMetrics.lineSpacing() + 2 * mw
+ height = max(buttonHeight, fontHeight)
+ width = buttonWidth + height + 4 * mw + 2 * fw
+ if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar):
+ width, height = height, width
+ return QSize(width, height)
+
+
+ def paintEvent(self, event):
+ p = QStylePainter(self)
+ q = self.parentWidget()
+ if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar):
+ fw = 1 or q.isFloating() and q.style().pixelMetric(
+ QStyle.PM_DockWidgetFrameWidth, None, q) or 0
+ mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q)
+ titleOpt = QStyleOptionDockWidget()
+ titleOpt.initFrom(q)
+ titleOpt.verticalTitleBar = True
+ titleOpt.rect = QRect(
+ QPoint(fw, fw + mw + \
+ self.collapseButton.size().height() + self.pinButton.size().height()),
+ QSize(
+ self.geometry().width() - (fw * 2),
+ self.geometry().height() - (fw * 2) - \
+ mw - self.collapseButton.size().height() - self.pinButton.size().height()))
+ titleOpt.title = q.windowTitle()
+ titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
+ titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
+ p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt)
+ else:
+ fw = q.isFloating() and q.style().pixelMetric(
+ QStyle.PM_DockWidgetFrameWidth, None, q) or 0
+ mw = q.style().pixelMetric(QStyle.PM_DockWidgetTitleMargin, None, q)
+ titleOpt = QStyleOptionDockWidget()
+ titleOpt.initFrom(q)
+ titleOpt.rect = QRect(
+ QPoint(fw + mw + \
+ self.collapseButton.size().width() + self.pinButton.size().width(), fw),
+ QSize(
+ self.geometry().width() - (fw * 2) - \
+ mw - self.collapseButton.size().width() - self.pinButton.size().width(),
+ self.geometry().height() - (fw * 2)))
+ titleOpt.title = q.windowTitle()
+ titleOpt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
+ titleOpt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
+ p.drawControl(QStyle.CE_DockWidgetTitle, titleOpt)
+
+
+ def resizeEvent(self, event):
+ q = self.parentWidget()
+ if hasFeature(q, QDockWidget.DockWidgetVerticalTitleBar):
+ fh = q.isFloating() and q.style().pixelMetric(
+ QStyle.PM_DockWidgetFrameWidth, None, q) or 0
+ opt = QStyleOptionDockWidget()
+ opt.initFrom(q)
+ opt.verticalTitleBar = True
+ opt.rect = QRect(
+ QPoint(fh, 40), #self.geometry().height() - (fh * 3)),
+ QSize(
+ self.geometry().width() - (fh * 2),
+ fh * 2))
+ opt.title = q.windowTitle()
+ opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
+ opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
+ floatRect = q.style().subElementRect(
+ QStyle.SE_DockWidgetFloatButton, opt, q)
+ if not floatRect.isNull():
+ self.floatButton.setGeometry(floatRect)
+ closeRect = q.style().subElementRect(
+ QStyle.SE_DockWidgetCloseButton, opt, q)
+ if not closeRect.isNull():
+ self.closeButton.setGeometry(closeRect)
+ top = fh
+ if not floatRect.isNull():
+ top = floatRect.x()
+ elif not closeRect.isNull():
+ top = closeRect.x()
+ size = self.collapseButton.size()
+ if not closeRect.isNull():
+ size = self.closeButton.size()
+ elif not floatRect.isNull():
+ size = self.floatButton.size()
+ collapseRect = QRect(QPoint(top, fh), size)
+ self.collapseButton.setGeometry(collapseRect)
+ pinRect = QRect(QPoint(top, fh+collapseRect.height()+1), size)
+ self.pinButton.setGeometry(pinRect)
+ else:
+ fw = q.isFloating() and q.style().pixelMetric(
+ QStyle.PM_DockWidgetFrameWidth, None, q) or 0
+ opt = QStyleOptionDockWidget()
+ opt.initFrom(q)
+ opt.rect = QRect(
+ QPoint(fw, fw),
+ QSize(
+ self.geometry().width() - (fw * 2),
+ self.geometry().height() - (fw * 2)))
+ opt.title = q.windowTitle()
+ opt.closable = hasFeature(q, QDockWidget.DockWidgetClosable)
+ opt.floatable = hasFeature(q, QDockWidget.DockWidgetFloatable)
+ floatRect = q.style().subElementRect(
+ QStyle.SE_DockWidgetFloatButton, opt, q)
+ if not floatRect.isNull():
+ self.floatButton.setGeometry(floatRect)
+ closeRect = q.style().subElementRect(
+ QStyle.SE_DockWidgetCloseButton, opt, q)
+ if not closeRect.isNull():
+ self.closeButton.setGeometry(closeRect)
+ top = fw
+ if not floatRect.isNull():
+ top = floatRect.y()
+ elif not closeRect.isNull():
+ top = closeRect.y()
+ size = self.collapseButton.size()
+ if not closeRect.isNull():
+ size = self.closeButton.size()
+ elif not floatRect.isNull():
+ size = self.floatButton.size()
+ collapseRect = QRect(QPoint(fw, top), size)
+ self.collapseButton.setGeometry(collapseRect)
+ pinRect = QRect(QPoint(fw + collapseRect.width() + 1, top), size)
+ self.pinButton.setGeometry(pinRect)
+
+
+ def setCollapsed(self, collapsed):
+ q = self.parentWidget()
+ if q and q.widget() and q.widget().isHidden() != collapsed:
+ self.toggleCollapsed()
+
+
+ def toggleFloating(self):
+ q = self.parentWidget()
+ q.setFloating(not q.isFloating())
+
+
+ def toggleCollapsed(self):
+ q = self.parentWidget()
+ if not q:
+ return
+ q.toggleCollapsed()
+ self.setCollapsedIcon(q.isCollapsed())
+
+
+ def setCollapsedIcon(self, flag):
+ self.collapseButton.setIcon(flag and self.openIcon or self.closeIcon)
+
+
+ def togglePinned(self, checked):
+ self.parent().setPinned(checked)
+
+
+ def featuresChanged(self, features):
+ q = self.parentWidget()
+ self.closeButton.setVisible(hasFeature(q, QDockWidget.DockWidgetClosable))
+ self.floatButton.setVisible(hasFeature(q, QDockWidget.DockWidgetFloatable))
+ # self.resizeEvent(None)
+
+
+
+class DockMainWidgetWrapper(QWidget):
+
+
+ def __init__(self, dockwidget):
+ QWidget.__init__(self, dockwidget)
+ self.widget = None
+ self.hlayout = QHBoxLayout(self)
+ self.hlayout.setSpacing(0)
+ self.hlayout.setContentsMargins(0, 0, 0, 0)
+ self.setLayout(self.hlayout)
+
+
+ def setWidget(self, widget):
+ self.widget = widget
+ self.widget_height = widget.height
+ self.layout().addWidget(widget)
+
+
+ def isCollapsed(self):
+ return self.widget.isVisible()
+
+
+ def setCollapsed(self, flag):
+ if not flag:
+ self.old_size = self.size()
+ self.layout().removeWidget(self.widget)
+ self.widget.hide()
+ if hasFeature(self.parent(), QDockWidget.DockWidgetVerticalTitleBar):
+ self.parent().setMaximumWidth(self.parent().width() - self.width())
+ else:
+ self.parent().setMaximumHeight(self.parent().height() - self.height())
+ else:
+ self.setFixedSize(self.old_size)
+ self.parent().setMinimumSize(QSize(1, 1))
+ self.parent().setMaximumSize(QSize(32768, 32768))
+ self.widget.show()
+ self.layout().addWidget(self.widget)
+ self.setMinimumSize(QSize(1, 1))
+ self.setMaximumSize(QSize(32768, 32768))
+
+
+
+class DockWidget(QDockWidget):
+
+
+ def __init__(self, *args):
+ QDockWidget.__init__(self, *args)
+ self.titleBar = DockWidgetTitleBar(self)
+ self.setTitleBarWidget(self.titleBar)
+ self.mainWidget = None
+ self.entered = False
+ self.pinned = True
+ self.shot = False
+
+
+ def enterEvent(self, event):
+ self.entered = True
+ if not self.shot and not self.isPinned() and not self.isFloating():
+ self.shot = True
+ QTimer.singleShot(500, self.autoshow)
+ return QDockWidget.enterEvent(self, event)
+
+
+ def leaveEvent(self, event):
+ self.entered = False
+ if not self.shot and not self.isPinned() and not self.isFloating():
+ self.shot = True
+ QTimer.singleShot(1000, self.autohide)
+ return QDockWidget.leaveEvent(self, event)
+
+
+ def autohide(self):
+ self.shot = False
+ if not self.entered:
+ self.setCollapsed(False)
+
+
+ def autoshow(self):
+ self.shot = False
+ if self.entered:
+ self.setCollapsed(True)
+
+
+ def isPinned(self):
+ return self.pinned
+
+
+ def setPinned(self, flag):
+ self.pinned = flag
+
+
+ def setWidget(self, widget):
+ self.mainWidget = DockMainWidgetWrapper(self)
+ self.mainWidget.setWidget(widget)
+ QDockWidget.setWidget(self, self.mainWidget)
+
+
+ def setCollapsed(self, flag):
+ self.mainWidget.setCollapsed(flag)
+ self.titleBarWidget().setCollapsedIcon(flag)
+
+
+ def isCollapsed(self):
+ return self.mainWidget.isCollapsed()
+
+
+ def toggleCollapsed(self):
+ self.setCollapsed(not self.isCollapsed())
+
+
+
+if __name__ == "__main__":
+ import sys
+ from PyQt5.QtGui import QTextEdit
+ app = QApplication(sys.argv)
+ app.setStyle("qtcurve")
+ win = QMainWindow()
+ dock1 = DockWidget("1st dockwidget", win)
+ dock1.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar)
+ combo = QComboBox(dock1)
+ dock1.setWidget(combo)
+ win.addDockWidget(Qt.LeftDockWidgetArea, dock1)
+ dock2 = DockWidget("2nd dockwidget")
+ dock2.setFeatures(dock1.features() | QDockWidget.DockWidgetVerticalTitleBar)
+ button = QPushButton("Hello, world!", dock2)
+ dock2.setWidget(button)
+ win.addDockWidget(Qt.RightDockWidgetArea, dock2)
+ edit = QTextEdit(win)
+ win.setCentralWidget(edit)
+ win.resize(640, 480)
+ win.show()
+ app.exec_()
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py
new file mode 100644
index 0000000000..ce9861f796
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+# Resource object code
+#
+# Created: Mi Aug 20 05:23:34 2008
+# by: The Resource Compiler for PyQt (Qt v4.3.4)
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore
+
+qt_resource_data = "\
+\x00\x00\x02\x0d\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\
+\x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8a\x49\x44\
+\x41\x54\x78\xda\xdd\x93\x31\x4b\x5c\x41\x14\x85\xcf\xbc\x37\xc3\
+\x6e\x50\x57\xc5\x64\xc5\x3f\x91\xca\x56\x48\x13\x42\x04\x8d\x8b\
+\x55\xfe\x80\xe8\x3f\x90\x20\x68\x61\x93\xc2\xc2\x5a\xd1\x36\x65\
+\x92\x26\x16\x46\xc5\x22\xe5\x6a\x23\x08\x8b\x98\x14\xba\xb3\xef\
+\xe5\xed\x2a\x6f\x67\x57\x9d\xf7\xf6\x38\x3c\xd0\xca\x28\x92\x42\
+\xf0\xbb\x5c\xb8\x30\x70\x38\x9c\x3b\x57\x90\xc4\xff\xe0\xb9\x7e\
+\x5a\x01\x09\x87\x70\x60\x02\x79\x3c\x86\xaf\xb8\xa0\x23\x13\xc0\
+\x18\x16\x47\x87\x47\x3f\x59\x61\x61\x5c\xc5\x7e\x0c\xe3\xbb\x49\
+\x1a\xd4\x44\x00\x14\x00\x74\x89\xcc\x6f\x91\x45\xf4\x5e\xf6\xa1\
+\x32\x52\x59\x01\x30\x95\x09\xc0\x60\x49\xff\xd5\xd3\xa5\x52\xa9\
+\x5f\x9f\x6b\x51\x35\x55\x68\xab\x11\x7a\x21\x6a\xac\x01\xaf\x00\
+\xbc\x64\xe6\xb7\xa1\x23\xe2\xb7\x67\x70\x95\xce\xdf\x66\xc0\x9f\
+\xac\x97\x0f\xcb\x6f\xb7\x76\xb6\xac\x92\x0a\xca\x53\x90\x42\x42\
+\x75\x14\x70\x05\xa0\xe5\xba\xee\x3a\x00\x06\x6a\xc5\x34\x08\x83\
+\x12\x67\x59\xbd\xc9\x20\x83\x3f\x58\x16\x1f\xc4\x74\x77\x6f\xf7\
+\x4a\x61\xa8\xe0\xab\x54\xc1\x4f\x7d\x78\x4d\x0f\x1d\x57\x68\x3b\
+\x23\x76\x30\x0d\x8e\xc3\x39\x2e\xd8\xcd\x3b\xb7\xc0\x6f\x5c\xdf\
+\xd8\xdd\x58\x8d\xeb\x71\x2a\x13\x09\xaf\xed\x41\x9e\x4b\x20\x02\
+\x7a\xa2\x42\xd2\xfc\x63\xbe\xa7\x0b\x97\x9f\xef\x5d\xa3\x7d\x6d\
+\x67\xb6\x7f\x6d\xef\xb1\xc5\x54\x5d\x28\xb0\x4e\xe4\xce\x72\x49\
+\x27\xe2\x51\x9b\xcd\xc9\x07\xff\x01\xe7\xc9\xf8\x24\x7e\xb7\x77\
+\xb0\xdf\x90\x2d\x49\x46\xa4\x4a\x72\xc6\x34\xe2\x37\x74\x6f\x0f\
+\x0a\xdc\x84\x1a\x06\xc1\xfb\x13\x7d\x6a\x73\x2a\x9f\x34\x1b\xad\
+\x71\x2e\x53\xe3\x0e\xee\xbd\x05\xf1\x51\x4c\xe2\x85\xaf\xb8\x96\
+\x7c\xc1\x3f\x78\x06\xc7\x74\x0d\x90\x24\xc3\xdb\x6d\x74\x09\xd1\
+\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x02\x6c\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\
+\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\
+\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x12\x00\x00\
+\x0b\x12\x01\xd2\xdd\x7e\xfc\x00\x00\x00\x07\x74\x49\x4d\x45\x07\
+\xd2\x0b\x01\x0d\x00\x32\x9c\x41\x83\x23\x00\x00\x01\xe9\x49\x44\
+\x41\x54\x78\xda\x85\x93\xbd\x8b\x13\x51\x14\xc5\x7f\x4f\x07\xd7\
+\xc4\x04\xb2\x90\x2d\x32\x33\x11\x41\x10\x54\x58\x85\xf8\x0f\xd8\
+\x5a\x6a\x61\x65\x63\x21\x06\x02\x3a\x29\xd2\xd8\x29\x62\x13\xc2\
+\x42\x94\x34\x61\x43\x48\x1a\x21\x56\xdb\xd8\x68\x65\xa7\x29\x76\
+\x41\x50\x41\x0c\x64\x87\xc9\x84\x7c\x1a\x21\xb3\x60\x66\x2c\xb2\
+\x6f\x36\x93\x1d\xf0\xc1\x2b\xde\xfd\x38\xe7\xde\x7b\xde\x15\xdd\
+\x6e\x17\x00\x21\x04\x42\x08\x34\x4d\xc3\xb2\x2c\xff\xbd\x7a\x57\
+\xe3\xe4\x55\x9a\xcd\x66\xc0\x09\x90\x4a\xa5\x00\xe8\x74\x3a\xb4\
+\x5a\xad\x80\x6f\x15\xc4\x30\x0c\x84\x69\x9a\x01\x06\x99\x2c\xcf\
+\x60\x30\x08\xad\x22\x91\x48\x00\x2c\x2b\x58\x67\x00\x3c\x49\x96\
+\x4c\x26\x01\x28\x95\x4a\x7e\x8c\x61\x18\xbe\x1f\xcb\xb2\xe8\xf5\
+\x7a\xd8\xb6\x4d\xbf\xdf\xf7\x01\xf6\x5f\xc7\xbc\x63\xa0\x8c\x34\
+\x66\x32\x99\x75\x1f\x4a\xa3\xd1\x08\xf4\x25\x4f\x64\xeb\x26\x3f\
+\xde\xc2\x95\xfb\x9f\xbe\xc8\xe4\x76\xbb\xcd\xbb\x67\x11\x62\xfa\
+\x6d\x60\x6f\xd9\x42\xa1\x50\xe0\x98\xe5\x0e\xf0\x5c\x02\x6c\x24\
+\xb7\xb9\x90\xba\xca\x87\x37\x2e\x49\xef\x80\x98\xae\xf2\xe7\xf0\
+\x3b\x03\xb1\x8d\x12\x4f\x9f\x0c\x14\xa8\x00\x8f\x4d\xd3\x44\x55\
+\x55\x4c\xd3\x44\xd7\x75\x66\x87\x7b\xb8\x7f\x67\x98\xfd\x23\xd4\
+\xcd\xdf\x28\x91\x34\x9e\x7b\xc4\xfe\xd7\x2e\x37\xae\xa7\xb9\x7c\
+\xeb\x09\x80\x50\x80\x2c\x50\xd5\x34\x2d\x50\xc1\xcf\xcf\x35\x46\
+\xdf\xde\x73\xf0\x6b\x41\x7a\xeb\x0c\x00\x9b\x31\x41\xc7\x76\xb9\
+\x14\xb9\xeb\xcf\x4b\x14\x8b\xc5\x80\x3c\xf9\x7c\x1e\xc0\xfb\xf8\
+\x2a\x4a\xc7\x76\x79\xb8\xe3\x04\x64\x7d\xf1\xe0\x1c\xd7\x2e\x9e\
+\xe5\xde\xcb\xf9\xb2\x83\xe1\x70\xc8\x68\x34\x62\x3c\x1e\x33\x99\
+\x4c\x7c\x15\x76\x9f\x9e\x3f\xa5\x82\xf4\xed\x3c\xda\x38\x51\xa1\
+\x5e\xaf\x87\xfd\x03\xc9\xec\x1b\xcb\xe5\xb2\x8c\x13\xb9\x5c\xce\
+\xf3\x87\x38\x9d\x4e\x03\x2d\xc4\xe3\xf1\x00\x90\xe3\x38\xa1\x3f\
+\x51\x51\x94\xa5\x8c\xb5\x5a\x2d\xb4\x02\x00\xcb\xb2\xa8\x56\xab\
+\xa7\x76\x05\xa0\x52\xa9\x90\xcd\x66\x11\xb3\xd9\x2c\xc0\x10\x8d\
+\x46\x99\xcf\xe7\xa1\xdb\xb8\x5e\x09\x80\x70\x1c\x87\xc5\x62\xf1\
+\xdf\xb5\x0d\x4b\x06\xf8\x07\xf0\x1d\xb1\x3d\x6a\xe9\x1c\x20\x00\
+\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+\x00\x00\x02\x0f\
+\x89\
+\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
+\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\
+\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\
+\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x01\xbb\x00\x00\x01\xbb\
+\x01\x3a\xec\xe3\xe2\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\
+\x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\
+\x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x8c\x49\x44\
+\x41\x54\x78\xda\xa5\x93\x3f\x4b\x9b\x51\x14\x87\x7f\xe7\xfe\x79\
+\x93\x5c\xd3\x94\x1a\xad\x28\x2e\x9a\x21\x38\x04\x6a\xa1\x85\x40\
+\x1d\x05\xc9\xa4\x11\xa1\x54\x27\x33\xb4\x73\xa5\x73\xe9\x26\x01\
+\xc1\xc5\x45\x17\xe9\xd2\x51\xfc\x16\x0e\xfd\x02\x8d\x58\x11\x41\
+\x13\x93\x9a\x28\xc6\x44\xdf\x7b\xdf\xa3\x59\x2d\x89\xd1\x3c\x70\
+\xa6\x03\x0f\x9c\xdf\x39\x87\x98\x19\xbd\x20\xf0\x00\x9a\xa5\x4c\
+\x64\x3e\xb2\xf4\x6c\x01\x18\x4b\x2b\x8b\x2b\x3f\x63\x1f\x63\x5b\
+\xf4\x83\xe8\xc9\x82\x16\x66\xc0\x50\x6e\x21\xb7\x3c\x7c\x38\xfc\
+\x9b\xa6\xa9\xff\x69\x82\x08\x50\xbc\x2c\xc2\x85\x9d\xc8\xce\x66\
+\xdf\x24\x92\x89\x3f\x94\xa1\x49\xb4\x41\xe1\x21\x7d\x40\xb1\x5e\
+\x04\xa8\xd5\x54\x22\xfd\x21\x1d\x37\x2f\xcc\x1e\xcd\xd1\x67\xde\
+\xe1\xed\x47\x05\xc2\x48\x59\xb2\x25\x04\x41\xd0\x12\x40\x5b\x4d\
+\xa3\xe3\xa3\x9e\x0e\xe9\x2d\xef\x93\x97\xf6\x93\xfe\x17\xfe\xce\
+\xdc\x5e\x10\x16\xb2\x42\x15\xf8\xe4\x43\x3b\x0d\xe5\x2b\xc8\x86\
+\x84\x8e\x68\x95\x1a\x4b\xe5\x0a\xc7\xfb\x6f\x89\xe8\x3d\xdf\xd3\
+\x56\x50\xa0\x02\x6c\x60\x81\x06\x40\x17\x04\x51\x13\x70\x65\x07\
+\x73\x63\x38\x30\x01\x75\x0c\x51\x7a\x5a\x88\x41\x01\xc4\x01\xbc\
+\x04\x38\xca\x70\x21\x87\x57\x43\xfd\xd6\xc6\x83\x5f\xcd\x91\xe6\
+\x3b\xe6\x4e\x23\x78\x42\x71\x4b\x70\x05\xc0\x02\xba\xae\xd9\x67\
+\xe7\x57\x9b\xb5\xaf\xbc\xee\x36\x1e\x0d\x91\x34\xc9\xfb\x02\x08\
+\x88\x22\xea\xc2\x0d\x73\x55\x69\x54\x67\x78\xed\x76\xaf\xab\x35\
+\x4a\xad\x24\x9f\x5a\xbc\xae\x0f\x59\x7b\x14\x1c\x54\x4e\xce\xa6\
+\x38\xcf\xe5\xae\x0f\x49\x28\x21\xc7\xfe\x25\x70\xfd\xf7\x76\xf7\
+\xbc\x5a\x9e\xe0\x55\x2e\xa3\x03\xff\x7d\xa3\xca\x87\xe6\x40\x34\
+\x68\xbf\x35\x37\xd1\x05\x3d\xbf\xf3\x1d\x7f\x4b\x95\x33\x4b\xa1\
+\xe2\xc5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\
+"
+
+qt_resource_name = "\
+\x00\x0e\
+\x06\x0c\x0a\x07\
+\x00\x61\
+\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x64\x00\x6f\x00\x77\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x07\
+\x07\x01\x57\xa7\
+\x00\x70\
+\x00\x69\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
+\x00\x0f\
+\x0f\x22\x64\xc7\
+\x00\x61\
+\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2d\x00\x72\x00\x69\x00\x67\x00\x68\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\
+"
+
+qt_resource_struct = "\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x02\x11\
+\x00\x00\x00\x36\x00\x00\x00\x00\x00\x01\x00\x00\x04\x81\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.qrc b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.qrc
new file mode 100644
index 0000000000..87c35d6151
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/dockwidget_icons.qrc
@@ -0,0 +1,8 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+<qresource>
+ <file>pin.png</file>
+ <file>arrow-right.png</file>
+ <file>arrow-down.png</file>
+</qresource>
+</RCC>
\ No newline at end of file
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py
new file mode 100644
index 0000000000..9e66a1cb5f
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/highlighter.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python
+
+"""
+highlightedtextedit.py
+
+A PyQt custom widget example for Qt Designer.
+
+Copyright (C) 2006 David Boddie <david@boddie.org.uk>
+Copyright (C) 2005-2006 Trolltech ASA. All rights reserved.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+
+from PyQt5 import QtCore, QtGui
+
+
+
+
+class PythonHighlighter(QtGui.QSyntaxHighlighter):
+
+ keywords = (
+ "and", "del", "for", "is", "raise",
+ "assert", "elif", "from", "lambda", "return",
+ "break", "else", "global", "not", "try",
+ "class", "except", "if", "or", "while",
+ "continue", "exec", "import", "pass", "yield",
+ "def", "finally", "in", "print"
+ )
+
+ def __init__(self, edit):
+ document = edit.document()
+ QtGui.QSyntaxHighlighter.__init__(self, document)
+
+ base_format = QtGui.QTextCharFormat()
+ base_format.setFont(edit.font())
+
+ self.base_format = base_format
+ self.document = document
+
+ self.updateHighlighter(base_format.font())
+
+ def highlightBlock(self, text):
+
+ self.setCurrentBlockState(0)
+
+ if text.trimmed().isEmpty():
+ self.setFormat(0, len(text), self.empty_format)
+ return
+
+ self.setFormat(0, len(text), self.base_format)
+
+ startIndex = 0
+ if self.previousBlockState() != 1:
+ startIndex = self.multiLineStringBegin.indexIn(text)
+
+ if startIndex > -1:
+ self.highlightRules(text, 0, startIndex)
+ else:
+ self.highlightRules(text, 0, len(text))
+
+ while startIndex >= 0:
+
+ endIndex = self.multiLineStringEnd.indexIn(text,
+ startIndex + len(self.multiLineStringBegin.pattern()))
+ if endIndex == -1:
+ self.setCurrentBlockState(1)
+ commentLength = text.length() - startIndex
+ else:
+ commentLength = endIndex - startIndex + \
+ self.multiLineStringEnd.matchedLength()
+ self.highlightRules(text, endIndex, len(text))
+
+ self.setFormat(startIndex, commentLength, self.multiLineStringFormat)
+ startIndex = self.multiLineStringBegin.indexIn(text,
+ startIndex + commentLength)
+
+ def highlightRules(self, text, start, finish):
+
+ for expression, format in self.rules:
+
+ index = expression.indexIn(text, start)
+ while index >= start and index < finish:
+ length = expression.matchedLength()
+ self.setFormat(index, min(length, finish - index), format)
+ index = expression.indexIn(text, index + length)
+
+ def updateFonts(self, font):
+
+ self.base_format.setFont(font)
+ self.empty_format = QtGui.QTextCharFormat(self.base_format)
+ #self.empty_format.setFontPointSize(font.pointSize()/4.0)
+
+ self.keywordFormat = QtGui.QTextCharFormat(self.base_format)
+ self.keywordFormat.setForeground(QtCore.Qt.darkBlue)
+ self.keywordFormat.setFontWeight(QtGui.QFont.Bold)
+ self.callableFormat = QtGui.QTextCharFormat(self.base_format)
+ self.callableFormat.setForeground(QtCore.Qt.darkBlue)
+ self.magicFormat = QtGui.QTextCharFormat(self.base_format)
+ self.magicFormat.setForeground(QtGui.QColor(224,128,0))
+ self.qtFormat = QtGui.QTextCharFormat(self.base_format)
+ self.qtFormat.setForeground(QtCore.Qt.blue)
+ self.qtFormat.setFontWeight(QtGui.QFont.Bold)
+ self.selfFormat = QtGui.QTextCharFormat(self.base_format)
+ self.selfFormat.setForeground(QtCore.Qt.red)
+ #self.selfFormat.setFontItalic(True)
+ self.singleLineCommentFormat = QtGui.QTextCharFormat(self.base_format)
+ self.singleLineCommentFormat.setForeground(QtCore.Qt.darkGreen)
+ self.multiLineStringFormat = QtGui.QTextCharFormat(self.base_format)
+ self.multiLineStringFormat.setBackground(
+ QtGui.QBrush(QtGui.QColor(127,127,255)))
+ self.quotationFormat1 = QtGui.QTextCharFormat(self.base_format)
+ self.quotationFormat1.setForeground(QtCore.Qt.blue)
+ self.quotationFormat2 = QtGui.QTextCharFormat(self.base_format)
+ self.quotationFormat2.setForeground(QtCore.Qt.blue)
+
+ def updateRules(self):
+
+ self.rules = []
+ self.rules += map(lambda s: (QtCore.QRegExp(r"\b"+s+r"\b"),
+ self.keywordFormat), self.keywords)
+
+ self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat))
+ self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat))
+ self.rules.append((QtCore.QRegExp(r"\bself\b"), self.selfFormat))
+ self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat))
+
+ self.rules.append((QtCore.QRegExp(r"#[^\n]*"), self.singleLineCommentFormat))
+
+ self.multiLineStringBegin = QtCore.QRegExp(r'\"\"\"')
+ self.multiLineStringEnd = QtCore.QRegExp(r'\"\"\"')
+
+ self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1))
+ self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2))
+
+ def updateHighlighter(self, font):
+
+ self.updateFonts(font)
+ self.updateRules()
+ self.setDocument(self.document)
+
+
+class QtQmlHighlighter(PythonHighlighter):
+
+ keywords = """"
+ break for throw case function try
+ catch if typeof continue in var default instanceof void
+ delete new undefined do return while else switch with
+ finally this """.split() + \
+ ['NaN', 'Infinity', 'undefined', 'print', 'parseInt',
+ 'parseFloat', 'isNaN', 'isFinite', 'decodeURI',
+ 'decodeURIComponent', 'encodeURI', 'encodeURIComponent',
+ 'escape', 'unescape', 'version', 'gc', 'Object',
+ 'Function', 'Number', 'Boolean', 'String', 'Date', 'Array',
+ 'RegExp', 'Error', 'EvalError','RangeError', 'ReferenceError',
+ 'SyntaxError', 'TypeError', 'URIError', 'eval', 'Math',
+ 'Enumeration', 'Variant', 'QObject', 'QMetaObject']
+
+ def __init__(self, edit):
+ PythonHighlighter.__init__(self, edit)
+
+
+ def updateRules(self):
+
+ self.rules = []
+ self.rules += map(lambda s: (QtCore.QRegExp(r"\b"+s+r"\b"),
+ self.keywordFormat), self.keywords)
+
+ self.rules.append((QtCore.QRegExp(r"\b[A-Za-z_]+\(.*\)"), self.callableFormat))
+ #self.rules.append((QtCore.QRegExp(r"\b__[a-z]+__\b"), self.magicFormat))
+ self.rules.append((QtCore.QRegExp(r"\bthis\b"), self.selfFormat))
+ self.rules.append((QtCore.QRegExp(r"\bQ([A-Z][a-z]*)+\b"), self.qtFormat))
+
+ self.rules.append((QtCore.QRegExp(r"//[^\n]*"), self.singleLineCommentFormat))
+
+ # XXX quick hack to support QtQml syntax
+ self.multiLineStringBegin = QtCore.QRegExp(r'/\*')
+ self.multiLineStringEnd = QtCore.QRegExp(r'\*/')
+ self.multiLineStringFormat = self.singleLineCommentFormat
+ self.rules.append((QtCore.QRegExp(r'\"[^\n]*\"'), self.quotationFormat1))
+ self.rules.append((QtCore.QRegExp(r"'[^\n]*'"), self.quotationFormat2))
+
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py
new file mode 100644
index 0000000000..bd635ddfef
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/indenter.py
@@ -0,0 +1,242 @@
+import re
+
+from rope.base import codeanalyze
+
+
+class TextIndenter(object):
+ """A class for formatting texts"""
+
+ def __init__(self, editor, indents=4):
+ self.editor = editor
+ self.indents = indents
+ self.line_editor = editor.line_editor()
+
+ def correct_indentation(self, lineno):
+ """Correct the indentation of a line"""
+
+ def deindent(self, lineno):
+ """Deindent the a line"""
+ current_indents = self._count_line_indents(lineno)
+ new_indents = max(0, current_indents - self.indents)
+ self._set_line_indents(lineno, new_indents)
+
+ def indent(self, lineno):
+ """Indent a line"""
+ current_indents = self._count_line_indents(lineno)
+ new_indents = current_indents + self.indents
+ self._set_line_indents(lineno, new_indents)
+
+ def entering_new_line(self, lineno):
+ """Indent a line
+
+ Uses `correct_indentation` and last line indents
+ """
+ last_line = ""
+ if lineno > 1:
+ last_line = self.line_editor.get_line(lineno - 1)
+ if last_line.strip() == '':
+ self._set_line_indents(lineno, len(last_line))
+ else:
+ self.correct_indentation(lineno)
+
+ def insert_tab(self, index):
+ """Inserts a tab in the given index"""
+ self.editor.insert(index, ' ' * self.indents)
+
+ def _set_line_indents(self, lineno, indents):
+ old_indents = self._count_line_indents(lineno)
+ indent_diffs = indents - old_indents
+ self.line_editor.indent_line(lineno, indent_diffs)
+
+ def _count_line_indents(self, lineno):
+ contents = self.line_editor.get_line(lineno)
+ result = 0
+ for x in contents:
+ if x == ' ':
+ result += 1
+ elif x == '\t':
+ result += 8
+ else:
+ break
+ return result
+
+
+class NormalIndenter(TextIndenter):
+
+ def __init__(self, editor):
+ super(NormalIndenter, self).__init__(editor)
+
+ def correct_indentation(self, lineno):
+ prev_indents = 0
+ if lineno > 1:
+ prev_indents = self._count_line_indents(lineno - 1)
+ self._set_line_indents(lineno, prev_indents)
+
+
+class PythonCodeIndenter(TextIndenter):
+
+ def __init__(self, editor, indents=4):
+ super(PythonCodeIndenter, self).__init__(editor, indents)
+
+ def _last_non_blank(self, lineno):
+ current_line = lineno - 1
+ while current_line != 1 and \
+ self.line_editor.get_line(current_line).strip() == '':
+ current_line -= 1
+ return current_line
+
+ def _get_correct_indentation(self, lineno):
+ if lineno == 1:
+ return 0
+ new_indent = self._get_base_indentation(lineno)
+
+ prev_lineno = self._last_non_blank(lineno)
+ prev_line = self.line_editor.get_line(prev_lineno)
+ if prev_lineno == lineno or prev_line.strip() == '':
+ new_indent = 0
+ current_line = self.line_editor.get_line(lineno)
+ new_indent += self._indents_caused_by_current_stmt(current_line)
+ return new_indent
+
+ def _get_base_indentation(self, lineno):
+ range_finder = _StatementRangeFinder(
+ self.line_editor, self._last_non_blank(lineno))
+ start = range_finder.get_statement_start()
+ if not range_finder.is_line_continued():
+ changes = self._indents_caused_by_prev_stmt(
+ (start, self._last_non_blank(lineno)))
+ return self._count_line_indents(start) + changes
+ if range_finder.last_open_parens():
+ open_parens = range_finder.last_open_parens()
+ parens_line = self.line_editor.get_line(open_parens[0])
+ if parens_line[open_parens[1] + 1:].strip() == '':
+ if len(range_finder.open_parens) > 1:
+ return range_finder.open_parens[-2][1] + 1
+ else:
+ return self._count_line_indents(start) + self.indents
+ return range_finder.last_open_parens()[1] + 1
+
+ start_line = self.line_editor.get_line(start)
+ if start == lineno - 1:
+ try:
+ equals_index = start_line.index(' = ') + 1
+ if start_line[equals_index + 1:].strip() == '\\':
+ return self._count_line_indents(start) + self.indents
+ return equals_index + 2
+ except ValueError:
+ match = re.search(r'(\b )|(\.)', start_line)
+ if match:
+ return match.start() + 1
+ else:
+ return len(start_line) + 1
+ else:
+ return self._count_line_indents(self._last_non_blank(lineno))
+
+ def _indents_caused_by_prev_stmt(self, stmt_range):
+ first_line = self.line_editor.get_line(stmt_range[0])
+ last_line = self.line_editor.get_line(stmt_range[1])
+ new_indent = 0
+ if self._strip(last_line).endswith(':'):
+ new_indent += self.indents
+ if self._startswith(first_line, ('return', 'raise', 'pass',
+ 'break', 'continue')):
+ new_indent -= self.indents
+ return new_indent
+
+ def _startswith(self, line, tokens):
+ line = self._strip(line)
+ for token in tokens:
+ if line == token or line.startswith(token + ' '):
+ return True
+
+ def _strip(self, line):
+ try:
+ numsign = line.rindex('#')
+ comment = line[numsign:]
+ if '\'' not in comment and '\"' not in comment:
+ line = line[:numsign]
+ except ValueError:
+ pass
+ return line.strip()
+
+ def _indents_caused_by_current_stmt(self, current_line):
+ new_indent = 0
+ if self._strip(current_line) == 'else:':
+ new_indent -= self.indents
+ if self._strip(current_line) == 'finally:':
+ new_indent -= self.indents
+ if self._startswith(current_line, ('elif',)):
+ new_indent -= self.indents
+ if self._startswith(current_line, ('except',)) and \
+ self._strip(current_line).endswith(':'):
+ new_indent -= self.indents
+ return new_indent
+
+ def correct_indentation(self, lineno):
+ """Correct the indentation of the line containing the given index"""
+ self._set_line_indents(lineno, self._get_correct_indentation(lineno))
+
+
+class _StatementRangeFinder(object):
+ """A method object for finding the range of a statement"""
+
+ def __init__(self, lines, lineno):
+ self.lines = lines
+ self.lineno = lineno
+ self.in_string = ''
+ self.open_count = 0
+ self.explicit_continuation = False
+ self.open_parens = []
+ self._analyze()
+
+ def _analyze_line(self, lineno):
+ current_line = self.lines.get_line(lineno)
+ for i, char in enumerate(current_line):
+ if char in '\'"':
+ if self.in_string == '':
+ self.in_string = char
+ if char * 3 == current_line[i:i + 3]:
+ self.in_string = char * 3
+ elif self.in_string == current_line[i:i + len(self.in_string)] and \
+ not (i > 0 and current_line[i - 1] == '\\' and
+ not (i > 1 and current_line[i - 2:i] == '\\\\')):
+ self.in_string = ''
+ if self.in_string != '':
+ continue
+ if char == '#':
+ break
+ if char in '([{':
+ self.open_count += 1
+ self.open_parens.append((lineno, i))
+ if char in ')]}':
+ self.open_count -= 1
+ if self.open_parens:
+ self.open_parens.pop()
+ if current_line and char != '#' and current_line.endswith('\\'):
+ self.explicit_continuation = True
+ else:
+ self.explicit_continuation = False
+
+ def _analyze(self):
+ last_statement = 1
+ block_start = codeanalyze.get_block_start(self.lines, self.lineno)
+ for current_line_number in range(block_start, self.lineno + 1):
+ if not self.explicit_continuation and \
+ self.open_count == 0 and self.in_string == '':
+ last_statement = current_line_number
+ self._analyze_line(current_line_number)
+ self.statement_start = last_statement
+
+ def get_statement_start(self):
+ return self.statement_start
+
+ def last_open_parens(self):
+ if not self.open_parens:
+ return None
+ return self.open_parens[-1]
+
+ def is_line_continued(self):
+ return self.open_count != 0 or self.explicit_continuation
+
+ def get_line_indents(self, line_number):
+ return self._count_line_indents(self.lines.get_line(line_number))
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py
new file mode 100644
index 0000000000..8912a8b7e9
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.py
@@ -0,0 +1,220 @@
+from PyQt5.QtCore import pyqtSlot, Qt
+from PyQt5.QtGui import QCloseEvent
+from PyQt5.QtWidgets import (QApplication, QFileDialog, QMainWindow,
+ QMessageBox, QSplitter, QTabWidget)
+
+from widget import PythonEditorWidget, QtQmlEditorWidget, SaveDialog
+from console import PythonConsole, QtQmlConsole
+from mainwindow_ui import Ui_ScriptEditor
+
+
+import traceback
+import os
+
+template_py = """\
+# -*- coding: utf-8 -*-
+from __future__ import with_statement
+
+"""
+
+class EditorMainWindow(QMainWindow):
+
+
+ def __init__(self, parent=None):
+ QMainWindow.__init__(self, parent)
+ self.ui = Ui_ScriptEditor()
+ self.ui.setupUi(self)
+ #self.ui.actionExit.triggered.connect(self.exit)
+ self.splitter = QSplitter(Qt.Vertical, self)
+ self.setCentralWidget(self.splitter)
+ self.edit_tab = QTabWidget(self.splitter)
+ self.console_tab = QTabWidget(self.splitter)
+ self.py_console = PythonConsole(self.console_tab)
+ self.console_tab.addTab(self.py_console, "&Python console")
+ self.js_console = QtQmlConsole(self.console_tab)
+ self.console_tab.addTab(self.js_console, "&QtQml console")
+ self.editors = []
+ self.on_actionNewPython_triggered()
+
+ @pyqtSlot()
+ def closeEvent(self, event):
+ while(self.editors.__len__()):
+ edit = self.edit_tab.currentWidget()
+ if edit:
+ if(edit.isModified()):
+ saveBox = SaveDialog("You have unsaved script. Save it now?")
+ prompt = saveBox.exec_()
+ if(prompt == QMessageBox.Save):
+ event.ignore()
+ self.save(True)
+ elif(prompt == QMessageBox.Cancel):
+ event.ignore()
+ return
+ elif(prompt == QMessageBox.Discard):
+ event.accept()
+ i = self.edit_tab.indexOf(edit)
+ self.edit_tab.removeTab(i)
+ self.editors.remove(edit)
+ event.accept()
+
+
+
+ @pyqtSlot()
+ def on_actionExit_triggered(self):
+ while(self.editors.__len__()):
+ edit = self.edit_tab.currentWidget()
+ if edit:
+ if(edit.isModified()):
+ saveBox = SaveDialog("You have unsaved script. Save it now?")
+ prompt = saveBox.exec_()
+ if(prompt == QMessageBox.Save):
+ self.save(True)
+ elif(prompt == QMessageBox.Cancel):
+ return
+ elif(prompt == QMessageBox.Discard):
+ pass
+ i = self.edit_tab.indexOf(edit)
+ self.edit_tab.removeTab(i)
+ self.editors.remove(edit)
+ self.close()
+
+ @pyqtSlot()
+ def on_actionNewPython_triggered(self):
+ pyedit = PythonEditorWidget(self.edit_tab)
+ pyedit.setPlainText(template_py)
+ self.edit_tab.addTab(pyedit, "Python")
+ self.edit_tab.setCurrentWidget(pyedit)
+ self.editors.append(pyedit)
+ self.py_console.attach()
+ self.console_tab.setCurrentIndex(0)
+ pyedit.setFocus()
+ pyedit.view.setFocus()
+
+
+ @pyqtSlot()
+ def on_actionNewQtQml_triggered(self):
+ jsedit = QtQmlEditorWidget(self.edit_tab)
+ self.edit_tab.addTab(jsedit, "QtQml")
+ self.edit_tab.setCurrentWidget(jsedit)
+ self.editors.append(jsedit)
+ self.js_console.attach()
+ self.console_tab.setCurrentIndex(1)
+
+
+ @pyqtSlot()
+ def on_actionClose_triggered(self):
+ edit = self.edit_tab.currentWidget()
+ if edit:
+ if(edit.isModified()):
+ saveBox = SaveDialog("Do you want to save this Script?")
+ prompt = saveBox.exec_()
+ if(prompt == QMessageBox.Save):
+ self.save(True)
+ elif(prompt == QMessageBox.Cancel):
+ return
+ elif(prompt == QMessageBox.Discard):
+ pass
+ i = self.edit_tab.indexOf(edit)
+ self.edit_tab.removeTab(i)
+ self.editors.remove(edit)
+
+
+ @pyqtSlot()
+ def on_actionClear_triggered(self):
+ #edit = self.edit_tab.currentWidget()
+ #edit.setPlainText(template_py)
+ self.py_console.clear()
+
+
+ @pyqtSlot()
+ def on_actionSave_As_triggered(self):
+ self.save()
+
+
+ @pyqtSlot()
+ def on_actionSave_triggered(self):
+ self.save(True)
+
+
+ #Path of the script file in each tab will be stored in tabToolTip
+ def save(self, Update = False):
+ edit = self.edit_tab.currentWidget()
+ contents = str(edit.toPlainText())
+ if((Update == False) or (self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python") ):
+ #Save in its first invocation and Save As will enter
+ filename = QFileDialog.getSaveFileName(self, "Save File", "", "*.spy")
+ fil = open(filename , 'w')
+ if(filename and self.edit_tab.tabText(self.edit_tab.currentIndex()) == "Python"):
+ #Script hasn't been saved before and user specifies a valid filename
+ self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename+'.spy')
+ self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename+'.spy')))
+ else:
+ #filename = self.edit_tab.tabText(self.edit_tab.currentIndex())
+ filename = self.edit_tab.tabToolTip(self.edit_tab.currentIndex())
+ fil = open( filename , 'w')
+ fil.write(contents)
+ fil.close()
+ edit.setModified(False)
+
+
+ @pyqtSlot()
+ def on_actionOpen_triggered(self):
+ filename = QFileDialog.getOpenFileName(self,"Open File","","*.spy")
+ try:
+ fil = open(filename , 'r')
+ except IOError:
+ return
+ code = fil.read()
+ edit = self.edit_tab.currentWidget()
+ self.edit_tab.setTabText(self.edit_tab.currentIndex(), os.path.basename(str(filename)))
+ self.edit_tab.setTabToolTip(self.edit_tab.currentIndex(), filename)
+ edit.setPlainText(code)
+ fil.close()
+
+
+ @pyqtSlot()
+ def on_actionRun_triggered(self):
+ self.run()
+
+
+ @pyqtSlot()
+ def on_actionRunConsole_triggered(self):
+ self.run(True)
+
+
+ def run(self, console=False):
+ edit = self.edit_tab.currentWidget()
+ code = str(edit.toPlainText())
+ if isinstance(edit, PythonEditorWidget):
+ self.py_console.attach()
+ self.console_tab.setCurrentIndex(0)
+ if console:
+ namespace = self.py_console.namespace
+ else:
+ namespace = {}
+ try:
+ exec code in namespace
+ except Exception as e:
+ traceback.print_exc()
+ try:
+ Scripter.activeWindow.redraw = True
+ Scripter.activeWindow.update()
+ except: pass
+ else:
+ self.js_console.attach()
+ self.console_tab.setCurrentIndex(1)
+ if console:
+ self.js_console.inter.execute(code)
+ else:
+ self.js_console.inter.execute_code(code)
+
+
+
+
+if __name__ == "__main__":
+ import sys
+ app = QApplication(sys.argv)
+ win = EditorMainWindow()
+ win.resize(640, 480)
+ win.show()
+ app.exec_()
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.ui b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.ui
new file mode 100644
index 0000000000..069e8df9eb
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow.ui
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ScriptEditor</class>
+ <widget class="QMainWindow" name="ScriptEditor">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>624</width>
+ <height>449</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Script Editor</string>
+ </property>
+ <widget class="QWidget" name="centralwidget"/>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>624</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>&amp;File</string>
+ </property>
+ <widget class="QMenu" name="menu_New">
+ <property name="title">
+ <string>&amp;New</string>
+ </property>
+ <addaction name="actionNewPython"/>
+ <addaction name="actionNewQtScript"/>
+ </widget>
+ <addaction name="menu_New"/>
+ <addaction name="actionOpen"/>
+ <addaction name="actionSave"/>
+ <addaction name="actionSave_As"/>
+ <addaction name="actionClose"/>
+ <addaction name="separator"/>
+ <addaction name="actionExit"/>
+ </widget>
+ <widget class="QMenu" name="menuRun">
+ <property name="title">
+ <string>&amp;Run</string>
+ </property>
+ <addaction name="actionRun"/>
+ <addaction name="actionRunConsole"/>
+ <addaction name="actionClear"/>
+ </widget>
+ <addaction name="menuFile"/>
+ <addaction name="menuRun"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionClose">
+ <property name="text">
+ <string>&amp;Close</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+W</string>
+ </property>
+ </action>
+ <action name="actionExit">
+ <property name="text">
+ <string>&amp;Exit</string>
+ </property>
+ </action>
+ <action name="actionRun">
+ <property name="text">
+ <string>&amp;Run</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+R</string>
+ </property>
+ </action>
+ <action name="actionRunConsole">
+ <property name="text">
+ <string>Run script in &amp;console</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+C</string>
+ </property>
+ </action>
+ <action name="actionNewPython">
+ <property name="text">
+ <string>Python</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
+ </action>
+ <action name="actionNewQtScript">
+ <property name="text">
+ <string>QtScript</string>
+ </property>
+ </action>
+ <action name="actionClear">
+ <property name="text">
+ <string>Clear</string>
+ </property>
+ <property name="toolTip">
+ <string>Clear The Console</string>
+ </property>
+ </action>
+ <action name="actionSave_As">
+ <property name="text">
+ <string>Save &amp;As</string>
+ </property>
+ <property name="toolTip">
+ <string>Save the script</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+A</string>
+ </property>
+ </action>
+ <action name="actionOpen">
+ <property name="text">
+ <string>&amp;Open</string>
+ </property>
+ <property name="toolTip">
+ <string>Open a script</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionSave">
+ <property name="text">
+ <string>&amp;Save</string>
+ </property>
+ <property name="toolTip">
+ <string>Save the current script</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py
new file mode 100644
index 0000000000..6e52bcf3f7
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/mainwindow_ui.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'mainwindow.ui'
+#
+# Created: Fri Aug 15 04:25:57 2014
+# by: PyQt5 UI code generator 5.3.1
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+class Ui_ScriptEditor(object):
+ def setupUi(self, ScriptEditor):
+ ScriptEditor.setObjectName("ScriptEditor")
+ ScriptEditor.resize(624, 449)
+ self.centralwidget = QtWidgets.QWidget(ScriptEditor)
+ self.centralwidget.setObjectName("centralwidget")
+ ScriptEditor.setCentralWidget(self.centralwidget)
+ self.menubar = QtWidgets.QMenuBar(ScriptEditor)
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 624, 25))
+ self.menubar.setObjectName("menubar")
+ self.menuFile = QtWidgets.QMenu(self.menubar)
+ self.menuFile.setObjectName("menuFile")
+ self.menu_New = QtWidgets.QMenu(self.menuFile)
+ self.menu_New.setObjectName("menu_New")
+ self.menuRun = QtWidgets.QMenu(self.menubar)
+ self.menuRun.setObjectName("menuRun")
+ ScriptEditor.setMenuBar(self.menubar)
+ self.statusbar = QtWidgets.QStatusBar(ScriptEditor)
+ self.statusbar.setObjectName("statusbar")
+ ScriptEditor.setStatusBar(self.statusbar)
+ self.actionClose = QtWidgets.QAction(ScriptEditor)
+ self.actionClose.setObjectName("actionClose")
+ self.actionExit = QtWidgets.QAction(ScriptEditor)
+ self.actionExit.setObjectName("actionExit")
+ self.actionRun = QtWidgets.QAction(ScriptEditor)
+ self.actionRun.setObjectName("actionRun")
+ self.actionRunConsole = QtWidgets.QAction(ScriptEditor)
+ self.actionRunConsole.setObjectName("actionRunConsole")
+ self.actionNewPython = QtWidgets.QAction(ScriptEditor)
+ self.actionNewPython.setObjectName("actionNewPython")
+ self.actionNewQtQml = QtWidgets.QAction(ScriptEditor)
+ self.actionNewQtQml.setObjectName("actionNewQtQml")
+ self.actionClear = QtWidgets.QAction(ScriptEditor)
+ self.actionClear.setObjectName("actionClear")
+ self.actionSave_As = QtWidgets.QAction(ScriptEditor)
+ self.actionSave_As.setObjectName("actionSave_As")
+ self.actionOpen = QtWidgets.QAction(ScriptEditor)
+ self.actionOpen.setObjectName("actionOpen")
+ self.actionSave = QtWidgets.QAction(ScriptEditor)
+ self.actionSave.setObjectName("actionSave")
+ self.menu_New.addAction(self.actionNewPython)
+ self.menu_New.addAction(self.actionNewQtQml)
+ self.menuFile.addAction(self.menu_New.menuAction())
+ self.menuFile.addAction(self.actionOpen)
+ self.menuFile.addAction(self.actionSave)
+ self.menuFile.addAction(self.actionSave_As)
+ self.menuFile.addAction(self.actionClose)
+ self.menuFile.addSeparator()
+ self.menuFile.addAction(self.actionExit)
+ self.menuRun.addAction(self.actionRun)
+ self.menuRun.addAction(self.actionRunConsole)
+ self.menuRun.addAction(self.actionClear)
+ self.menubar.addAction(self.menuFile.menuAction())
+ self.menubar.addAction(self.menuRun.menuAction())
+
+ self.retranslateUi(ScriptEditor)
+ QtCore.QMetaObject.connectSlotsByName(ScriptEditor)
+
+ def retranslateUi(self, ScriptEditor):
+ _translate = QtCore.QCoreApplication.translate
+ ScriptEditor.setWindowTitle(_translate("ScriptEditor", "Script Editor"))
+ self.menuFile.setTitle(_translate("ScriptEditor", "&File"))
+ self.menu_New.setTitle(_translate("ScriptEditor", "&New"))
+ self.menuRun.setTitle(_translate("ScriptEditor", "&Run"))
+ self.actionClose.setText(_translate("ScriptEditor", "&Close"))
+ self.actionClose.setShortcut(_translate("ScriptEditor", "Ctrl+W"))
+ self.actionExit.setText(_translate("ScriptEditor", "&Exit"))
+ self.actionRun.setText(_translate("ScriptEditor", "&Run"))
+ self.actionRun.setShortcut(_translate("ScriptEditor", "Ctrl+R"))
+ self.actionRunConsole.setText(_translate("ScriptEditor", "Run script in &console"))
+ self.actionRunConsole.setShortcut(_translate("ScriptEditor", "Ctrl+C"))
+ self.actionNewPython.setText(_translate("ScriptEditor", "Python"))
+ self.actionNewPython.setShortcut(_translate("ScriptEditor", "Ctrl+N"))
+ self.actionNewQtQml.setText(_translate("ScriptEditor", "QtQml"))
+ self.actionClear.setText(_translate("ScriptEditor", "Clear"))
+ self.actionClear.setToolTip(_translate("ScriptEditor", "Clear The Console"))
+ self.actionSave_As.setText(_translate("ScriptEditor", "Save &As"))
+ self.actionSave_As.setToolTip(_translate("ScriptEditor", "Save the script"))
+ self.actionSave_As.setShortcut(_translate("ScriptEditor", "Ctrl+A"))
+ self.actionOpen.setText(_translate("ScriptEditor", "&Open"))
+ self.actionOpen.setToolTip(_translate("ScriptEditor", "Open a script"))
+ self.actionOpen.setShortcut(_translate("ScriptEditor", "Ctrl+O"))
+ self.actionSave.setText(_translate("ScriptEditor", "&Save"))
+ self.actionSave.setToolTip(_translate("ScriptEditor", "Save the current script"))
+ self.actionSave.setShortcut(_translate("ScriptEditor", "Ctrl+S"))
+
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/pin.png b/plugins/extensions/pykrita/plugin/krita/sceditor/pin.png
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/rope.zip b/plugins/extensions/pykrita/plugin/krita/sceditor/rope.zip
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py
new file mode 100644
index 0000000000..3b3878e460
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/krita/sceditor/widget.py
@@ -0,0 +1,463 @@
+# -*- coding: utf-8 -*-
+import re
+import sys
+import os
+
+# I put the rope package into a ZIP-file to save space
+# and to keep everything clear
+path = os.path.dirname(os.path.abspath(__file__))
+sys.path.insert(0, os.path.join(path, "rope.zip"))
+
+
+from rope.base.project import get_no_project
+from rope.contrib.codeassist import code_assist
+
+from PyQt5.QtCore import QCoreApplication, QLine, Qt
+from PyQt5.QtGui import (QBrush, QColor, QFont, QKeyEvent, QTextBlockUserData,
+ QTextCursor, QPainter, QPalette, QPen)
+from PyQt5.QtWidgets import (QApplication, QFrame, QHBoxLayout, QMessageBox,
+ QPlainTextEdit, QVBoxLayout, QWidget)
+
+from indenter import PythonCodeIndenter
+from assist import AutoComplete, CallTip
+
+from highlighter import PythonHighlighter, QtQmlHighlighter
+
+
+
+
+
+
+
+
+
+class EditorBlockData(QTextBlockUserData):
+
+ def __init__(self):
+ QTextBlockUserData.__init__(self)
+
+
+
+
+
+
+class RopeEditorWrapper(object):
+
+
+ def __init__(self, editview):
+ self.editview = editview
+
+
+ def length(self):
+ return self.editview.length()
+
+
+ def line_editor(self):
+ return self
+
+
+ def _get_block(self, line_no=None):
+ cursor = self.editview.textCursor()
+ row = cursor.blockNumber()
+ if line_no == None:
+ line_no = row
+ block = cursor.block()
+ while row > line_no:
+ block = block.previous()
+ row -= 1
+ while row < line_no:
+ block = block.next()
+ row += 1
+ return block
+
+
+ def get_line(self, line_no=None):
+ return unicode(self._get_block(line_no).text())
+
+
+ def indent_line(self, line_no, indent_length):
+ block = self._get_block(line_no)
+ cursor = QTextCursor(block)
+ cursor.joinPreviousEditBlock()
+ cursor.movePosition(QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
+ if indent_length < 0:
+ for i in range(-indent_length):
+ cursor.deleteChar()
+ else:
+ cursor.insertText(" " * indent_length)
+ if indent_length:
+ cursor.movePosition(
+ QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
+ line = unicode(cursor.block().text())
+ if len(line) and line[0] == " ":
+ cursor.movePosition(
+ QTextCursor.NextWord, QTextCursor.MoveAnchor)
+ self.editview.setTextCursor(cursor)
+ cursor.endEditBlock()
+
+
+
+
+class EditorView(QPlainTextEdit):
+
+
+ def __init__(self, parent=None, text=None,
+ EditorHighlighterClass=PythonHighlighter,
+ indenter=PythonCodeIndenter):
+ QPlainTextEdit.__init__(self, parent)
+ self.setFrameStyle(QFrame.NoFrame)
+ self.setTabStopWidth(4)
+ self.setLineWrapMode(QPlainTextEdit.NoWrap)
+ font = QFont()
+ font.setFamily("lucidasanstypewriter")
+ font.setFixedPitch(True)
+ font.setPointSize(10)
+ self.setFont(font)
+ self.highlighter = EditorHighlighterClass(self)
+ if text:
+ self.setPlainText(text)
+ self.frame_style = self.frameStyle()
+ self.draw_line = True
+ self.print_width = self.fontMetrics().width("x"*78)
+ self.line_pen = QPen(QColor("lightgrey"))
+ self.last_row = self.last_col = -1
+ self.last_block = None
+ self.highlight_line = True
+ self.highlight_color = self.palette().highlight().color().light(175)
+ self.highlight_brush = QBrush(QColor(self.highlight_color))
+ self.cursorPositionChanged.connect(self.onCursorPositionChanged)
+ self.indenter = indenter(RopeEditorWrapper(self))
+ # True if you want to catch Emacs keys in actions
+ self.disable_shortcuts = False
+
+ self.prj = get_no_project()
+ self.prj.root = None
+ self.calltip = CallTip(self)
+ self.autocomplete = AutoComplete(self)
+
+
+ def closeEvent(self, event):
+ self.calltip.close()
+ self.autocomplete.close()
+
+
+ def isModified(self):
+ return self.document().isModified()
+
+
+ def setModified(self, flag):
+ self.document().setModified(flag)
+
+
+ def length(self):
+ return self.document().blockCount()
+
+
+ def goto(self, line_no):
+ cursor = self.textCursor()
+ block = cursor.block()
+ row = cursor.blockNumber()
+ while row > line_no:
+ block = block.previous()
+ row -= 1
+ while row < line_no:
+ block = block.next()
+ row += 1
+ cursor = QTextCursor(block)
+ self.setTextCursor(cursor)
+
+
+ def move_start_of_doc(self):
+ cursor = self.textCursor()
+ cursor.setPosition(0)
+ self.setTextCursor(cursor)
+
+
+ def move_end_of_doc(self):
+ cursor = self.textCursor()
+ block = cursor.block()
+ while block.isValid():
+ last_block = block
+ block = block.next()
+ cursor.setPosition(last_block.position())
+ cursor.movePosition(
+ QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
+ self.setTextCursor(cursor)
+
+
+ def move_start_of_row(self):
+ cursor = self.textCursor()
+ cursor.movePosition(
+ QTextCursor.StartOfBlock, QTextCursor.MoveAnchor)
+ self.setTextCursor(cursor)
+
+
+ def move_end_of_row(self):
+ cursor = self.textCursor()
+ cursor.movePosition(
+ QTextCursor.EndOfBlock, QTextCursor.MoveAnchor)
+ self.setTextCursor(cursor)
+
+
+ def highline(self, cursor):
+ self.viewport().update()
+
+
+ def onCursorPositionChanged(self):
+ cursor = self.textCursor()
+ row, col = cursor.blockNumber(), cursor.columnNumber()
+ if self.last_row != row:
+ self.last_row = row
+ if self.highlight_line:
+ self.highline(cursor)
+ if col != self.last_col:
+ self.last_col = col
+ self.cursorPositionChanged.emit(row, col)
+
+
+ def _create_line(self):
+ x = self.print_width
+ self.line = QLine(x, 0, x, self.height())
+
+
+ def resizeEvent(self, event):
+ self._create_line()
+ QPlainTextEdit.resizeEvent(self, event)
+
+
+ def paintEvent(self, event):
+ painter = QPainter(self.viewport())
+ if self.highlight_line:
+ r = self.cursorRect()
+ r.setX(0)
+ r.setWidth(self.viewport().width())
+ painter.fillRect(r, self.highlight_brush)
+ if self.draw_line:
+ painter.setPen(self.line_pen)
+ painter.drawLine(self.line)
+ painter.end()
+ QPlainTextEdit.paintEvent(self, event)
+
+
+ def setDocument(self, document):
+ QPlainTextEdit.setDocument(self, document)
+ self.highlighter.setDocument(document)
+
+
+ def indent(self):
+ self.indenter.correct_indentation(self.textCursor().blockNumber())
+
+
+ def tab_pressed(self):
+ self.indent()
+
+
+ def dedent(self):
+ self.indenter.deindent(self.textCursor().blockNumber())
+
+
+ def backtab_pressed(self):
+ self.dedent()
+ return True
+
+
+ def backspace_pressed(self):
+ cursor = self.textCursor()
+ text = unicode(cursor.block().text())
+ col = cursor.columnNumber()
+ if col > 0 and text[:col].strip() == "":
+ self.indenter.deindent(self.textCursor().blockNumber())
+ return True
+
+
+ def autocomplete_pressed(self):
+ try:
+ items = code_assist(self.prj,
+ unicode(self.toPlainText()),
+ self.textCursor().position())
+ except Exception as e:
+ items = []
+ if items:
+ self.autocomplete.setItems(items)
+ self.autocomplete.show()
+
+
+ def after_return_pressed(self):
+ self.indenter.entering_new_line(self.textCursor().blockNumber())
+
+
+ def keyPressEvent(self, event):
+ if self.autocomplete.active:
+ if self.autocomplete.keyPressEvent(event):
+ return
+ elif self.calltip.active:
+ if self.calltip.keyPressEvent(event):
+ return
+
+ m = event.modifiers()
+ k = event.key()
+ t = event.text()
+ # Disable some shortcuts
+ if self.disable_shortcuts and \
+ m & Qt.ControlModifier and k in [Qt.Key_A, Qt.Key_R,
+ Qt.Key_C, Qt.Key_K,
+ Qt.Key_X, Qt.Key_V,
+ Qt.Key_Y, Qt.Key_Z]:
+ new_ev = QKeyEvent(event.type(), k, m, t)
+ event.ignore()
+ QCoreApplication.postEvent(self.parent(), new_ev)
+ return
+ elif k == Qt.Key_Tab:
+ if self.tab_pressed():
+ return
+ elif k == Qt.Key_Backtab:
+ if self.backtab_pressed():
+ return
+ elif k == Qt.Key_Backspace:
+ if self.backspace_pressed():
+ return
+ elif k == Qt.Key_Period or \
+ (k == Qt.Key_Space and event.modifiers() == Qt.ControlModifier):
+ QPlainTextEdit.keyPressEvent(self, event)
+ self.autocomplete_pressed()
+ return
+ elif k in [Qt.Key_ParenLeft, Qt.Key_BraceLeft, Qt.Key_BracketLeft]:
+ QPlainTextEdit.keyPressEvent(self, event)
+ self.paren_opened(k)
+ return
+ QPlainTextEdit.keyPressEvent(self, event)
+ if k == Qt.Key_Return or k == Qt.Key_Enter:
+ self.after_return_pressed()
+
+
+
+ def paren_opened(self, key):
+ close_char = {
+ Qt.Key_ParenLeft: ")",
+ Qt.Key_BraceLeft:" }",
+ Qt.Key_BracketLeft:"]"
+ }
+ cursor = self.textCursor()
+ cursor.insertText(close_char[key])
+ cursor.setPosition(cursor.position()-1)
+ self.setTextCursor(cursor)
+
+
+
+class EditorSidebar(QWidget):
+
+
+ def __init__(self, editor):
+ QWidget.__init__(self, editor)
+ self.editor = editor
+ self.view = editor.view
+ self.doc = editor.view.document
+ self.fm = self.fontMetrics()
+ self.show_line_numbers = True
+
+ self.setAutoFillBackground(True)
+ #bg = editor.view.palette().base().color()
+ #pal = QPalette()
+ #pal.setColor(self.backgroundRole(), bg)
+ #self.setPalette(pal)
+ self.setBackgroundRole(QPalette.Base)
+
+ self.doc().documentLayout().update.connect(self.update)
+ self.view.verticalScrollBar().valueChanged.connect(self.update)
+ self.first_row = self.last_row = self.rows = 0
+ width = 10
+ if self.show_line_numbers:
+ width += self.fm.width("00000")
+ self.setFixedWidth(width)
+
+
+
+ def paintEvent(self, event):
+ QWidget.paintEvent(self, event)
+ p = QPainter(self)
+ view = self.view
+ first = view.firstVisibleBlock()
+ first_row = first.blockNumber()
+ block = first
+ row = first_row
+ y = view.contentOffset().y()
+ pageBottom = max(
+ view.height(),
+ view.verticalScrollBar().value() + view.viewport().height())
+ fm = self.fm
+ w = self.width() - 8
+ while block.isValid():
+ txt = str(row).rjust(5)
+ y = view.blockBoundingGeometry(block).y()
+ if y >= pageBottom:
+ break
+ x = w - fm.width(txt)
+ p.drawText(x, y, txt)
+ row += 1
+ block = block.next()
+ p.end()
+
+
+
+class EditorWidget(QFrame):
+
+
+ def __init__(self, parent=None, text=None,
+ EditorSidebarClass=EditorSidebar,
+ EditorViewClass=EditorView):
+ QFrame.__init__(self, parent)
+ self.view = EditorViewClass(self, text)
+ self.sidebar = EditorSidebarClass(self)
+ self.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken)
+ self.setLineWidth(2)
+ self.vlayout = QVBoxLayout()
+ self.vlayout.setSpacing(0)
+ self.setLayout(self.vlayout)
+ self.hlayout = QHBoxLayout()
+ self.vlayout.addLayout(self.hlayout)
+ self.hlayout.addWidget(self.sidebar)
+ self.hlayout.addWidget(self.view)
+ self.vlayout.setContentsMargins(2, 2, 2, 2)
+
+
+ def setPlainText(self, text):
+ self.view.document().setPlainText(text)
+ self.view.setModified(False)
+
+ def isModified(self):
+ return self.view.document().isModified()
+
+ def toPlainText(self):
+ return unicode(self.view.document().toPlainText())
+
+ def setModified(self, flag):
+ self.view.document().setModified(flag)
+
+class PythonEditorWidget(EditorWidget):
+ pass
+
+class QtQmlEditorWidget(QPlainTextEdit):
+
+ def __init__(self, parent):
+ QPlainTextEdit.__init__(self, parent)
+ self.highlighter = QtQmlHighlighter(self)
+
+class SaveDialog(QMessageBox):
+
+ def __init__(self, msg):
+ QMessageBox.__init__(self)
+ self.setWindowTitle("Save")
+ self.setText(msg)
+ self.setStandardButtons(QMessageBox.Save |QMessageBox.Discard | QMessageBox.Cancel)
+ self.setDefaultButton(QMessageBox.Save)
+
+
+if __name__ == "__main__":
+ if __file__ == "<stdin>": __file__ = "./widget.py"
+ import sys
+ app = QApplication(sys.argv)
+ src = open(__file__).read()
+ edit = EditorWidget(text=src)
+ edit.resize(640,480)
+ edit.show()
+ app.exec_()
diff --git a/plugins/extensions/pykrita/plugin/kritapykrita.json b/plugins/extensions/pykrita/plugin/kritapykrita.json
new file mode 100644
index 0000000000..9b13672f2a
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/kritapykrita.json
@@ -0,0 +1,9 @@
+{
+ "Id": "Python Plugin Manager",
+ "Type": "Service",
+ "X-KDE-Library": "kritapykrita",
+ "X-KDE-ServiceTypes": [
+ "Krita/ViewPlugin"
+ ],
+ "X-Krita-Version": "28"
+}
diff --git a/plugins/extensions/pykrita/plugin/manager.ui b/plugins/extensions/pykrita/plugin/manager.ui
new file mode 100644
index 0000000000..71e4cfec17
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/manager.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ManagerPage</class>
+ <widget class="QWidget" name="ManagerPage">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <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>
+ <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>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/extensions/pykrita/plugin/plugin.cpp b/plugins/extensions/pykrita/plugin/plugin.cpp
new file mode 100644
index 0000000000..0201a53601
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugin.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt (boud@valdyas.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "plugin.h"
+#include "engine.h"
+#include "utilities.h"
+
+#include <klocalizedstring.h>
+#include <kis_debug.h>
+#include <kpluginfactory.h>
+#include <KoResourcePaths.h>
+
+#include <kis_preference_set_registry.h>
+#include "pyqtpluginsettings.h"
+
+#include <Krita.h>
+#include <Extension.h>
+
+K_PLUGIN_FACTORY_WITH_JSON(KritaPyQtPluginFactory, "kritapykrita.json", registerPlugin<KritaPyQtPlugin>();)
+
+KritaPyQtPlugin::KritaPyQtPlugin(QObject *parent, const QVariantList &)
+ : KisViewPlugin(parent)
+ , m_engineFailureReason(m_engine.tryInitializeGetFailureReason())
+ , m_autoReload(false)
+{
+
+ qDebug() << "Loading Python plugin";
+
+ KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance();
+ PyQtPluginSettingsFactory* settingsFactory = new PyQtPluginSettingsFactory(&m_engine);
+
+ QByteArray pythonPath = qgetenv("PYTHONPATH");
+ qDebug() << "\tPython path:" << pythonPath;
+
+ QStringList pluginDirectories = KoResourcePaths::findDirs("pythonscripts");
+ qDebug() << "\tPython plugin directories" << pluginDirectories;
+ Q_FOREACH(const QString pluginDir, pluginDirectories) {
+ pythonPath.prepend(pluginDir.toUtf8() + ":");
+ }
+ qputenv("PYTHONPATH", pythonPath);
+
+
+ //load and save preferences
+ //if something in kritarc is missing, then the default from this load function will be used and saved back to kconfig.
+ //this way, cfg.readEntry() in any part won't be able to set its own default
+ KisPreferenceSet* settings = settingsFactory->createPreferenceSet();
+ Q_ASSERT(settings);
+ settings->loadPreferences();
+ settings->savePreferences();
+ delete settings;
+
+ preferenceSetRegistry->add("PyQtPluginSettingsFactory", settingsFactory);
+
+ // Try to import the `pykrita` module
+ PyKrita::Python py = PyKrita::Python();
+ PyObject* pykritaPackage = py.moduleImport("pykrita");
+ pykritaPackage = py.moduleImport("krita");
+
+ if (pykritaPackage) {
+ dbgScript << "Loaded pykrita, now load plugins";
+ m_engine.tryLoadEnabledPlugins();
+ //py.functionCall("_pykritaLoaded", PyKrita::Python::PYKRITA_ENGINE);
+ }
+ else {
+ dbgScript << "Cannot load pykrita module";
+ m_engine.setBroken();
+ }
+ Q_FOREACH (Extension* ext, Krita::instance()->extensions())
+ {
+ ext->setup();
+ }
+}
+
+KritaPyQtPlugin::~KritaPyQtPlugin()
+{
+
+}
+
+#include "plugin.moc"
diff --git a/plugins/extensions/pykrita/plugin/plugin.h b/plugins/extensions/pykrita/plugin/plugin.h
new file mode 100644
index 0000000000..36ff7d4ff9
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugin.h
@@ -0,0 +1,42 @@
+/*
+ * 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>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifndef _PYQT_PLUGIN_H_
+#define _PYQT_PLUGIN_H_
+
+#include <QObject>
+
+#include <kis_view_plugin.h>
+#include "engine.h"
+
+class KritaPyQtPlugin : public KisViewPlugin
+{
+ Q_OBJECT
+public:
+ KritaPyQtPlugin(QObject *parent, const QVariantList &);
+ virtual ~KritaPyQtPlugin();
+private:
+ PyKrita::Engine m_engine;
+ QString m_engineFailureReason;
+ bool m_autoReload;
+};
+
+#endif
diff --git a/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt
new file mode 100644
index 0000000000..a8bb913cdc
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/CMakeLists.txt
@@ -0,0 +1,93 @@
+# 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 "__pycache__*" EXCLUDE
+ )
+ # TODO Is there any way to form a long PATTERN options string
+ # and use it in a single install() call?
+ # NOTE Install specified patterns one-by-one...
+ foreach(_pattern ${install_pykrita_plugin_PATTERNS})
+ install(
+ DIRECTORY ${name}
+ DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
+ FILES_MATCHING
+ PATTERN "${_pattern}"
+ PATTERN "__pycache__*" EXCLUDE
+ )
+ endforeach()
+ else()
+ message(FATAL_ERROR "Do not know what to do with ${name}")
+ endif()
+endfunction()
+
+#install_pykrita_plugin(hello)
+install_pykrita_plugin(assignprofiledialog)
+install_pykrita_plugin(scripter)
+#install_pykrita_plugin(highpass)
+install_pykrita_plugin(tenbrushes)
+
+# if(PYTHON_VERSION_MAJOR VERSION_EQUAL 3)
+# install_pykrita_plugin(cmake_utils)
+# install_pykrita_plugin(js_utils PATTERNS "*.json")
+# install_pykrita_plugin(expand PATTERNS "*.expand" "templates/*.tpl")
+# endif()
+
+install(
+ DIRECTORY libkritapykrita
+ DESTINATION ${DATA_INSTALL_DIR}/krita/pykrita
+ FILES_MATCHING
+ PATTERN "*.py"
+ PATTERN "__pycache__*" EXCLUDE
+ )
diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/__init__.py b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/__init__.py
new file mode 100644
index 0000000000..68c3ce8b95
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/__init__.py
@@ -0,0 +1,2 @@
+# let's make a module
+from .assignprofiledialog import *
diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py
new file mode 100644
index 0000000000..c18777c99d
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/assignprofiledialog.py
@@ -0,0 +1,44 @@
+import sys
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from krita import *
+
+class AssignProfileDialog(Extension):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ def assignProfile(self):
+ doc = Application.activeDocument()
+ if doc == None:
+ QMessageBox.information(Application.activeWindow().qwindow(), "Assign Profile", "There is no active document.")
+ return
+
+ self.dialog = QDialog(Application.activeWindow().qwindow())
+
+ self.cmbProfile = QComboBox(self.dialog)
+ for profile in sorted(Application.profiles(doc.colorModel(), doc.colorDepth())):
+ self.cmbProfile.addItem(profile)
+
+ vbox = QVBoxLayout(self.dialog)
+ vbox.addWidget(self.cmbProfile)
+ self.buttonBox = QDialogButtonBox(self.dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.buttonBox.accepted.connect(self.dialog.accept)
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.dialog.reject)
+ vbox.addWidget(self.buttonBox)
+ self.dialog.show()
+ self.dialog.activateWindow()
+ self.dialog.exec_()
+
+ def accept(self):
+ doc = Application.activeDocument()
+ doc.setColorProfile(self.cmbProfile.currentText())
+
+ def setup(self):
+ action = Application.createAction("Assign Profile to Image")
+ action.triggered.connect(self.assignProfile)
+
+Scripter.addExtension(AssignProfileDialog(Application))
diff --git a/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
new file mode 100644
index 0000000000..945bd803a3
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/assignprofiledialog/kritapykrita_assignprofiledialog.desktop
@@ -0,0 +1,19 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=assignprofiledialog
+X-Python-2-Compatible=false
+Name=Assign Profile to Image
+Name[ca]=Assigna un perfil a una imatge
+Name[nl]=Profiel aan afbeelding toekennen
+Name[pt]=Atribuir um Perfil à Imagem
+Name[sv]=Tilldela profil till bild
+Name[uk]=Призначити профіль до зображення
+Name[x-test]=xxAssign Profile to Imagexx
+Comment=Assign a profile to an image without converting it.
+Comment[ca]=Assigna un perfil a una imatge sense convertir-la.
+Comment[nl]=Een profiel aan een afbeelding toekennen zonder het te converteren.
+Comment[pt]=Atribui um perfil à imagem sem a converter.
+Comment[sv]=Tilldela en profil till en bild utan att konvertera den.
+Comment[uk]=Призначити профіль до зображення без його перетворення.
+Comment[x-test]=xxAssign a profile to an image without converting it.xx
diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/__init__.py b/plugins/extensions/pykrita/plugin/plugins/hello/__init__.py
new file mode 100644
index 0000000000..de744cb395
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/hello/__init__.py
@@ -0,0 +1,2 @@
+# let's make a module
+from .hello import *
diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py
new file mode 100644
index 0000000000..9f1ef780bc
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py
@@ -0,0 +1,31 @@
+import sys
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from krita import *
+
+def hello():
+ QMessageBox.information(QWidget(), "Test", "Hello! This is Krita " + Application.version())
+
+class HelloExtension(Extension):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ def setup(self):
+ qDebug("Hello Setup")
+ action = Krita.instance().createAction("hello")
+ action.triggered.connect(hello)
+
+Scripter.addExtension(HelloExtension(Krita.instance()))
+
+class HelloDocker(DockWidget):
+ def __init__(self):
+ super().__init__()
+ label = QLabel("Hello", self)
+ self.setWidget(label)
+ self.label = label
+
+ def canvasChanged(self, canvas):
+ self.label.setText("Hellodocker: canvas changed");
+
+Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker))
diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop
new file mode 100644
index 0000000000..bfaa0f3779
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/hello/kritapykrita_hello.desktop
@@ -0,0 +1,19 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=hello
+X-Python-2-Compatible=false
+Name=Hello World
+Name[ca]=Hola món
+Name[nl]=Hallo wereld
+Name[pt]=Olá Mundo
+Name[sv]=Hello World
+Name[uk]=Привіт, світе
+Name[x-test]=xxHello Worldxx
+Comment=Basic plugin to test PyKrita
+Comment[ca]=Connector bàsic per provar el PyKrita
+Comment[nl]=Basisplug-in om PyKrita te testen
+Comment[pt]='Plugin' básico para testar o PyKrita
+Comment[sv]=Enkelt insticksprogram för att utprova PyKrita
+Comment[uk]=Базовий додаток для тестування PyKrita
+Comment[x-test]=xxBasic plugin to test PyKritaxx
diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/__init__.py b/plugins/extensions/pykrita/plugin/plugins/highpass/__init__.py
new file mode 100644
index 0000000000..f1ccabc909
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/highpass/__init__.py
@@ -0,0 +1,2 @@
+# let's make a module
+from .highpass import *
diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/high-pass.scm b/plugins/extensions/pykrita/plugin/plugins/highpass/high-pass.scm
new file mode 100644
index 0000000000..94f6c3c397
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/highpass/high-pass.scm
@@ -0,0 +1,176 @@
+;high-pass.scm
+; by Rob Antonishen
+; http://ffaat.pointclark.net
+
+; Version 1.2 (20120427)
+
+; Description
+;
+; This implements a highpass filter using the blur and invert method
+; parameters are blur radius, a preserve colour toggle, and whether to keep the original layer
+;
+; Changes:
+; v1.1 fixes "bug" created by bug fix for gimp 2.6.9 in gimp-histogram
+; v1.2 changed to a different layer blend mode (thanks Blacklemon67) that
+; has less histogram banding, by eliminating the need for the contrast boost.
+;
+
+; License:
+;
+; This program is free software; you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as published by
+; the Free Software Foundation; either version 2 of the License, or
+; (at your option) any later version.
+;
+; This program is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+; GNU General Public License for more details.
+;
+; The GNU Public License is available at
+; http://www.gnu.org/copyleft/gpl.html
+
+(define (high-pass img inLayer inRadius inMode inKeepOrig)
+ (let*
+ (
+ (blur-layer 0)
+ (blur-layer2 0)
+ (working-layer 0)
+ (colours-layer 0)
+ (colours-layer2 0)
+ (orig-name (car (gimp-drawable-get-name inLayer)))
+ )
+ ; it begins here
+ (gimp-context-push)
+ (gimp-image-undo-group-start img)
+
+ ;if keep original selected, make a working copy
+ (if (= inKeepOrig TRUE)
+ (begin
+ (set! working-layer (car (gimp-layer-copy inLayer FALSE)))
+ (gimp-image-add-layer img working-layer -1)
+ (gimp-drawable-set-name working-layer "working")
+ )
+ (set! working-layer inLayer) ;else
+ )
+
+ (gimp-image-set-active-layer img working-layer)
+
+ ;if keeping colours
+ (if (or (= inMode 1) (= inMode 3))
+ (begin
+ (set! colours-layer (car (gimp-layer-copy inLayer FALSE)))
+ (gimp-image-add-layer img colours-layer -1)
+ (gimp-image-lower-layer img colours-layer)
+ (gimp-drawable-set-name colours-layer "colours")
+ (gimp-image-set-active-layer img working-layer)
+ )
+ )
+
+ ;if Greyscale, desaturate
+ (if (or (= inMode 2) (= inMode 3))
+ (gimp-desaturate working-layer)
+ )
+
+ ;Duplicate on top and blur
+ (set! blur-layer (car (gimp-layer-copy working-layer FALSE)))
+ (gimp-image-add-layer img blur-layer -1)
+ (gimp-drawable-set-name blur-layer "blur")
+
+ ;blur
+ (plug-in-gauss 1 img blur-layer inRadius inRadius 1)
+
+ (if (<= inMode 3)
+ (begin
+ ;v1.2 using grain extract...
+ (gimp-layer-set-mode blur-layer GRAIN-EXTRACT-MODE)
+ (set! working-layer (car (gimp-image-merge-down img blur-layer 0)))
+
+ ; if preserve chroma, change set the mode to value and merge down with the layer we kept earlier.
+ (if (= inMode 3)
+ (begin
+ (gimp-layer-set-mode working-layer VALUE-MODE)
+ (set! working-layer (car (gimp-image-merge-down img working-layer 0)))
+ )
+ )
+
+ ; if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier.
+ (if (= inMode 1)
+ (begin
+ (gimp-context-set-foreground (list
+ (car (gimp-histogram colours-layer HISTOGRAM-RED 0 255))
+ (car (gimp-histogram colours-layer HISTOGRAM-GREEN 0 255))
+ (car (gimp-histogram colours-layer HISTOGRAM-BLUE 0 255)))
+ )
+ (gimp-drawable-fill colours-layer FOREGROUND-FILL)
+ (gimp-layer-set-mode working-layer OVERLAY-MODE)
+ (set! working-layer (car (gimp-image-merge-down img working-layer 0)))
+ )
+ )
+ )
+ (begin ;else 4=redrobes method
+
+ (gimp-image-set-active-layer img blur-layer) ;top layer
+
+ ;get the average colour of the input layer
+ (set! colours-layer (car (gimp-layer-copy inLayer FALSE)))
+ (gimp-image-add-layer img colours-layer -1)
+ (gimp-drawable-set-name colours-layer "colours")
+
+ (gimp-context-set-foreground (list
+ (car (gimp-histogram colours-layer HISTOGRAM-RED 0 255))
+ (car (gimp-histogram colours-layer HISTOGRAM-GREEN 0 255))
+ (car (gimp-histogram colours-layer HISTOGRAM-BLUE 0 255)))
+ )
+ (gimp-drawable-fill colours-layer FOREGROUND-FILL)
+ (gimp-image-set-active-layer img colours-layer)
+
+ ;copy the solid colour layer
+ (set! colours-layer2 (car (gimp-layer-copy colours-layer FALSE)))
+ (gimp-image-add-layer img colours-layer2 -1)
+ (gimp-layer-set-mode colours-layer SUBTRACT-MODE)
+ (gimp-image-set-active-layer img colours-layer2)
+
+ ;copy the blurred layer
+ (set! blur-layer2 (car (gimp-layer-copy blur-layer FALSE)))
+ (gimp-image-add-layer img blur-layer2 -1)
+ (gimp-layer-set-mode blur-layer2 SUBTRACT-MODE)
+
+ (set! blur-layer (car (gimp-image-merge-down img colours-layer 0)))
+ (set! blur-layer2 (car (gimp-image-merge-down img blur-layer2 0)))
+
+ (gimp-layer-set-mode blur-layer SUBTRACT-MODE)
+ (gimp-layer-set-mode blur-layer2 ADDITION-MODE)
+
+ (set! working-layer (car (gimp-image-merge-down img blur-layer 0)))
+ (set! working-layer (car (gimp-image-merge-down img blur-layer2 0)))
+
+ )
+ )
+
+ (if (= inKeepOrig TRUE)
+ (gimp-drawable-set-name working-layer (string-append orig-name " high pass"))
+ (gimp-drawable-set-name working-layer orig-name)
+ )
+
+ ;done
+ (gimp-progress-end)
+ (gimp-image-undo-group-end img)
+ (gimp-displays-flush)
+ (gimp-context-pop)
+ )
+)
+
+(script-fu-register "high-pass"
+ "<Image>/Filters/Generic/_High Pass Filter"
+ "Basic High Pass Filter."
+ "Rob Antonishen"
+ "Rob Antonishen"
+ "April 2012"
+ "RGB* GRAY*"
+ SF-IMAGE "image" 0
+ SF-DRAWABLE "drawable" 0
+ SF-ADJUSTMENT "Filter Radius" '(10 2 200 1 10 0 0)
+ SF-OPTION "Mode" '("Colour" "Preserve DC" "Greyscale" "Greyscale, Apply Chroma" "Redrobes")
+ SF-TOGGLE "Keep Original Layer?" TRUE
+)
\ No newline at end of file
diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py
new file mode 100644
index 0000000000..853cd8884c
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/highpass/highpass.py
@@ -0,0 +1,117 @@
+import sys
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from krita import *
+
+class HighpassExtension(Extension):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ def setup(self):
+ action = Application.createAction("High Pass")
+ action.triggered.connect(self.showDialog)
+
+ def showDialog(self):
+ doc = Application.activeDocument()
+ if doc == None:
+ QMessageBox.information(Application.activeWindow().qwindow(), "Highpass Filter", "There is no active image.")
+ return
+
+ self.dialog = QDialog(Application.activeWindow().qwindow())
+
+ self.intRadius = QSpinBox()
+ self.intRadius.setValue(10)
+ self.intRadius.setRange(2, 200)
+
+ self.cmbMode = QComboBox()
+ self.cmbMode.addItems(["Color", "Preserve DC", "Greyscale", "Greyscale, Apply Chroma", "Redrobes"])
+ self.keepOriginal = QCheckBox("Keep Original Layer");
+ self.keepOriginal.setChecked(True)
+ form = QFormLayout()
+ form.addRow("Filter Radius", self.intRadius)
+ form.addRow("Mode", self.cmbMode)
+ form.addRow("", self.keepOriginal)
+
+ self.buttonBox = QDialogButtonBox(self.dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.buttonBox.accepted.connect(self.dialog.accept)
+ self.buttonBox.accepted.connect(self.highpass)
+ self.buttonBox.rejected.connect(self.dialog.reject)
+
+ vbox = QVBoxLayout(self.dialog)
+ vbox.addLayout(form)
+ vbox.addWidget(self.buttonBox)
+
+ self.dialog.show()
+ self.dialog.activateWindow()
+ self.dialog.exec_()
+
+ def highpass(self):
+ # XXX: Start undo macro
+ image = Application.activeDocument()
+ original = image.activeNode()
+ working_layer = original
+
+ # We can only highpass on paint layers
+ if self.keepOriginal.isChecked() or original.type() != "paintlayer":
+ working_layer = image.createNode("working", "paintlayer")
+ working_layer.setColorSpace(original.colorModel(), original.colorSpace(), original.profile())
+ working_layer.writeBytes(original.readBytes(0, 0, image.width(), image.height()),
+ 0, 0, image.width(), image.height())
+ original.parentNode().addChildNode(working_layer, original) # XXX: Unimplemented
+
+ image.setActiveNode(working_layer)
+ colors_layer = None;
+
+ # if keeping colors
+ if self.cmbMode.currentIndex() == 1 or self.cmbMode.currentIndex() == 3:
+ colors_layer = working_layer.duplicate() # XXX: Unimplemented
+ colors_layer.setName("colors")
+ original.parentNode().addChildNode(working_layer, colors_layer) # XXX: Unimplemented
+
+ # if greyscale, desature
+ if (self.cmbMode.currentIndex() == 2 or self.cmbMode.currentIndex() == 3):
+ filter = Application.filter("desaturate")
+ filter.apply(working_layer, 0, 0, image.width(), image.height())
+
+ # Duplicate on top and blur
+ blur_layer = working_layer.duplicate()
+ blur_layer.setName("blur")
+ original.parentNode().addChildNode(blur_layer, working_layer) # XXX: Unimplemented
+
+ # blur
+ filter = Application.filter("gaussian blur")
+ filter_configuration = filter.configuration()
+ filter_configuration.setProperty("horizRadius", self.intRadius.value())
+ filter_configuration.setProperty("vertRadius", self.intRadius.value())
+ filter_configuration.setProperty("lockAspect", true)
+ filter.setConfiguration(filter_configuration)
+ filter.apply(blur_layer, 0, 0, image.width(), image.height())
+
+
+ if self.cmbMode.currentIndex() <= 3:
+ blur_layer.setBlendingMode("grain_extract")
+ working_layer = image.mergeDown(blur_layer)
+
+ # if preserve chroma, change set the mode to value and merge down with the layer we kept earlier.
+ if self.cmbMode.currentIndex() == 3:
+ working_layer.setBlendingMode("value")
+ working_layer = image.mergeDown(working_layer)
+
+ # if preserve DC, change set the mode to overlay and merge down with the average colour of the layer we kept earlier.
+ if self.cmbMode.currentIndex() == 1:
+ # get the average color of the entire image
+ # clear the colors layer to the given color
+ working_layer = image.mergeDown(working_layer)
+
+ else: # Mode == 4, RedRobes
+ image.setActiveNode(blur_layer)
+ # Get the average color of the input layer
+ # copy the solid colour layer
+ # copy the blurred layer
+ # XXX: End undo macro
+
+Scripter.addExtension(HighpassExtension(Krita.instance()))
+
diff --git a/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop
new file mode 100644
index 0000000000..20f59fd9c2
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/highpass/kritapykrita_highpass.desktop
@@ -0,0 +1,19 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=highpass
+X-Python-2-Compatible=false
+Name=Highpass Filter
+Name[ca]=Filtre passa-alt
+Name[nl]=Hoogdoorlaatfilter
+Name[pt]=Filtro Passa-Alto
+Name[sv]=Högpassfilter
+Name[uk]=Високочастотний фільтр
+Name[x-test]=xxHighpass Filterxx
+Comment=Highpass Filter, based on http://registry.gimp.org/node/7385
+Comment[ca]=Filtre passa-alt, basat en el http://registry.gimp.org/node/7385
+Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385
+Comment[pt]=Filtro passa-alto, baseado em http://registry.gimp.org/node/7385
+Comment[sv]=Högpassfilter, baserat på http://registry.gimp.org/node/7385
+Comment[uk]=Високочастотний фільтр, засновано на on http://registry.gimp.org/node/7385
+Comment[x-test]=xxHighpass Filter, based on http://registry.gimp.org/node/7385xx
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/__init__.py
new file mode 100644
index 0000000000..d892e73e9a
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/__init__.py
@@ -0,0 +1,2 @@
+# let's make a module
+from .scripter import *
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugcontroller.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugcontroller.py
new file mode 100644
index 0000000000..4d6e6f14df
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugcontroller.py
@@ -0,0 +1,82 @@
+from scripter.debugger_scripter import debugger
+import asyncio
+
+
+class DebugController (object):
+
+ def __init__(self, scripter):
+ self._debugger = None
+ self._cmd = None
+ self.scripter = scripter
+
+ def start(self, document):
+ self.setCmd(compile(document.data, document.filePath, "exec"))
+ self._debugger = debugger.Debugger(self.scripter, self._cmd)
+ self._debugger.debugprocess.start()
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(self._debugger.start())
+ self.updateUIDebugger()
+
+ def step(self):
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(self._debugger.step())
+ self.scripter.uicontroller.setStepped(True)
+ self.updateUIDebugger()
+
+ def stop(self):
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(self._debugger.stop())
+ self.updateUIDebugger()
+ self._debugger = None
+
+ def setCmd(self, cmd):
+ self._cmd = cmd
+
+ @property
+ def isActive(self):
+ try:
+ if self._debugger:
+ return self._debugger.debugprocess.is_alive()
+ return False
+ except:
+ return False
+
+ @property
+ def currentLine(self):
+ try:
+ if self._debugger:
+ return int(self.debuggerData['code']['lineNumber'])
+ except:
+ return 0
+
+ def updateUIDebugger(self):
+ widget = self.scripter.uicontroller.findTabWidget('Debugger')
+ exception = self._debuggerException()
+
+ if exception:
+ self.scripter.uicontroller.showException(exception)
+ if not self.isActive or self._quitDebugger():
+ widget.disableToolbar(True)
+
+ self.scripter.uicontroller.repaintDebugArea()
+ widget.updateWidget()
+
+ @property
+ def debuggerData(self):
+ try:
+ if self._debugger:
+ return self._debugger.application_data
+ except:
+ return
+
+ def _quitDebugger(self):
+ try:
+ return self.debuggerData['quit']
+ except:
+ return False
+
+ def _debuggerException(self):
+ try:
+ return self.debuggerData['exception']
+ except:
+ return False
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py
new file mode 100644
index 0000000000..017ca0a0f2
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debugger.py
@@ -0,0 +1,99 @@
+import bdb
+import multiprocessing
+import asyncio
+from . import debuggerformatter
+
+
+class Debugger(bdb.Bdb):
+
+ def __init__(self, scripter, cmd):
+ bdb.Bdb.__init__(self)
+
+ self.quit = False
+ self.debugq = multiprocessing.Queue()
+ self.scripter = scripter
+ self.applicationq = multiprocessing.Queue()
+ self.filePath = self.scripter.documentcontroller.activeDocument.filePath
+ self.application_data = {}
+ self.exception_data = {}
+ self.debugprocess = multiprocessing.Process(target=self._run, args=(self.filePath,))
+ self.currentLine = 0
+
+ bdb.Bdb.reset(self)
+
+ def _run(self, filename):
+ try:
+ self.mainpyfile = self.canonic(filename)
+ with open(filename, "rb") as fp:
+ statement = "exec(compile(%r, %r, 'exec'))" % \
+ (fp.read(), self.mainpyfile)
+ self.run(statement)
+ except Exception as e:
+ raise e
+
+ def user_call(self, frame, args):
+ name = frame.f_code.co_name or "<unknown>"
+
+ def user_line(self, frame):
+ """Handler that executes with every line of code"""
+ co = frame.f_code
+
+ if self.filePath!=co.co_filename:
+ return
+
+ self.currentLine = frame.f_lineno
+ self.applicationq.put({ "code": { "file": co.co_filename,
+ "name": co.co_name,
+ "lineNumber": str(frame.f_lineno)
+ },
+ "frame": { "firstLineNumber": co.co_firstlineno,
+ "locals": debuggerformatter.format_data(frame.f_locals),
+ "globals": debuggerformatter.format_data(frame.f_globals)
+ },
+ "trace": "line"
+ })
+
+ if self.quit:
+ return self.set_quit()
+ if self.currentLine==0:
+ return
+ else:
+ cmd = self.debugq.get()
+
+ if cmd == "step":
+ return
+ if cmd == "stop":
+ return self.set_quit()
+
+ def user_return(self, frame, value):
+ name = frame.f_code.co_name or "<unknown>"
+
+ if name == '<module>':
+ self.applicationq.put({ "quit": True})
+
+ def user_exception(self, frame, exception):
+ self.applicationq.put({ "exception": str(exception[1])})
+
+ async def display(self):
+ """Coroutine for updating the UI"""
+
+ while True:
+ if self.applicationq.empty():
+ await asyncio.sleep(0.3)
+ else:
+ while not self.applicationq.empty():
+ self.application_data.update(self.applicationq.get())
+ self.scripter.uicontroller.repaintDebugArea()
+ return
+
+ async def start(self):
+ await self.display()
+
+ async def step(self):
+ self.debugq.put("step")
+ await self.display()
+
+ async def stop(self):
+ self.debugq.put("stop")
+ self.applicationq.put({ "quit": True})
+ await self.display()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py
new file mode 100644
index 0000000000..ff3f6a3c93
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/debugger_scripter/debuggerformatter.py
@@ -0,0 +1,26 @@
+import re
+import inspect
+
+
+def format_data(data):
+ globals()['types'] = __import__('types')
+
+ exclude_keys = ['copyright', 'credits', 'False',
+ 'True', 'None', 'Ellipsis', 'quit',
+ 'QtCriticalMsg', 'krita_path',
+ 'QtWarningMsg', 'QWIDGETSIZE_MAX',
+ 'QtFatalMsg', 'PYQT_CONFIGURATION',
+ 'on_load', 'PYQT_VERSION', 'on_pykrita_unloading',
+ 'on_unload', 'QT_VERSION', 'QtInfoMsg',
+ 'PYQT_VERSION_STR', 'qApp', 'QtSystemMsg',
+ 'QtDebugMsg', 'on_pykrita_loaded', 'QT_VERSION_STR']
+ exclude_valuetypes = [types.BuiltinFunctionType,
+ types.BuiltinMethodType,
+ types.ModuleType,
+ types.FunctionType]
+
+ return [{k: {'value': str(v), 'type': str(type(v))}} for k, v in data.items() if not (k in exclude_keys or
+ type(v) in exclude_valuetypes or
+ re.search(r'^(__).*\1$', k) or
+ inspect.isclass(v) or
+ inspect.isfunction(v))]
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/document_scripter/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/document_scripter/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/document_scripter/document.py b/plugins/extensions/pykrita/plugin/plugins/scripter/document_scripter/document.py
new file mode 100644
index 0000000000..97c27fded4
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/document_scripter/document.py
@@ -0,0 +1,40 @@
+from PyQt5.QtCore import QFile, QIODevice, QTextStream
+
+
+class Document(object):
+
+ def __init__(self, filePath):
+ self._document = []
+ self._filePath = filePath
+
+ def open(self, filePath=''):
+ if filePath:
+ self._filePath = filePath
+
+ with open(self._filePath, 'r') as pythonFile:
+ self._document = pythonFile.read()
+
+ def save(self):
+ with open(self._filePath, 'w') as pythonFile:
+ print(self._document, file=pythonFile)
+
+ def compare(self, new_doc):
+ if len(self._document) != len(new_doc):
+ return False
+
+ if new_doc != self._document:
+ return False
+
+ return True
+
+ @property
+ def data(self):
+ return self._document
+
+ @data.setter
+ def data(self, data):
+ self._document = data
+
+ @property
+ def filePath(self):
+ return self._filePath
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/documentcontroller.py b/plugins/extensions/pykrita/plugin/plugins/scripter/documentcontroller.py
new file mode 100644
index 0000000000..1989bd5d24
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/documentcontroller.py
@@ -0,0 +1,33 @@
+from scripter.document_scripter import document
+
+
+class DocumentController(object):
+
+ def __init__(self):
+ self._activeDocument = None
+
+ @property
+ def activeDocument(self):
+ return self._activeDocument
+
+ def openDocument(self, filePath):
+ if filePath:
+ newDocument = document.Document(filePath)
+ newDocument.open()
+ self._activeDocument = newDocument
+ return newDocument
+
+ def saveDocument(self, data, filePath):
+ if not self._activeDocument:
+ self._activeDocument = document.Document(filePath)
+
+ text = str(data)
+
+ if not self._activeDocument.compare(text):
+ self._activeDocument.data = text
+ self._activeDocument.save()
+
+ return self._activeDocument
+
+ def clearActiveDocument(self):
+ self._activeDocument = None
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/icons/debug.svg b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/debug.svg
new file mode 100644
index 0000000000..78dcb2dd12
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/debug.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 412.76 412.76" style="enable-background:new 0 0 412.76 412.76;" xml:space="preserve">
+<g>
+ <path d="M376.493,310.998c-1.577-29.167-24.972-52.562-54.139-54.139v-21.72h-6.235v21.72 c-29.167,1.577-52.562,24.972-54.139,54.139h-21.465v6.235h21.465c1.577,29.167,24.972,52.568,54.139,54.145v21.715h6.235v-21.715 c29.167-1.576,52.562-24.978,54.139-54.145h26.141v-6.235H376.493z M316.119,365.143c-25.733-1.56-46.345-22.178-47.903-47.91 h47.903V365.143z M316.119,310.998h-47.903c1.559-25.732,22.17-46.345,47.903-47.903V310.998z M322.354,365.143v-47.91h47.903 C368.692,342.965,348.087,363.583,322.354,365.143z M322.354,310.998v-47.903c25.732,1.559,46.344,22.171,47.903,47.903H322.354z M291.336,90.016c-0.755-4.676,2.43-9.091,7.106-9.852c2.064-0.338,50.595-8.716,53.512-50.531 c0.322-4.677,4.365-8.257,9.151-7.974c4.737,0.335,8.312,4.445,7.989,9.17c-3.879,55.512-67.291,66.201-67.931,66.302 c-0.438,0.067-0.901,0.104-1.358,0.104C295.574,97.235,292.012,94.2,291.336,90.016z M32.383,30.83 c-0.329-4.725,3.249-8.835,7.971-9.164c4.874-0.344,8.841,3.291,9.17,7.968c2.92,41.82,51.441,50.199,53.512,50.537 c4.67,0.761,7.858,5.176,7.109,9.846c-0.679,4.184-4.241,7.225-8.473,7.225c-0.457,0-0.916-0.037-1.379-0.11 C99.668,97.031,36.255,86.341,32.383,30.83z M330.562,162.532c2.448,0.101,60.922,3.069,72.655,50.017 c0.555,2.229,0.214,4.536-0.962,6.503c-1.181,1.967-3.062,3.361-5.291,3.91c-0.67,0.182-1.382,0.268-2.083,0.268 c-3.945,0-7.367-2.68-8.33-6.504c-8.592-34.382-56.27-37.01-56.75-37.027c-2.284-0.095-4.403-1.078-5.956-2.768 c-1.553-1.69-2.356-3.885-2.259-6.181c0.195-4.612,3.939-8.229,8.531-8.229L330.562,162.532z M73.686,179.698 c-0.472,0.024-48.193,2.761-56.76,37.027c-0.959,3.824-4.381,6.504-8.33,6.504c-0.703,0-1.41-0.086-2.088-0.268 c-2.22-0.549-4.095-1.943-5.282-3.91c-1.179-1.967-1.52-4.274-0.965-6.503c11.734-46.947,70.208-49.916,72.692-50.022l0.359-0.006 c4.615,0,8.381,3.611,8.58,8.217C82.098,175.471,78.42,179.487,73.686,179.698z M160.925,49.106 c-0.219-1.708-0.332-3.428-0.332-5.133c0-22.289,18.14-40.429,40.429-40.429c22.293,0,40.426,18.14,40.426,40.429 c0,1.705-0.115,3.419-0.335,5.133c24.266,10.692,45.236,31.113,59.291,57.771l1.607,3.054l-3.069,1.589 c-26.348,13.707-55.752,20.658-87.392,20.658l0,0c-46.841,0-86.804-15.013-106.457-23.967l-3.398-1.553l1.827-3.261 C117.494,78.493,137.804,59.306,160.925,49.106z M116.005,298.235c1.148,4.592-1.653,9.262-6.241,10.412 c-0.441,0.122-46.098,12.398-52.008,53.037c-0.612,4.189-4.265,7.355-8.494,7.355c-0.411,0-0.828-0.024-1.245-0.098 c-4.683-0.676-7.946-5.048-7.265-9.736c7.618-52.349,62.502-66.646,64.834-67.23c0.679-0.17,1.388-0.256,2.095-0.256 C111.621,291.732,115.046,294.399,116.005,298.235z M319.236,222.145c-1.419,0-2.801,0.152-4.201,0.219 c2.643-11.971,4.11-24.527,4.11-37.503c0-22.94-4.287-44.88-12.732-65.215l-1.438-3.449l-3.318,1.717 c-21.896,11.317-57.305,17.881-83.147,20.247l-2.405,0.216l-0.603,2.338c-7.021,27.088-11.162,60.313-13.594,91.155 c-3.851-49.042-9.508-90.613-9.603-91.249l-0.429-2.947c-45.039-4.82-83.361-20.372-90.08-23.435l-3.157-1.44l-1.416,3.169 c-9.372,21.029-14.322,44.85-14.322,68.893c0,79.579,52.988,144.319,118.12,144.319c8.675,0,17.11-1.23,25.246-3.41 c5.054,46.838,44.804,83.445,92.963,83.445c51.569,0,93.529-41.96,93.529-93.529C412.76,264.112,370.812,222.145,319.236,222.145z M319.236,402.968c-48.135,0-87.294-39.153-87.294-87.294c0-48.129,39.159-87.294,87.294-87.294s87.294,39.165,87.294,87.294 C406.53,363.815,367.365,402.968,319.236,402.968z" fill="#FFFFFF"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/icons/debug_arrow.svg b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/debug_arrow.svg
new file mode 100644
index 0000000000..37c2c7b4b5
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/debug_arrow.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 268.832 268.832" style="enable-background:new 0 0 268.832 268.832;" xml:space="preserve">
+<g>
+ <path d="M265.171,125.577l-80-80c-4.881-4.881-12.797-4.881-17.678,0c-4.882,4.882-4.882,12.796,0,17.678l58.661,58.661H12.5 c-6.903,0-12.5,5.597-12.5,12.5c0,6.902,5.597,12.5,12.5,12.5h213.654l-58.659,58.661c-4.882,4.882-4.882,12.796,0,17.678 c2.44,2.439,5.64,3.661,8.839,3.661s6.398-1.222,8.839-3.661l79.998-80C270.053,138.373,270.053,130.459,265.171,125.577z" fill="#FFDA44"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/icons/run.svg b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/run.svg
new file mode 100644
index 0000000000..9390c6720b
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/run.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 41.999 41.999" style="enable-background:new 0 0 41.999 41.999;" xml:space="preserve" width="512px" height="512px">
+<path d="M36.068,20.176l-29-20C6.761-0.035,6.363-0.057,6.035,0.114C5.706,0.287,5.5,0.627,5.5,0.999v40 c0,0.372,0.206,0.713,0.535,0.886c0.146,0.076,0.306,0.114,0.465,0.114c0.199,0,0.397-0.06,0.568-0.177l29-20 c0.271-0.187,0.432-0.494,0.432-0.823S36.338,20.363,36.068,20.176z M7.5,39.095V2.904l26.239,18.096L7.5,39.095z" fill="#FFFFFF"/>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/icons/step.svg b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/step.svg
new file mode 100644
index 0000000000..fee979d9b1
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/step.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
+<path d="M9.488,3.907H24v2.791H9.488V3.907z M11.163,8.372H24v2.791H11.163V8.372z M11.163,12.837H24v2.791H11.163V12.837z
+ M9.488,17.302H24v2.791H9.488V17.302z M2.791,10.604c0,1.233,1,2.233,2.233,2.233H6.14v-2.233l3.767,3.628L6.14,17.86v-2.232H5.023
+ C2.249,15.628,0,13.379,0,10.604V8.93c0-2.774,2.249-5.023,5.023-5.023H6.14v2.791H5.023c-1.233,0-2.233,1-2.233,2.232V10.604z"/>
+</svg>
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/icons/stop.svg b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/stop.svg
new file mode 100644
index 0000000000..6e419fd202
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/icons/stop.svg
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 58 58" style="enable-background:new 0 0 58 58;" xml:space="preserve">
+<circle style="fill:#D75A4A;" cx="29" cy="29" r="29"/>
+<g>
+ <rect x="16" y="16" style="fill:#FFFFFF;" width="26" height="26"/>
+ <path style="fill:#FFFFFF;" d="M43,43H15V15h28V43z M17,41h24V17H17V41z"/>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop b/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop
new file mode 100644
index 0000000000..20c5b428c8
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/kritapykrita_scripter.desktop
@@ -0,0 +1,19 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=scripter
+X-Python-2-Compatible=false
+Name=Scripter
+Name[ca]=Scripter
+Name[nl]=Scriptschrijver
+Name[pt]=Programador
+Name[sv]=Skriptgenerator
+Name[uk]=Скриптер
+Name[x-test]=xxScripterxx
+Comment=Plugin to execute ad-hoc Python code
+Comment[ca]=Connector per executar codi Python ad-hoc
+Comment[nl]=Plug-in om ad-hoc Python code uit te voeren
+Comment[pt]='Plugin' para executar código em Python arbitrário
+Comment[sv]=Insticksprogram för att köra godtycklig Python-kod
+Comment[uk]=Додаток для виконання апріорного коду Python
+Comment[x-test]=xxPlugin to execute ad-hoc Python codexx
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py
new file mode 100644
index 0000000000..5436380a5b
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/resources_rc.py
@@ -0,0 +1,542 @@
+# -*- coding: utf-8 -*-
+
+# Resource object code
+#
+# Created by: The Resource Compiler for PyQt5 (Qt v5.8.0)
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x0f\xd9\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\
+\x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\
+\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\
+\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\
+\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\
+\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\
+\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\
+\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\
+\x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\
+\x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\
+\x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\
+\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\
+\x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\
+\x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\
+\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\
+\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\
+\x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\
+\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\
+\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\
+\x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\
+\x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
+\x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\
+\x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\
+\x36\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\
+\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\
+\x30\x20\x30\x20\x34\x31\x32\x2e\x37\x36\x20\x34\x31\x32\x2e\x37\
+\x36\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\
+\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\x3e\x0a\x09\x3c\
+\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x37\x36\x2e\x34\x39\x33\
+\x2c\x33\x31\x30\x2e\x39\x39\x38\x63\x2d\x31\x2e\x35\x37\x37\x2d\
+\x32\x39\x2e\x31\x36\x37\x2d\x32\x34\x2e\x39\x37\x32\x2d\x35\x32\
+\x2e\x35\x36\x32\x2d\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\
+\x33\x39\x76\x2d\x32\x31\x2e\x37\x32\x68\x2d\x36\x2e\x32\x33\x35\
+\x76\x32\x31\x2e\x37\x32\x20\x20\x20\x63\x2d\x32\x39\x2e\x31\x36\
+\x37\x2c\x31\x2e\x35\x37\x37\x2d\x35\x32\x2e\x35\x36\x32\x2c\x32\
+\x34\x2e\x39\x37\x32\x2d\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\
+\x31\x33\x39\x68\x2d\x32\x31\x2e\x34\x36\x35\x76\x36\x2e\x32\x33\
+\x35\x68\x32\x31\x2e\x34\x36\x35\x63\x31\x2e\x35\x37\x37\x2c\x32\
+\x39\x2e\x31\x36\x37\x2c\x32\x34\x2e\x39\x37\x32\x2c\x35\x32\x2e\
+\x35\x36\x38\x2c\x35\x34\x2e\x31\x33\x39\x2c\x35\x34\x2e\x31\x34\
+\x35\x76\x32\x31\x2e\x37\x31\x35\x68\x36\x2e\x32\x33\x35\x76\x2d\
+\x32\x31\x2e\x37\x31\x35\x20\x20\x20\x63\x32\x39\x2e\x31\x36\x37\
+\x2d\x31\x2e\x35\x37\x36\x2c\x35\x32\x2e\x35\x36\x32\x2d\x32\x34\
+\x2e\x39\x37\x38\x2c\x35\x34\x2e\x31\x33\x39\x2d\x35\x34\x2e\x31\
+\x34\x35\x68\x32\x36\x2e\x31\x34\x31\x76\x2d\x36\x2e\x32\x33\x35\
+\x48\x33\x37\x36\x2e\x34\x39\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\
+\x31\x39\x2c\x33\x36\x35\x2e\x31\x34\x33\x63\x2d\x32\x35\x2e\x37\
+\x33\x33\x2d\x31\x2e\x35\x36\x2d\x34\x36\x2e\x33\x34\x35\x2d\x32\
+\x32\x2e\x31\x37\x38\x2d\x34\x37\x2e\x39\x30\x33\x2d\x34\x37\x2e\
+\x39\x31\x20\x20\x20\x68\x34\x37\x2e\x39\x30\x33\x56\x33\x36\x35\
+\x2e\x31\x34\x33\x7a\x20\x4d\x33\x31\x36\x2e\x31\x31\x39\x2c\x33\
+\x31\x30\x2e\x39\x39\x38\x68\x2d\x34\x37\x2e\x39\x30\x33\x63\x31\
+\x2e\x35\x35\x39\x2d\x32\x35\x2e\x37\x33\x32\x2c\x32\x32\x2e\x31\
+\x37\x2d\x34\x36\x2e\x33\x34\x35\x2c\x34\x37\x2e\x39\x30\x33\x2d\
+\x34\x37\x2e\x39\x30\x33\x56\x33\x31\x30\x2e\x39\x39\x38\x7a\x20\
+\x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\x33\
+\x76\x2d\x34\x37\x2e\x39\x31\x68\x34\x37\x2e\x39\x30\x33\x20\x20\
+\x20\x43\x33\x36\x38\x2e\x36\x39\x32\x2c\x33\x34\x32\x2e\x39\x36\
+\x35\x2c\x33\x34\x38\x2e\x30\x38\x37\x2c\x33\x36\x33\x2e\x35\x38\
+\x33\x2c\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x36\x35\x2e\x31\x34\
+\x33\x7a\x20\x4d\x33\x32\x32\x2e\x33\x35\x34\x2c\x33\x31\x30\x2e\
+\x39\x39\x38\x76\x2d\x34\x37\x2e\x39\x30\x33\x63\x32\x35\x2e\x37\
+\x33\x32\x2c\x31\x2e\x35\x35\x39\x2c\x34\x36\x2e\x33\x34\x34\x2c\
+\x32\x32\x2e\x31\x37\x31\x2c\x34\x37\x2e\x39\x30\x33\x2c\x34\x37\
+\x2e\x39\x30\x33\x48\x33\x32\x32\x2e\x33\x35\x34\x7a\x20\x20\x20\
+\x20\x4d\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\x31\x36\
+\x63\x2d\x30\x2e\x37\x35\x35\x2d\x34\x2e\x36\x37\x36\x2c\x32\x2e\
+\x34\x33\x2d\x39\x2e\x30\x39\x31\x2c\x37\x2e\x31\x30\x36\x2d\x39\
+\x2e\x38\x35\x32\x63\x32\x2e\x30\x36\x34\x2d\x30\x2e\x33\x33\x38\
+\x2c\x35\x30\x2e\x35\x39\x35\x2d\x38\x2e\x37\x31\x36\x2c\x35\x33\
+\x2e\x35\x31\x32\x2d\x35\x30\x2e\x35\x33\x31\x20\x20\x20\x63\x30\
+\x2e\x33\x32\x32\x2d\x34\x2e\x36\x37\x37\x2c\x34\x2e\x33\x36\x35\
+\x2d\x38\x2e\x32\x35\x37\x2c\x39\x2e\x31\x35\x31\x2d\x37\x2e\x39\
+\x37\x34\x63\x34\x2e\x37\x33\x37\x2c\x30\x2e\x33\x33\x35\x2c\x38\
+\x2e\x33\x31\x32\x2c\x34\x2e\x34\x34\x35\x2c\x37\x2e\x39\x38\x39\
+\x2c\x39\x2e\x31\x37\x63\x2d\x33\x2e\x38\x37\x39\x2c\x35\x35\x2e\
+\x35\x31\x32\x2d\x36\x37\x2e\x32\x39\x31\x2c\x36\x36\x2e\x32\x30\
+\x31\x2d\x36\x37\x2e\x39\x33\x31\x2c\x36\x36\x2e\x33\x30\x32\x20\
+\x20\x20\x63\x2d\x30\x2e\x34\x33\x38\x2c\x30\x2e\x30\x36\x37\x2d\
+\x30\x2e\x39\x30\x31\x2c\x30\x2e\x31\x30\x34\x2d\x31\x2e\x33\x35\
+\x38\x2c\x30\x2e\x31\x30\x34\x43\x32\x39\x35\x2e\x35\x37\x34\x2c\
+\x39\x37\x2e\x32\x33\x35\x2c\x32\x39\x32\x2e\x30\x31\x32\x2c\x39\
+\x34\x2e\x32\x2c\x32\x39\x31\x2e\x33\x33\x36\x2c\x39\x30\x2e\x30\
+\x31\x36\x7a\x20\x4d\x33\x32\x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\
+\x33\x20\x20\x20\x63\x2d\x30\x2e\x33\x32\x39\x2d\x34\x2e\x37\x32\
+\x35\x2c\x33\x2e\x32\x34\x39\x2d\x38\x2e\x38\x33\x35\x2c\x37\x2e\
+\x39\x37\x31\x2d\x39\x2e\x31\x36\x34\x63\x34\x2e\x38\x37\x34\x2d\
+\x30\x2e\x33\x34\x34\x2c\x38\x2e\x38\x34\x31\x2c\x33\x2e\x32\x39\
+\x31\x2c\x39\x2e\x31\x37\x2c\x37\x2e\x39\x36\x38\x63\x32\x2e\x39\
+\x32\x2c\x34\x31\x2e\x38\x32\x2c\x35\x31\x2e\x34\x34\x31\x2c\x35\
+\x30\x2e\x31\x39\x39\x2c\x35\x33\x2e\x35\x31\x32\x2c\x35\x30\x2e\
+\x35\x33\x37\x20\x20\x20\x63\x34\x2e\x36\x37\x2c\x30\x2e\x37\x36\
+\x31\x2c\x37\x2e\x38\x35\x38\x2c\x35\x2e\x31\x37\x36\x2c\x37\x2e\
+\x31\x30\x39\x2c\x39\x2e\x38\x34\x36\x63\x2d\x30\x2e\x36\x37\x39\
+\x2c\x34\x2e\x31\x38\x34\x2d\x34\x2e\x32\x34\x31\x2c\x37\x2e\x32\
+\x32\x35\x2d\x38\x2e\x34\x37\x33\x2c\x37\x2e\x32\x32\x35\x63\x2d\
+\x30\x2e\x34\x35\x37\x2c\x30\x2d\x30\x2e\x39\x31\x36\x2d\x30\x2e\
+\x30\x33\x37\x2d\x31\x2e\x33\x37\x39\x2d\x30\x2e\x31\x31\x20\x20\
+\x20\x43\x39\x39\x2e\x36\x36\x38\x2c\x39\x37\x2e\x30\x33\x31\x2c\
+\x33\x36\x2e\x32\x35\x35\x2c\x38\x36\x2e\x33\x34\x31\x2c\x33\x32\
+\x2e\x33\x38\x33\x2c\x33\x30\x2e\x38\x33\x7a\x20\x4d\x33\x33\x30\
+\x2e\x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x63\x32\x2e\x34\
+\x34\x38\x2c\x30\x2e\x31\x30\x31\x2c\x36\x30\x2e\x39\x32\x32\x2c\
+\x33\x2e\x30\x36\x39\x2c\x37\x32\x2e\x36\x35\x35\x2c\x35\x30\x2e\
+\x30\x31\x37\x20\x20\x20\x63\x30\x2e\x35\x35\x35\x2c\x32\x2e\x32\
+\x32\x39\x2c\x30\x2e\x32\x31\x34\x2c\x34\x2e\x35\x33\x36\x2d\x30\
+\x2e\x39\x36\x32\x2c\x36\x2e\x35\x30\x33\x63\x2d\x31\x2e\x31\x38\
+\x31\x2c\x31\x2e\x39\x36\x37\x2d\x33\x2e\x30\x36\x32\x2c\x33\x2e\
+\x33\x36\x31\x2d\x35\x2e\x32\x39\x31\x2c\x33\x2e\x39\x31\x63\x2d\
+\x30\x2e\x36\x37\x2c\x30\x2e\x31\x38\x32\x2d\x31\x2e\x33\x38\x32\
+\x2c\x30\x2e\x32\x36\x38\x2d\x32\x2e\x30\x38\x33\x2c\x30\x2e\x32\
+\x36\x38\x20\x20\x20\x63\x2d\x33\x2e\x39\x34\x35\x2c\x30\x2d\x37\
+\x2e\x33\x36\x37\x2d\x32\x2e\x36\x38\x2d\x38\x2e\x33\x33\x2d\x36\
+\x2e\x35\x30\x34\x63\x2d\x38\x2e\x35\x39\x32\x2d\x33\x34\x2e\x33\
+\x38\x32\x2d\x35\x36\x2e\x32\x37\x2d\x33\x37\x2e\x30\x31\x2d\x35\
+\x36\x2e\x37\x35\x2d\x33\x37\x2e\x30\x32\x37\x63\x2d\x32\x2e\x32\
+\x38\x34\x2d\x30\x2e\x30\x39\x35\x2d\x34\x2e\x34\x30\x33\x2d\x31\
+\x2e\x30\x37\x38\x2d\x35\x2e\x39\x35\x36\x2d\x32\x2e\x37\x36\x38\
+\x20\x20\x20\x63\x2d\x31\x2e\x35\x35\x33\x2d\x31\x2e\x36\x39\x2d\
+\x32\x2e\x33\x35\x36\x2d\x33\x2e\x38\x38\x35\x2d\x32\x2e\x32\x35\
+\x39\x2d\x36\x2e\x31\x38\x31\x63\x30\x2e\x31\x39\x35\x2d\x34\x2e\
+\x36\x31\x32\x2c\x33\x2e\x39\x33\x39\x2d\x38\x2e\x32\x32\x39\x2c\
+\x38\x2e\x35\x33\x31\x2d\x38\x2e\x32\x32\x39\x4c\x33\x33\x30\x2e\
+\x35\x36\x32\x2c\x31\x36\x32\x2e\x35\x33\x32\x7a\x20\x4d\x37\x33\
+\x2e\x36\x38\x36\x2c\x31\x37\x39\x2e\x36\x39\x38\x20\x20\x20\x63\
+\x2d\x30\x2e\x34\x37\x32\x2c\x30\x2e\x30\x32\x34\x2d\x34\x38\x2e\
+\x31\x39\x33\x2c\x32\x2e\x37\x36\x31\x2d\x35\x36\x2e\x37\x36\x2c\
+\x33\x37\x2e\x30\x32\x37\x63\x2d\x30\x2e\x39\x35\x39\x2c\x33\x2e\
+\x38\x32\x34\x2d\x34\x2e\x33\x38\x31\x2c\x36\x2e\x35\x30\x34\x2d\
+\x38\x2e\x33\x33\x2c\x36\x2e\x35\x30\x34\x63\x2d\x30\x2e\x37\x30\
+\x33\x2c\x30\x2d\x31\x2e\x34\x31\x2d\x30\x2e\x30\x38\x36\x2d\x32\
+\x2e\x30\x38\x38\x2d\x30\x2e\x32\x36\x38\x20\x20\x20\x63\x2d\x32\
+\x2e\x32\x32\x2d\x30\x2e\x35\x34\x39\x2d\x34\x2e\x30\x39\x35\x2d\
+\x31\x2e\x39\x34\x33\x2d\x35\x2e\x32\x38\x32\x2d\x33\x2e\x39\x31\
+\x63\x2d\x31\x2e\x31\x37\x39\x2d\x31\x2e\x39\x36\x37\x2d\x31\x2e\
+\x35\x32\x2d\x34\x2e\x32\x37\x34\x2d\x30\x2e\x39\x36\x35\x2d\x36\
+\x2e\x35\x30\x33\x63\x31\x31\x2e\x37\x33\x34\x2d\x34\x36\x2e\x39\
+\x34\x37\x2c\x37\x30\x2e\x32\x30\x38\x2d\x34\x39\x2e\x39\x31\x36\
+\x2c\x37\x32\x2e\x36\x39\x32\x2d\x35\x30\x2e\x30\x32\x32\x6c\x30\
+\x2e\x33\x35\x39\x2d\x30\x2e\x30\x30\x36\x20\x20\x20\x63\x34\x2e\
+\x36\x31\x35\x2c\x30\x2c\x38\x2e\x33\x38\x31\x2c\x33\x2e\x36\x31\
+\x31\x2c\x38\x2e\x35\x38\x2c\x38\x2e\x32\x31\x37\x43\x38\x32\x2e\
+\x30\x39\x38\x2c\x31\x37\x35\x2e\x34\x37\x31\x2c\x37\x38\x2e\x34\
+\x32\x2c\x31\x37\x39\x2e\x34\x38\x37\x2c\x37\x33\x2e\x36\x38\x36\
+\x2c\x31\x37\x39\x2e\x36\x39\x38\x7a\x20\x4d\x31\x36\x30\x2e\x39\
+\x32\x35\x2c\x34\x39\x2e\x31\x30\x36\x20\x20\x20\x63\x2d\x30\x2e\
+\x32\x31\x39\x2d\x31\x2e\x37\x30\x38\x2d\x30\x2e\x33\x33\x32\x2d\
+\x33\x2e\x34\x32\x38\x2d\x30\x2e\x33\x33\x32\x2d\x35\x2e\x31\x33\
+\x33\x63\x30\x2d\x32\x32\x2e\x32\x38\x39\x2c\x31\x38\x2e\x31\x34\
+\x2d\x34\x30\x2e\x34\x32\x39\x2c\x34\x30\x2e\x34\x32\x39\x2d\x34\
+\x30\x2e\x34\x32\x39\x63\x32\x32\x2e\x32\x39\x33\x2c\x30\x2c\x34\
+\x30\x2e\x34\x32\x36\x2c\x31\x38\x2e\x31\x34\x2c\x34\x30\x2e\x34\
+\x32\x36\x2c\x34\x30\x2e\x34\x32\x39\x20\x20\x20\x63\x30\x2c\x31\
+\x2e\x37\x30\x35\x2d\x30\x2e\x31\x31\x35\x2c\x33\x2e\x34\x31\x39\
+\x2d\x30\x2e\x33\x33\x35\x2c\x35\x2e\x31\x33\x33\x63\x32\x34\x2e\
+\x32\x36\x36\x2c\x31\x30\x2e\x36\x39\x32\x2c\x34\x35\x2e\x32\x33\
+\x36\x2c\x33\x31\x2e\x31\x31\x33\x2c\x35\x39\x2e\x32\x39\x31\x2c\
+\x35\x37\x2e\x37\x37\x31\x6c\x31\x2e\x36\x30\x37\x2c\x33\x2e\x30\
+\x35\x34\x6c\x2d\x33\x2e\x30\x36\x39\x2c\x31\x2e\x35\x38\x39\x20\
+\x20\x20\x63\x2d\x32\x36\x2e\x33\x34\x38\x2c\x31\x33\x2e\x37\x30\
+\x37\x2d\x35\x35\x2e\x37\x35\x32\x2c\x32\x30\x2e\x36\x35\x38\x2d\
+\x38\x37\x2e\x33\x39\x32\x2c\x32\x30\x2e\x36\x35\x38\x6c\x30\x2c\
+\x30\x63\x2d\x34\x36\x2e\x38\x34\x31\x2c\x30\x2d\x38\x36\x2e\x38\
+\x30\x34\x2d\x31\x35\x2e\x30\x31\x33\x2d\x31\x30\x36\x2e\x34\x35\
+\x37\x2d\x32\x33\x2e\x39\x36\x37\x6c\x2d\x33\x2e\x33\x39\x38\x2d\
+\x31\x2e\x35\x35\x33\x6c\x31\x2e\x38\x32\x37\x2d\x33\x2e\x32\x36\
+\x31\x20\x20\x20\x43\x31\x31\x37\x2e\x34\x39\x34\x2c\x37\x38\x2e\
+\x34\x39\x33\x2c\x31\x33\x37\x2e\x38\x30\x34\x2c\x35\x39\x2e\x33\
+\x30\x36\x2c\x31\x36\x30\x2e\x39\x32\x35\x2c\x34\x39\x2e\x31\x30\
+\x36\x7a\x20\x4d\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\
+\x32\x33\x35\x63\x31\x2e\x31\x34\x38\x2c\x34\x2e\x35\x39\x32\x2d\
+\x31\x2e\x36\x35\x33\x2c\x39\x2e\x32\x36\x32\x2d\x36\x2e\x32\x34\
+\x31\x2c\x31\x30\x2e\x34\x31\x32\x20\x20\x20\x63\x2d\x30\x2e\x34\
+\x34\x31\x2c\x30\x2e\x31\x32\x32\x2d\x34\x36\x2e\x30\x39\x38\x2c\
+\x31\x32\x2e\x33\x39\x38\x2d\x35\x32\x2e\x30\x30\x38\x2c\x35\x33\
+\x2e\x30\x33\x37\x63\x2d\x30\x2e\x36\x31\x32\x2c\x34\x2e\x31\x38\
+\x39\x2d\x34\x2e\x32\x36\x35\x2c\x37\x2e\x33\x35\x35\x2d\x38\x2e\
+\x34\x39\x34\x2c\x37\x2e\x33\x35\x35\x63\x2d\x30\x2e\x34\x31\x31\
+\x2c\x30\x2d\x30\x2e\x38\x32\x38\x2d\x30\x2e\x30\x32\x34\x2d\x31\
+\x2e\x32\x34\x35\x2d\x30\x2e\x30\x39\x38\x20\x20\x20\x63\x2d\x34\
+\x2e\x36\x38\x33\x2d\x30\x2e\x36\x37\x36\x2d\x37\x2e\x39\x34\x36\
+\x2d\x35\x2e\x30\x34\x38\x2d\x37\x2e\x32\x36\x35\x2d\x39\x2e\x37\
+\x33\x36\x63\x37\x2e\x36\x31\x38\x2d\x35\x32\x2e\x33\x34\x39\x2c\
+\x36\x32\x2e\x35\x30\x32\x2d\x36\x36\x2e\x36\x34\x36\x2c\x36\x34\
+\x2e\x38\x33\x34\x2d\x36\x37\x2e\x32\x33\x63\x30\x2e\x36\x37\x39\
+\x2d\x30\x2e\x31\x37\x2c\x31\x2e\x33\x38\x38\x2d\x30\x2e\x32\x35\
+\x36\x2c\x32\x2e\x30\x39\x35\x2d\x30\x2e\x32\x35\x36\x20\x20\x20\
+\x43\x31\x31\x31\x2e\x36\x32\x31\x2c\x32\x39\x31\x2e\x37\x33\x32\
+\x2c\x31\x31\x35\x2e\x30\x34\x36\x2c\x32\x39\x34\x2e\x33\x39\x39\
+\x2c\x31\x31\x36\x2e\x30\x30\x35\x2c\x32\x39\x38\x2e\x32\x33\x35\
+\x7a\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\x31\
+\x34\x35\x63\x2d\x31\x2e\x34\x31\x39\x2c\x30\x2d\x32\x2e\x38\x30\
+\x31\x2c\x30\x2e\x31\x35\x32\x2d\x34\x2e\x32\x30\x31\x2c\x30\x2e\
+\x32\x31\x39\x20\x20\x20\x63\x32\x2e\x36\x34\x33\x2d\x31\x31\x2e\
+\x39\x37\x31\x2c\x34\x2e\x31\x31\x2d\x32\x34\x2e\x35\x32\x37\x2c\
+\x34\x2e\x31\x31\x2d\x33\x37\x2e\x35\x30\x33\x63\x30\x2d\x32\x32\
+\x2e\x39\x34\x2d\x34\x2e\x32\x38\x37\x2d\x34\x34\x2e\x38\x38\x2d\
+\x31\x32\x2e\x37\x33\x32\x2d\x36\x35\x2e\x32\x31\x35\x6c\x2d\x31\
+\x2e\x34\x33\x38\x2d\x33\x2e\x34\x34\x39\x6c\x2d\x33\x2e\x33\x31\
+\x38\x2c\x31\x2e\x37\x31\x37\x20\x20\x20\x63\x2d\x32\x31\x2e\x38\
+\x39\x36\x2c\x31\x31\x2e\x33\x31\x37\x2d\x35\x37\x2e\x33\x30\x35\
+\x2c\x31\x37\x2e\x38\x38\x31\x2d\x38\x33\x2e\x31\x34\x37\x2c\x32\
+\x30\x2e\x32\x34\x37\x6c\x2d\x32\x2e\x34\x30\x35\x2c\x30\x2e\x32\
+\x31\x36\x6c\x2d\x30\x2e\x36\x30\x33\x2c\x32\x2e\x33\x33\x38\x63\
+\x2d\x37\x2e\x30\x32\x31\x2c\x32\x37\x2e\x30\x38\x38\x2d\x31\x31\
+\x2e\x31\x36\x32\x2c\x36\x30\x2e\x33\x31\x33\x2d\x31\x33\x2e\x35\
+\x39\x34\x2c\x39\x31\x2e\x31\x35\x35\x20\x20\x20\x63\x2d\x33\x2e\
+\x38\x35\x31\x2d\x34\x39\x2e\x30\x34\x32\x2d\x39\x2e\x35\x30\x38\
+\x2d\x39\x30\x2e\x36\x31\x33\x2d\x39\x2e\x36\x30\x33\x2d\x39\x31\
+\x2e\x32\x34\x39\x6c\x2d\x30\x2e\x34\x32\x39\x2d\x32\x2e\x39\x34\
+\x37\x63\x2d\x34\x35\x2e\x30\x33\x39\x2d\x34\x2e\x38\x32\x2d\x38\
+\x33\x2e\x33\x36\x31\x2d\x32\x30\x2e\x33\x37\x32\x2d\x39\x30\x2e\
+\x30\x38\x2d\x32\x33\x2e\x34\x33\x35\x6c\x2d\x33\x2e\x31\x35\x37\
+\x2d\x31\x2e\x34\x34\x6c\x2d\x31\x2e\x34\x31\x36\x2c\x33\x2e\x31\
+\x36\x39\x20\x20\x20\x63\x2d\x39\x2e\x33\x37\x32\x2c\x32\x31\x2e\
+\x30\x32\x39\x2d\x31\x34\x2e\x33\x32\x32\x2c\x34\x34\x2e\x38\x35\
+\x2d\x31\x34\x2e\x33\x32\x32\x2c\x36\x38\x2e\x38\x39\x33\x63\x30\
+\x2c\x37\x39\x2e\x35\x37\x39\x2c\x35\x32\x2e\x39\x38\x38\x2c\x31\
+\x34\x34\x2e\x33\x31\x39\x2c\x31\x31\x38\x2e\x31\x32\x2c\x31\x34\
+\x34\x2e\x33\x31\x39\x63\x38\x2e\x36\x37\x35\x2c\x30\x2c\x31\x37\
+\x2e\x31\x31\x2d\x31\x2e\x32\x33\x2c\x32\x35\x2e\x32\x34\x36\x2d\
+\x33\x2e\x34\x31\x20\x20\x20\x63\x35\x2e\x30\x35\x34\x2c\x34\x36\
+\x2e\x38\x33\x38\x2c\x34\x34\x2e\x38\x30\x34\x2c\x38\x33\x2e\x34\
+\x34\x35\x2c\x39\x32\x2e\x39\x36\x33\x2c\x38\x33\x2e\x34\x34\x35\
+\x63\x35\x31\x2e\x35\x36\x39\x2c\x30\x2c\x39\x33\x2e\x35\x32\x39\
+\x2d\x34\x31\x2e\x39\x36\x2c\x39\x33\x2e\x35\x32\x39\x2d\x39\x33\
+\x2e\x35\x32\x39\x43\x34\x31\x32\x2e\x37\x36\x2c\x32\x36\x34\x2e\
+\x31\x31\x32\x2c\x33\x37\x30\x2e\x38\x31\x32\x2c\x32\x32\x32\x2e\
+\x31\x34\x35\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x32\x32\x32\x2e\
+\x31\x34\x35\x7a\x20\x20\x20\x20\x4d\x33\x31\x39\x2e\x32\x33\x36\
+\x2c\x34\x30\x32\x2e\x39\x36\x38\x63\x2d\x34\x38\x2e\x31\x33\x35\
+\x2c\x30\x2d\x38\x37\x2e\x32\x39\x34\x2d\x33\x39\x2e\x31\x35\x33\
+\x2d\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\x32\x39\x34\x63\x30\
+\x2d\x34\x38\x2e\x31\x32\x39\x2c\x33\x39\x2e\x31\x35\x39\x2d\x38\
+\x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x2d\x38\x37\x2e\
+\x32\x39\x34\x73\x38\x37\x2e\x32\x39\x34\x2c\x33\x39\x2e\x31\x36\
+\x35\x2c\x38\x37\x2e\x32\x39\x34\x2c\x38\x37\x2e\x32\x39\x34\x20\
+\x20\x20\x43\x34\x30\x36\x2e\x35\x33\x2c\x33\x36\x33\x2e\x38\x31\
+\x35\x2c\x33\x36\x37\x2e\x33\x36\x35\x2c\x34\x30\x32\x2e\x39\x36\
+\x38\x2c\x33\x31\x39\x2e\x32\x33\x36\x2c\x34\x30\x32\x2e\x39\x36\
+\x38\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x46\x46\x46\x46\
+\x46\x22\x2f\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\
+\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\
+\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\
+\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\
+\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\
+\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\
+\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\
+\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\
+\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\
+\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
+\x00\x00\x03\x68\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\
+\x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\
+\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\
+\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\x2e\
+\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\
+\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\
+\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\
+\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x78\
+\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
+\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
+\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\
+\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\
+\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x76\x65\x72\
+\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\x3d\x22\x43\
+\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\x22\x20\x79\
+\x3d\x22\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\
+\x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\
+\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\
+\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\
+\x30\x20\x30\x20\x34\x31\x2e\x39\x39\x39\x20\x34\x31\x2e\x39\x39\
+\x39\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\
+\x72\x65\x73\x65\x72\x76\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\
+\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\
+\x31\x32\x70\x78\x22\x3e\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\
+\x4d\x33\x36\x2e\x30\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x6c\x2d\
+\x32\x39\x2d\x32\x30\x43\x36\x2e\x37\x36\x31\x2d\x30\x2e\x30\x33\
+\x35\x2c\x36\x2e\x33\x36\x33\x2d\x30\x2e\x30\x35\x37\x2c\x36\x2e\
+\x30\x33\x35\x2c\x30\x2e\x31\x31\x34\x43\x35\x2e\x37\x30\x36\x2c\
+\x30\x2e\x32\x38\x37\x2c\x35\x2e\x35\x2c\x30\x2e\x36\x32\x37\x2c\
+\x35\x2e\x35\x2c\x30\x2e\x39\x39\x39\x76\x34\x30\x20\x20\x63\x30\
+\x2c\x30\x2e\x33\x37\x32\x2c\x30\x2e\x32\x30\x36\x2c\x30\x2e\x37\
+\x31\x33\x2c\x30\x2e\x35\x33\x35\x2c\x30\x2e\x38\x38\x36\x63\x30\
+\x2e\x31\x34\x36\x2c\x30\x2e\x30\x37\x36\x2c\x30\x2e\x33\x30\x36\
+\x2c\x30\x2e\x31\x31\x34\x2c\x30\x2e\x34\x36\x35\x2c\x30\x2e\x31\
+\x31\x34\x63\x30\x2e\x31\x39\x39\x2c\x30\x2c\x30\x2e\x33\x39\x37\
+\x2d\x30\x2e\x30\x36\x2c\x30\x2e\x35\x36\x38\x2d\x30\x2e\x31\x37\
+\x37\x6c\x32\x39\x2d\x32\x30\x20\x20\x63\x30\x2e\x32\x37\x31\x2d\
+\x30\x2e\x31\x38\x37\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x34\x39\
+\x34\x2c\x30\x2e\x34\x33\x32\x2d\x30\x2e\x38\x32\x33\x53\x33\x36\
+\x2e\x33\x33\x38\x2c\x32\x30\x2e\x33\x36\x33\x2c\x33\x36\x2e\x30\
+\x36\x38\x2c\x32\x30\x2e\x31\x37\x36\x7a\x20\x4d\x37\x2e\x35\x2c\
+\x33\x39\x2e\x30\x39\x35\x56\x32\x2e\x39\x30\x34\x6c\x32\x36\x2e\
+\x32\x33\x39\x2c\x31\x38\x2e\x30\x39\x36\x4c\x37\x2e\x35\x2c\x33\
+\x39\x2e\x30\x39\x35\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\
+\x46\x46\x46\x46\x46\x22\x2f\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\
+\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\
+\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\
+\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\
+\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\
+\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\
+\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\
+\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\
+\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\
+\x3c\x2f\x73\x76\x67\x3e\x0a\
+\x00\x00\x02\xe2\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\
+\x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\
+\x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\
+\x65\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x39\
+\x2e\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\
+\x20\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\
+\x65\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\
+\x6c\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\
+\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\
+\x64\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\
+\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\
+\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\
+\x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x76\x69\x65\x77\
+\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x35\x38\x20\x35\x38\x22\x20\
+\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\x6c\x65\x2d\x62\x61\
+\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\x77\x20\x30\x20\x30\
+\x20\x35\x38\x20\x35\x38\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\
+\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0d\x0a\
+\x3c\x63\x69\x72\x63\x6c\x65\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\
+\x69\x6c\x6c\x3a\x23\x44\x37\x35\x41\x34\x41\x3b\x22\x20\x63\x78\
+\x3d\x22\x32\x39\x22\x20\x63\x79\x3d\x22\x32\x39\x22\x20\x72\x3d\
+\x22\x32\x39\x22\x2f\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x09\x3c\x72\
+\x65\x63\x74\x20\x78\x3d\x22\x31\x36\x22\x20\x79\x3d\x22\x31\x36\
+\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x23\x46\
+\x46\x46\x46\x46\x46\x3b\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\
+\x36\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x36\x22\x2f\x3e\
+\x0d\x0a\x09\x3c\x70\x61\x74\x68\x20\x73\x74\x79\x6c\x65\x3d\x22\
+\x66\x69\x6c\x6c\x3a\x23\x46\x46\x46\x46\x46\x46\x3b\x22\x20\x64\
+\x3d\x22\x4d\x34\x33\x2c\x34\x33\x48\x31\x35\x56\x31\x35\x68\x32\
+\x38\x56\x34\x33\x7a\x20\x4d\x31\x37\x2c\x34\x31\x68\x32\x34\x56\
+\x31\x37\x48\x31\x37\x56\x34\x31\x7a\x22\x2f\x3e\x0d\x0a\x3c\x2f\
+\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\
+\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\
+\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\
+\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\
+\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\
+\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\
+\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\
+\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\
+\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\
+\x3e\x0d\x0a\x3c\x67\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x67\
+\x3e\x0d\x0a\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\
+\x0a\
+\x00\x00\x04\x03\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x69\x73\x6f\
+\x2d\x38\x38\x35\x39\x2d\x31\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\
+\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\
+\x20\x49\x6c\x6c\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\
+\x30\x2e\x30\x2c\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\
+\x50\x6c\x75\x67\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\
+\x72\x73\x69\x6f\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\
+\x64\x20\x30\x29\x20\x20\x2d\x2d\x3e\x0a\x3c\x21\x44\x4f\x43\x54\
+\x59\x50\x45\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\
+\x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\
+\x31\x2e\x31\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\
+\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\
+\x68\x69\x63\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\
+\x2f\x73\x76\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x73\x76\
+\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\
+\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\
+\x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\
+\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\
+\x3d\x22\x43\x61\x70\x61\x5f\x31\x22\x20\x78\x3d\x22\x30\x70\x78\
+\x22\x20\x79\x3d\x22\x30\x70\x78\x22\x20\x77\x69\x64\x74\x68\x3d\
+\x22\x35\x31\x32\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\
+\x35\x31\x32\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\
+\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\x38\x2e\
+\x38\x33\x32\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x65\x6e\x61\x62\
+\x6c\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x6e\x65\
+\x77\x20\x30\x20\x30\x20\x32\x36\x38\x2e\x38\x33\x32\x20\x32\x36\
+\x38\x2e\x38\x33\x32\x3b\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\x63\
+\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x3c\x67\
+\x3e\x0a\x09\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x36\x35\
+\x2e\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x6c\x2d\x38\x30\
+\x2d\x38\x30\x63\x2d\x34\x2e\x38\x38\x31\x2d\x34\x2e\x38\x38\x31\
+\x2d\x31\x32\x2e\x37\x39\x37\x2d\x34\x2e\x38\x38\x31\x2d\x31\x37\
+\x2e\x36\x37\x38\x2c\x30\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\
+\x38\x38\x32\x2d\x34\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\
+\x2c\x30\x2c\x31\x37\x2e\x36\x37\x38\x6c\x35\x38\x2e\x36\x36\x31\
+\x2c\x35\x38\x2e\x36\x36\x31\x48\x31\x32\x2e\x35\x20\x20\x20\x63\
+\x2d\x36\x2e\x39\x30\x33\x2c\x30\x2d\x31\x32\x2e\x35\x2c\x35\x2e\
+\x35\x39\x37\x2d\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x63\x30\x2c\
+\x36\x2e\x39\x30\x32\x2c\x35\x2e\x35\x39\x37\x2c\x31\x32\x2e\x35\
+\x2c\x31\x32\x2e\x35\x2c\x31\x32\x2e\x35\x68\x32\x31\x33\x2e\x36\
+\x35\x34\x6c\x2d\x35\x38\x2e\x36\x35\x39\x2c\x35\x38\x2e\x36\x36\
+\x31\x63\x2d\x34\x2e\x38\x38\x32\x2c\x34\x2e\x38\x38\x32\x2d\x34\
+\x2e\x38\x38\x32\x2c\x31\x32\x2e\x37\x39\x36\x2c\x30\x2c\x31\x37\
+\x2e\x36\x37\x38\x20\x20\x20\x63\x32\x2e\x34\x34\x2c\x32\x2e\x34\
+\x33\x39\x2c\x35\x2e\x36\x34\x2c\x33\x2e\x36\x36\x31\x2c\x38\x2e\
+\x38\x33\x39\x2c\x33\x2e\x36\x36\x31\x73\x36\x2e\x33\x39\x38\x2d\
+\x31\x2e\x32\x32\x32\x2c\x38\x2e\x38\x33\x39\x2d\x33\x2e\x36\x36\
+\x31\x6c\x37\x39\x2e\x39\x39\x38\x2d\x38\x30\x43\x32\x37\x30\x2e\
+\x30\x35\x33\x2c\x31\x33\x38\x2e\x33\x37\x33\x2c\x32\x37\x30\x2e\
+\x30\x35\x33\x2c\x31\x33\x30\x2e\x34\x35\x39\x2c\x32\x36\x35\x2e\
+\x31\x37\x31\x2c\x31\x32\x35\x2e\x35\x37\x37\x7a\x22\x20\x66\x69\
+\x6c\x6c\x3d\x22\x23\x46\x46\x44\x41\x34\x34\x22\x2f\x3e\x0a\x3c\
+\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\
+\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\
+\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\
+\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\
+\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\
+\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\
+\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\
+\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\
+\x3e\x0a\x3c\x67\x3e\x0a\x3c\x2f\x67\x3e\x0a\x3c\x2f\x73\x76\x67\
+\x3e\x0a\
+\x00\x00\x03\x58\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
+\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\x47\x65\x6e\x65\
+\x72\x61\x74\x6f\x72\x3a\x20\x41\x64\x6f\x62\x65\x20\x49\x6c\x6c\
+\x75\x73\x74\x72\x61\x74\x6f\x72\x20\x31\x36\x2e\x30\x2e\x30\x2c\
+\x20\x53\x56\x47\x20\x45\x78\x70\x6f\x72\x74\x20\x50\x6c\x75\x67\
+\x2d\x49\x6e\x20\x2e\x20\x53\x56\x47\x20\x56\x65\x72\x73\x69\x6f\
+\x6e\x3a\x20\x36\x2e\x30\x30\x20\x42\x75\x69\x6c\x64\x20\x30\x29\
+\x20\x20\x2d\x2d\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\
+\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\
+\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\
+\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\
+\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\
+\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\
+\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x73\x76\x67\x20\
+\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x69\x64\
+\x3d\x22\x4c\x61\x79\x65\x72\x5f\x31\x22\x20\x78\x6d\x6c\x6e\x73\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\
+\x6c\x6e\x73\x3a\x78\x6c\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\
+\x39\x2f\x78\x6c\x69\x6e\x6b\x22\x20\x78\x3d\x22\x30\x70\x78\x22\
+\x20\x79\x3d\x22\x30\x70\x78\x22\x0d\x0a\x09\x20\x77\x69\x64\x74\
+\x68\x3d\x22\x32\x34\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\
+\x22\x32\x34\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\
+\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x65\x6e\x61\x62\x6c\
+\x65\x2d\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3d\x22\x6e\x65\
+\x77\x20\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x78\x6d\x6c\
+\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\
+\x22\x3e\x0d\x0a\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x39\x2e\
+\x34\x38\x38\x2c\x33\x2e\x39\x30\x37\x48\x32\x34\x76\x32\x2e\x37\
+\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x33\x2e\x39\x30\x37\x7a\x20\
+\x4d\x31\x31\x2e\x31\x36\x33\x2c\x38\x2e\x33\x37\x32\x48\x32\x34\
+\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\x31\x36\x33\x56\x38\x2e\
+\x33\x37\x32\x7a\x20\x4d\x31\x31\x2e\x31\x36\x33\x2c\x31\x32\x2e\
+\x38\x33\x37\x48\x32\x34\x76\x32\x2e\x37\x39\x31\x48\x31\x31\x2e\
+\x31\x36\x33\x56\x31\x32\x2e\x38\x33\x37\x7a\x0d\x0a\x09\x20\x4d\
+\x39\x2e\x34\x38\x38\x2c\x31\x37\x2e\x33\x30\x32\x48\x32\x34\x76\
+\x32\x2e\x37\x39\x31\x48\x39\x2e\x34\x38\x38\x56\x31\x37\x2e\x33\
+\x30\x32\x7a\x20\x4d\x32\x2e\x37\x39\x31\x2c\x31\x30\x2e\x36\x30\
+\x34\x63\x30\x2c\x31\x2e\x32\x33\x33\x2c\x31\x2c\x32\x2e\x32\x33\
+\x33\x2c\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\x33\x33\x48\x36\x2e\
+\x31\x34\x76\x2d\x32\x2e\x32\x33\x33\x6c\x33\x2e\x37\x36\x37\x2c\
+\x33\x2e\x36\x32\x38\x4c\x36\x2e\x31\x34\x2c\x31\x37\x2e\x38\x36\
+\x76\x2d\x32\x2e\x32\x33\x32\x48\x35\x2e\x30\x32\x33\x0d\x0a\x09\
+\x43\x32\x2e\x32\x34\x39\x2c\x31\x35\x2e\x36\x32\x38\x2c\x30\x2c\
+\x31\x33\x2e\x33\x37\x39\x2c\x30\x2c\x31\x30\x2e\x36\x30\x34\x56\
+\x38\x2e\x39\x33\x63\x30\x2d\x32\x2e\x37\x37\x34\x2c\x32\x2e\x32\
+\x34\x39\x2d\x35\x2e\x30\x32\x33\x2c\x35\x2e\x30\x32\x33\x2d\x35\
+\x2e\x30\x32\x33\x48\x36\x2e\x31\x34\x76\x32\x2e\x37\x39\x31\x48\
+\x35\x2e\x30\x32\x33\x63\x2d\x31\x2e\x32\x33\x33\x2c\x30\x2d\x32\
+\x2e\x32\x33\x33\x2c\x31\x2d\x32\x2e\x32\x33\x33\x2c\x32\x2e\x32\
+\x33\x32\x56\x31\x30\x2e\x36\x30\x34\x7a\x22\x2f\x3e\x0d\x0a\x3c\
+\x2f\x73\x76\x67\x3e\x0d\x0a\
+"
+
+qt_resource_name = b"\
+\x00\x05\
+\x00\x6f\xa6\x53\
+\x00\x69\
+\x00\x63\x00\x6f\x00\x6e\x00\x73\
+\x00\x09\
+\x09\xba\x8f\xa7\
+\x00\x64\
+\x00\x65\x00\x62\x00\x75\x00\x67\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x07\
+\x09\xc1\x5a\x27\
+\x00\x72\
+\x00\x75\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x08\
+\x0b\x63\x55\x87\
+\x00\x73\
+\x00\x74\x00\x6f\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0f\
+\x0a\x15\x41\x07\
+\x00\x64\
+\x00\x65\x00\x62\x00\x75\x00\x67\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x08\
+\x0a\xc3\x55\x87\
+\x00\x73\
+\x00\x74\x00\x65\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\
+"
+
+qt_resource_struct = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x02\
+\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x0f\xdd\
+\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x16\x2f\
+\x00\x00\x00\x76\x00\x00\x00\x00\x00\x01\x00\x00\x1a\x36\
+\x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x13\x49\
+"
+
+def qInitResources():
+ QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
+
+qInitResources()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/scripter.py b/plugins/extensions/pykrita/plugin/plugins/scripter/scripter.py
new file mode 100644
index 0000000000..e0b6f6b6cf
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/scripter.py
@@ -0,0 +1,25 @@
+from PyQt5.QtWidgets import QDialog
+from PyQt5.QtCore import QSettings, QStandardPaths
+from krita import *
+from scripter import uicontroller, documentcontroller, debugcontroller
+
+
+class ScripterExtension(Extension):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+
+ def setup(self):
+ action = Krita.instance().createAction("Scripter")
+ action.triggered.connect(self.initialize)
+
+ def initialize(self):
+ configPath = QStandardPaths.writableLocation(QStandardPaths.GenericConfigLocation)
+ self.settings = QSettings(configPath + '/kritarc', QSettings.IniFormat)
+ self.uicontroller = uicontroller.UIController()
+ self.documentcontroller = documentcontroller.DocumentController()
+ self.debugcontroller = debugcontroller.DebugController(self)
+ self.uicontroller.initialize(self)
+
+
+Krita.instance().addExtension(ScripterExtension(Krita.instance()))
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/scripter.qrc b/plugins/extensions/pykrita/plugin/plugins/scripter/scripter.qrc
new file mode 100644
index 0000000000..2266176553
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/scripter.qrc
@@ -0,0 +1,9 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/icons">
+ <file alias="debug.svg">icons/debug.svg</file>
+ <file alias="debug_arrow.svg">icons/debug_arrow.svg</file>
+ <file alias="run.svg">icons/run.svg</file>
+ <file alias="step.svg">icons/step.svg</file>
+ <file alias="stop.svg">icons/stop.svg</file>
+</qresource>
+</RCC>
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/scripterdialog.py b/plugins/extensions/pykrita/plugin/plugins/scripter/scripterdialog.py
new file mode 100644
index 0000000000..b467cfd548
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/scripterdialog.py
@@ -0,0 +1,14 @@
+from PyQt5.QtWidgets import QDialog
+
+
+class ScripterDialog(QDialog):
+
+ def __init__(self, uicontroller, parent=None):
+ super(ScripterDialog, self).__init__(parent)
+
+ self.uicontroller = uicontroller
+
+ def closeEvent(self, event):
+ self.uicontroller._writeSettings()
+ self.uicontroller._saveSettings()
+ event.accept()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/test.py b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py
new file mode 100644
index 0000000000..7ec3b3bcf3
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/test.py
@@ -0,0 +1,24 @@
+# editor.py
+
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+
+import syntax
+
+app = QApplication([])
+editor = QPlainTextEdit()
+f = QFont("monospace", 10, QFont.Normal)
+f.setFixedPitch(True)
+editor.document().setDefaultFont(f)
+highlight = syntax.PythonHighlighter(editor.document())
+
+
+editor.show()
+
+# Load syntax.py into the editor for demo purposes
+
+#infile = open('syntax.py', 'r')
+#editor.setPlainText(infile.read())
+
+app.exec_()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/__init__.py
new file mode 100644
index 0000000000..b662747b04
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/__init__.py
@@ -0,0 +1,7 @@
+action_classes = ['newaction.newaction.NewAction',
+ 'openaction.openaction.OpenAction',
+ 'saveaction.saveaction.SaveAction',
+ 'runaction.runaction.RunAction',
+ 'settingsaction.settingsaction.SettingsAction',
+ 'debugaction.debugaction.DebugAction',
+ 'closeaction.closeaction.CloseAction']
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py
new file mode 100644
index 0000000000..3a4d918f86
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/closeaction/closeaction.py
@@ -0,0 +1,37 @@
+from PyQt5.QtWidgets import QAction, QMessageBox
+from PyQt5.QtGui import QKeySequence
+from PyQt5.QtCore import Qt
+
+
+class CloseAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(CloseAction, self).__init__(parent)
+ self.scripter = scripter
+
+ self.triggered.connect(self.close)
+
+ self.setText('Close')
+ self.setObjectName('close')
+ self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q))
+
+ @property
+ def parent(self):
+ return 'File'
+
+ def close(self):
+ msgBox = QMessageBox(self.scripter.uicontroller.mainWidget)
+
+ msgBox.setInformativeText("Do you want to save the current document?");
+ msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel);
+ msgBox.setDefaultButton(QMessageBox.Save);
+
+ ret = msgBox.exec();
+
+ if ret == QMessageBox.Cancel:
+ return
+ if ret == QMessageBox.Save:
+ if not self.scripter.uicontroller.invokeAction('save'):
+ return
+
+ self.scripter.uicontroller.closeScripter()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/debugaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/debugaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/debugaction/debugaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/debugaction/debugaction.py
new file mode 100644
index 0000000000..123f109e24
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/debugaction/debugaction.py
@@ -0,0 +1,29 @@
+from PyQt5.QtWidgets import QAction
+from PyQt5.QtGui import QIcon, QPixmap, QKeySequence
+from scripter import resources_rc
+from PyQt5.QtCore import Qt
+
+
+class DebugAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(DebugAction, self).__init__(parent)
+ self.scripter = scripter
+
+ self.triggered.connect(self.debug)
+
+ self.setText('Debug')
+ self.setToolTip('Debug Ctrl+D')
+ self.setIcon(QIcon(':/icons/debug.svg'))
+ self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_D))
+
+ @property
+ def parent(self):
+ return 'toolBar'
+
+ def debug(self):
+ if self.scripter.uicontroller.invokeAction('save'):
+ self.scripter.uicontroller.setActiveWidget('Debugger')
+ self.scripter.debugcontroller.start(self.scripter.documentcontroller.activeDocument)
+ widget = self.scripter.uicontroller.findTabWidget('Debugger')
+ widget.startDebugger()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py
new file mode 100644
index 0000000000..e2f1798bac
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/newaction/newaction.py
@@ -0,0 +1,39 @@
+from PyQt5.QtWidgets import QAction, QMessageBox
+from PyQt5.QtGui import QKeySequence
+from PyQt5.QtCore import Qt
+
+
+class NewAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(NewAction, self).__init__(parent)
+ self.scripter = scripter
+
+ self.triggered.connect(self.new)
+
+ self.setText('New')
+ self.setObjectName('new')
+ self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_N))
+
+ @property
+ def parent(self):
+ return 'File'
+
+ def new(self):
+ msgBox = QMessageBox(self.scripter.uicontroller.mainWidget)
+
+ msgBox.setText("The document has been modified.");
+ msgBox.setInformativeText("Do you want to save your changes?");
+ msgBox.setStandardButtons(QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel);
+ msgBox.setDefaultButton(QMessageBox.Save);
+
+ ret = msgBox.exec();
+
+ if ret == QMessageBox.Cancel:
+ return
+ if ret == QMessageBox.Save:
+ self.scripter.uicontroller.invokeAction('save')
+
+ self.scripter.documentcontroller.clearActiveDocument()
+ self.scripter.uicontroller.setStatusBar()
+ self.scripter.uicontroller.clearEditor()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py
new file mode 100644
index 0000000000..5b2347ba6e
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/openaction/openaction.py
@@ -0,0 +1,38 @@
+from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
+from PyQt5.QtGui import QKeySequence
+from PyQt5.QtCore import Qt
+
+
+class OpenAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(OpenAction, self).__init__(parent)
+ self.scripter = scripter
+
+ self.triggered.connect(self.open)
+
+ self.setText('Open')
+ self.setObjectName('open')
+ self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_O))
+
+ @property
+ def parent(self):
+ return 'File'
+
+ def open(self):
+ dialog = QFileDialog(self.scripter.uicontroller.mainWidget)
+ dialog.setNameFilter('Python files (*.py)')
+
+ if dialog.exec():
+ try:
+ selectedFile = dialog.selectedFiles()[0]
+ fileExtension = selectedFile.rsplit('.', maxsplit=1)[1]
+
+ if fileExtension=='py':
+ document = self.scripter.documentcontroller.openDocument(selectedFile)
+ self.scripter.uicontroller.setDocumentEditor(document)
+ self.scripter.uicontroller.setStatusBar(document.filePath)
+ except:
+ QMessageBox.information(self.scripter.uicontroller.mainWidget,
+ 'Invalid File',
+ 'Open files with .py extension')
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py
new file mode 100644
index 0000000000..002b3f5f89
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/docwrapper.py
@@ -0,0 +1,12 @@
+from PyQt5.QtGui import QTextCursor
+
+class DocWrapper:
+
+ def __init__(self, textdocument):
+ self.textdocument = textdocument
+
+ def write(self, text, view = None):
+ cursor = QTextCursor(self.textdocument)
+ cursor.clearSelection()
+ cursor.movePosition(QTextCursor.End)
+ cursor.insertText(text)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py
new file mode 100644
index 0000000000..ddebfd0f86
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/runaction/runaction.py
@@ -0,0 +1,44 @@
+from PyQt5.QtWidgets import QAction, QMessageBox
+from PyQt5.QtGui import QIcon, QKeySequence
+from PyQt5.QtCore import Qt
+import sys
+from . import docwrapper
+import os
+from scripter import resources_rc
+
+
+class RunAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(RunAction, self).__init__(parent)
+ self.scripter = scripter
+
+ self.editor = self.scripter.uicontroller.editor
+ self.output = self.scripter.uicontroller.findTabWidget('OutPut')
+
+ self.triggered.connect(self.run)
+
+ self.setText('Run')
+ self.setToolTip('Run Ctrl+R')
+ self.setIcon(QIcon(':/icons/run.svg'))
+ self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R))
+
+ @property
+ def parent(self):
+ return 'toolBar'
+
+ def run(self):
+ self.scripter.uicontroller.setActiveWidget('OutPut')
+ stdout = sys.stdout
+ stderr = sys.stderr
+ output = docwrapper.DocWrapper(self.output.document())
+ output.write("======================================\n")
+ sys.stdout = output
+ sys.stderr = output
+ script = self.editor.document().toPlainText()
+ try:
+ exec(script)
+ except Exception as e:
+ self.scripter.uicontroller.showException(str(e))
+ sys.stdout = stdout
+ sys.stderr = stderr
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py
new file mode 100644
index 0000000000..eb69823d50
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/saveaction/saveaction.py
@@ -0,0 +1,50 @@
+from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
+from PyQt5.QtGui import QKeySequence
+from PyQt5.QtCore import Qt
+
+
+class SaveAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(SaveAction, self).__init__(parent)
+ self.scripter = scripter
+ self.editor = self.scripter.uicontroller.editor
+
+ self.triggered.connect(self.save)
+
+ self.setText('Save')
+ self.setObjectName('save')
+ self.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_S))
+
+ @property
+ def parent(self):
+ return 'File'
+
+ def save(self):
+ text = self.editor.toPlainText()
+ fileName = ''
+ fileExtension = ''
+
+ if not self.scripter.documentcontroller.activeDocument:
+ try:
+ fileName = QFileDialog.getSaveFileName(self.scripter.uicontroller.mainWidget,
+ 'Save Python File', '',
+ 'Python File (*.py)')[0]
+ if not fileName:
+ return
+
+ fileExtension = fileName.rsplit('.', maxsplit=1)[1]
+ except:
+ if not fileExtension=='py':
+ QMessageBox.information(self.scripter.uicontroller.mainWidget,
+ 'Invalid File',
+ 'Save files with .py extension')
+ return
+
+ document = self.scripter.documentcontroller.saveDocument(text, fileName)
+ if document:
+ self.scripter.uicontroller.setStatusBar(document.filePath)
+ else:
+ self.scripter.uicontroller.setStatusBar('untitled')
+
+ return document
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/fontscombobox.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/fontscombobox.py
new file mode 100644
index 0000000000..92777ee24f
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/fontscombobox.py
@@ -0,0 +1,38 @@
+from PyQt5.QtWidgets import QComboBox, QCompleter
+from PyQt5.QtGui import QFontDatabase
+from PyQt5.QtCore import Qt
+
+
+class FontsComboBox(QComboBox):
+
+ def __init__(self, editor, parent=None):
+ super(FontsComboBox, self).__init__(parent)
+
+ self.editor = editor
+
+ _fontDataBase = QFontDatabase()
+
+ self.addItems(_fontDataBase.families())
+ self.setCurrentIndex(self.findText(self.editor.font))
+
+ com = QCompleter()
+ com.setCaseSensitivity(Qt.CaseInsensitive)
+ com.setCompletionMode(QCompleter.PopupCompletion)
+
+ # Style sheet to set false on combobox-popup
+ self.setStyleSheet("QComboBox { combobox-popup: 0; }")
+ self.setMaxVisibleItems(10)
+ self.setCompleter(com)
+ self.currentIndexChanged.connect(self._currentIndexChanged)
+
+ def _currentIndexChanged(self, index):
+ self.editor.font = self.itemText(index)
+
+ def readSettings(self, settings):
+ fontName = settings.value('fontName', '')
+
+ if fontName:
+ self.setCurrentIndex(self.findText(fontName))
+
+ def writeSettings(self, settings):
+ settings.setValue('fontName', self.editor.font)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/settingsaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/settingsaction.py
new file mode 100644
index 0000000000..fcec55e1ce
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/settingsaction.py
@@ -0,0 +1,32 @@
+from PyQt5.QtWidgets import QAction
+from PyQt5.QtCore import Qt
+from . import settingsdialog
+
+
+class SettingsAction(QAction):
+
+ def __init__(self, scripter, parent=None):
+ super(SettingsAction, self).__init__(parent)
+ self.scripter = scripter
+
+ self.triggered.connect(self.openSettings)
+
+ self.settingsDialog = settingsdialog.SettingsDialog(self.scripter)
+ self.settingsDialog.setWindowModality(Qt.WindowModal)
+ self.settingsDialog.setFixedSize(400, 250)
+
+ self.setText('Settings')
+
+ @property
+ def parent(self):
+ return 'File'
+
+ def openSettings(self):
+ self.settingsDialog.show()
+ self.settingsDialog.exec()
+
+ def readSettings(self):
+ self.settingsDialog.readSettings(self.scripter.settings)
+
+ def writeSettings(self):
+ self.settingsDialog.writeSettings(self.scripter.settings)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/settingsdialog.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/settingsdialog.py
new file mode 100644
index 0000000000..adc0848a50
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/settingsdialog.py
@@ -0,0 +1,24 @@
+from PyQt5.QtWidgets import QDialog, QFormLayout
+from . import syntaxstylescombobox, fontscombobox
+
+
+class SettingsDialog(QDialog):
+
+ def __init__(self, scripter, parent=None):
+ super(SettingsDialog, self).__init__(parent)
+
+ self.scripter = scripter
+ self.setWindowTitle('Settings')
+ self.mainLayout = QFormLayout(self)
+ self.mainLayout.addRow('Syntax Highlither', syntaxstylescombobox.SyntaxStylesComboBox(self.scripter.uicontroller.highlight))
+ self.mainLayout.addRow('Fonts', fontscombobox.FontsComboBox(self.scripter.uicontroller.editor))
+
+ def readSettings(self, settings):
+ for index in range(self.mainLayout.rowCount()):
+ widget = self.mainLayout.itemAt(index, QFormLayout.FieldRole).widget()
+ widget.readSettings(settings)
+
+ def writeSettings(self, settings):
+ for index in range(self.mainLayout.rowCount()):
+ widget = self.mainLayout.itemAt(index, QFormLayout.FieldRole).widget()
+ widget.writeSettings(settings)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/syntaxstylescombobox.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/syntaxstylescombobox.py
new file mode 100644
index 0000000000..4a98ce3b73
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/actions/settingsaction/syntaxstylescombobox.py
@@ -0,0 +1,33 @@
+from PyQt5.QtWidgets import QComboBox
+from scripter.ui_scripter.syntax import syntaxstyles
+
+
+class SyntaxStylesComboBox(QComboBox):
+
+ def __init__(self, highlight, parent=None):
+ super(SyntaxStylesComboBox, self).__init__(parent)
+
+ self.highlight = highlight
+ self.styleClasses = [syntaxstyles.DefaultSyntaxStyle, syntaxstyles.PythonVimSyntaxStyle]
+
+ for styleClass in self.styleClasses:
+ className = styleClass.__name__
+ self.addItem(className)
+
+ if className == type(self.highlight.getSyntaxStyle()).__name__:
+ self.setCurrentIndex(self.findText(className))
+
+ self.currentIndexChanged.connect(self._currentIndexChanged)
+
+ def _currentIndexChanged(self, index):
+ self.highlight.setSyntaxStyle(getattr(syntaxstyles, self.itemText(index))())
+ self.highlight.rehighlight()
+
+ def readSettings(self, settings):
+ syntaxStyle = settings.value('syntaxStyle', '')
+
+ if syntaxStyle:
+ self.setCurrentIndex(self.findText(syntaxStyle))
+
+ def writeSettings(self, settings):
+ settings.setValue('syntaxStyle', type(self.highlight.getSyntaxStyle()).__name__)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/debugarea.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/debugarea.py
new file mode 100644
index 0000000000..efca9827fe
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/debugarea.py
@@ -0,0 +1,16 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import QSize
+
+
+class DebugArea(QWidget):
+
+ def __init__(self, editor):
+ super(DebugArea, self).__init__(editor)
+ self.codeEditor = editor
+
+ def sizeHint(self):
+ return QSize(self.codeEditor.debugAreaWidth(), 0)
+
+ def paintEvent(self, event):
+ """It Invokes the draw method(debugAreaPaintEvent) in CodeEditor"""
+ self.codeEditor.debugAreaPaintEvent(event)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/linenumberarea.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/linenumberarea.py
new file mode 100644
index 0000000000..425a9f4123
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/linenumberarea.py
@@ -0,0 +1,16 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import QSize
+
+
+class LineNumberArea(QWidget):
+
+ def __init__(self, editor):
+ super(LineNumberArea, self).__init__(editor)
+ self.codeEditor = editor
+
+ def sizeHint(self):
+ return QSize(self.codeEditor.lineNumberAreaWidth(), 0)
+
+ def paintEvent(self, event):
+ """It Invokes the draw method(lineNumberAreaPaintEvent) in CodeEditor"""
+ self.codeEditor.lineNumberAreaPaintEvent(event)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py
new file mode 100644
index 0000000000..e34aef5525
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/pythoneditor.py
@@ -0,0 +1,151 @@
+from PyQt5.QtCore import Qt, QRect, QSize, QPoint
+from PyQt5.QtWidgets import QPlainTextEdit, QTextEdit
+from PyQt5.QtGui import QIcon, QColor, QPainter, QTextFormat, QFont, QTextCursor
+from scripter.ui_scripter.editor import linenumberarea, debugarea
+from scripter import resources_rc
+
+
+class CodeEditor(QPlainTextEdit):
+
+ DEBUG_AREA_WIDTH = 20
+
+ def __init__(self, scripter, parent=None):
+ super(CodeEditor, self).__init__(parent)
+
+ self.setLineWrapMode(self.NoWrap)
+
+ self.scripter = scripter
+ self.lineNumberArea = linenumberarea.LineNumberArea(self)
+ self.debugArea = debugarea.DebugArea(self)
+
+ self.blockCountChanged.connect(self.updateMarginsWidth)
+ self.updateRequest.connect(self.updateLineNumberArea)
+ self.cursorPositionChanged.connect(self.highlightCurrentLine)
+
+ self.updateMarginsWidth()
+ self.highlightCurrentLine()
+ self.font = "Monospace"
+ self._stepped = False
+ self.debugArrow = QIcon(':/icons/debug_arrow.svg')
+
+
+ def debugAreaWidth(self):
+ return self.DEBUG_AREA_WIDTH
+
+ def lineNumberAreaWidth(self):
+ """The lineNumberAreaWidth is the quatity of decimal places in blockCount"""
+ digits = 1
+ max_ = max(1, self.blockCount())
+ while (max_ >= 10):
+ max_ /= 10
+ digits += 1
+
+ space = 3 + self.fontMetrics().width('9') * digits
+
+ return space
+
+ def resizeEvent(self, event):
+ super(CodeEditor, self).resizeEvent(event)
+
+ qRect = self.contentsRect()
+ self.debugArea.setGeometry(QRect(qRect.left(),
+ qRect.top(),
+ self.debugAreaWidth(),
+ qRect.height()))
+
+ self.lineNumberArea.setGeometry(QRect(qRect.left() + self.debugAreaWidth(),
+ qRect.top(),
+ self.lineNumberAreaWidth(),
+ qRect.height()))
+
+ def updateMarginsWidth(self):
+ self.setViewportMargins(self.lineNumberAreaWidth() + self.debugAreaWidth(), 0, 0, 0)
+
+ def updateLineNumberArea(self, rect, dy):
+ """ This slot is invoked when the editors viewport has been scrolled """
+
+ if dy:
+ self.lineNumberArea.scroll(0, dy)
+ self.debugArea.scroll(0, dy)
+ else:
+ self.lineNumberArea.update(0, rect.y(), self.lineNumberArea.width(), rect.height())
+
+ if rect.contains(self.viewport().rect()):
+ self.updateMarginsWidth()
+
+ def lineNumberAreaPaintEvent(self, event):
+ """This method draws the current lineNumberArea for while"""
+ painter = QPainter(self.lineNumberArea)
+ painter.fillRect(event.rect(), QColor(Qt.lightGray).darker(300))
+
+ block = self.firstVisibleBlock()
+ blockNumber = block.blockNumber()
+ top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
+ bottom = top + int(self.blockBoundingRect(block).height())
+ while block.isValid() and top <= event.rect().bottom():
+ if block.isVisible() and bottom >= event.rect().top():
+ number = str(blockNumber + 1)
+ painter.setPen(QColor(Qt.lightGray))
+ painter.drawText(0, top, self.lineNumberArea.width(), self.fontMetrics().height(),
+ Qt.AlignRight, number)
+
+ block = block.next()
+ top = bottom
+ bottom = top + int(self.blockBoundingRect(block).height())
+ blockNumber += 1
+
+ def debugAreaPaintEvent(self, event):
+ if self.scripter.debugcontroller.isActive and self.scripter.debugcontroller.currentLine:
+ lineNumber = self.scripter.debugcontroller.currentLine
+ block = self.document().findBlockByLineNumber(lineNumber-1)
+
+ if self._stepped:
+ cursor = QTextCursor(block)
+ self.setTextCursor(cursor)
+ self._stepped = False
+
+ top = int(self.blockBoundingGeometry(block).translated(self.contentOffset()).top())
+ bottom = top + int(self.blockBoundingRect(block).height())
+
+ painter = QPainter(self.debugArea)
+ pixmap = self.debugArrow.pixmap(QSize(self.debugAreaWidth()-3, int(self.blockBoundingRect(block).height())))
+ painter.drawPixmap(QPoint(0, top), pixmap)
+
+ def highlightCurrentLine(self):
+ """Highlight current line under cursor"""
+ currentSelection = QTextEdit.ExtraSelection()
+
+ lineColor = QColor(Qt.gray).darker(250)
+ currentSelection.format.setBackground(lineColor)
+ currentSelection.format.setProperty(QTextFormat.FullWidthSelection, True)
+ currentSelection.cursor = self.textCursor()
+ currentSelection.cursor.clearSelection()
+
+ self.setExtraSelections([currentSelection])
+
+ def wheelEvent(self, e):
+ """When the CTRL is pressed during the wheelEvent, zoomIn and zoomOut
+ slots are invoked"""
+ if e.modifiers() == Qt.ControlModifier:
+ delta = e.angleDelta().y()
+ if delta < 0:
+ self.zoomOut()
+ elif delta > 0:
+ self.zoomIn()
+ else:
+ super(CodeEditor, self).wheelEvent(e)
+
+ @property
+ def font(self):
+ return self._font
+
+ @font.setter
+ def font(self, font):
+ self._font = font
+ self.setFont(QFont(font, 10))
+
+ def setStepped(self, status):
+ self._stepped = status
+
+ def repaintDebugArea(self):
+ self.debugArea.repaint()
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/statusbar.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/statusbar.py
new file mode 100644
index 0000000000..927a5c4ca1
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/editor/statusbar.py
@@ -0,0 +1,16 @@
+from PyQt5.QtWidgets import QWidget
+from PyQt5.QtCore import QSize
+
+
+class StatusBar(QWidget):
+
+ def __init__(self, editor):
+ super(StatusBar, self).__init__(editor)
+ self.codeEditor = editor
+
+ def sizeHint(self):
+ return QSize(self.codeEditor.width(), 0)
+
+ def paintEvent(self, event):
+ """It Invokes the draw method(statusBarPaintEvent) in CodeEditor"""
+ self.codeEditor.statusBarPaintEvent(event)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py
new file mode 100644
index 0000000000..7519ff7e87
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntax.py
@@ -0,0 +1,156 @@
+# syntax.py: taken from https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting
+
+
+import sys
+from PyQt5.QtCore import QRegExp
+from PyQt5.QtGui import QSyntaxHighlighter
+
+
+class PythonHighlighter (QSyntaxHighlighter):
+ """Syntax highlighter for the Python language.
+ """
+ # Python keywords
+ keywords = [
+ 'and', 'assert', 'break', 'class', 'continue', 'def',
+ 'del', 'elif', 'else', 'except', 'exec', 'finally',
+ 'for', 'from', 'global', 'if', 'import', 'in',
+ 'is', 'lambda', 'not', 'or', 'pass', 'print',
+ 'raise', 'return', 'try', 'while', 'yield',
+ 'None', 'True', 'False',
+ ]
+
+ # Python operators
+ operators = [
+ '=',
+ # Comparison
+ '==', '!=', '<', '<=', '>', '>=',
+ # Arithmetic
+ '\+', '-', '\*', '/', '//', '\%', '\*\*',
+ # In-place
+ '\+=', '-=', '\*=', '/=', '\%=',
+ # Bitwise
+ '\^', '\|', '\&', '\~', '>>', '<<',
+ ]
+
+ # Python braces
+ braces = [
+ '\{', '\}', '\(', '\)', '\[', '\]',
+ ]
+
+ def __init__(self, document, syntaxStyle):
+ QSyntaxHighlighter.__init__(self, document)
+
+ self.syntaxStyle = syntaxStyle
+ self.document = document
+
+ # Multi-line strings (expression, flag, style)
+ # FIXME: The triple-quotes in these two lines will mess up the
+ # syntax highlighting from this point onward
+ self.tri_single = (QRegExp(r"""'''(?!")"""), 1, 'string2')
+ self.tri_double = (QRegExp(r'''"""(?!')'''), 2, 'string2')
+
+ rules = []
+
+ # Keyword, operator, and brace rules
+ rules += [(r'\b%s\b' % w, 0, 'keyword')
+ for w in PythonHighlighter.keywords]
+ rules += [(r'%s' % o, 0, 'operator')
+ for o in PythonHighlighter.operators]
+ rules += [(r'%s' % b, 0, 'brace')
+ for b in PythonHighlighter.braces]
+
+ # All other rules
+ rules += [
+ # 'self'
+ (r'\bself\b', 0, 'self'),
+
+ # Double-quoted string, possibly containing escape sequences
+ (r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'),
+ # Single-quoted string, possibly containing escape sequences
+ (r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'),
+
+ # 'def' followed by an identifier
+ (r'\bdef\b\s*(\w+)', 1, 'defclass'),
+ # 'class' followed by an identifier
+ (r'\bclass\b\s*(\w+)', 1, 'defclass'),
+
+ # From '#' until a newline
+ (r'#[^\n]*', 0, 'comment'),
+
+ # Numeric literals
+ (r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'),
+ (r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'),
+ (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'),
+ ]
+
+ # Build a QRegExp for each pattern
+ self.rules = [(QRegExp(pat), index, identifier)
+ for (pat, index, identifier) in rules]
+
+
+ def highlightBlock(self, text):
+ """Apply syntax highlighting to the given block of text."""
+ # Do other syntax formatting
+ for expression, nth, identifier in self.rules:
+ index = expression.indexIn(text, 0)
+
+ while index >= 0:
+ # We actually want the index of the nth match
+ index = expression.pos(nth)
+ length = len(expression.cap(nth))
+ self.setFormat(index, length, self.syntaxStyle[identifier])
+ index = expression.indexIn(text, index + length)
+
+ self.setCurrentBlockState(0)
+
+ # Do multi-line strings
+ in_multiline = self.match_multiline(text, *self.tri_single)
+ if not in_multiline:
+ in_multiline = self.match_multiline(text, *self.tri_double)
+
+
+ def match_multiline(self, text, delimiter, in_state, style):
+ """Do highlighting of multi-line strings. ``delimiter`` should be a
+ ``QRegExp`` for triple-single-quotes or triple-double-quotes, and
+ ``in_state`` should be a unique integer to represent the corresponding
+ state changes when inside those strings. Returns True if we're still
+ inside a multi-line string when this function is finished.
+ """
+ # If inside triple-single quotes, start at 0
+ if self.previousBlockState() == in_state:
+ start = 0
+ add = 0
+ # Otherwise, look for the delimiter on this line
+ else:
+ start = delimiter.indexIn(text)
+ # Move past this match
+ add = delimiter.matchedLength()
+
+ # As long as there's a delimiter match on this line...
+ while start >= 0:
+ # Look for the ending delimiter
+ end = delimiter.indexIn(text, start + add)
+ # Ending delimiter on this line?
+ if end >= add:
+ length = end - start + add + delimiter.matchedLength()
+ self.setCurrentBlockState(0)
+ # No; multi-line string
+ else:
+ self.setCurrentBlockState(in_state)
+ length = len(text) - start + add
+ # Apply formatting
+ self.setFormat(start, length, self.syntaxStyle[style])
+ # Look for the next match
+ start = delimiter.indexIn(text, start + length)
+
+ # Return True if still inside a multi-line string, False otherwise
+ if self.currentBlockState() == in_state:
+ return True
+ else:
+ return False
+
+ def getSyntaxStyle(self):
+ return self.syntaxStyle
+
+ def setSyntaxStyle(self, syntaxStyle):
+ self.syntaxStyle = syntaxStyle
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py
new file mode 100644
index 0000000000..3aae750bd4
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/syntax/syntaxstyles.py
@@ -0,0 +1,56 @@
+from PyQt5.QtGui import QColor, QTextCharFormat, QFont
+
+
+def format(color, style='', darker=100, lighter=100):
+ """Return a QTextCharFormat with the given attributes.
+ """
+ _color = QColor(color)
+ _color = _color.darker(darker)
+ _color = _color.lighter(lighter)
+
+ _format = QTextCharFormat()
+ _format.setForeground(_color)
+ if 'bold' in style:
+ _format.setFontWeight(QFont.Bold)
+ if 'italic' in style:
+ _format.setFontItalic(True)
+
+ return _format
+
+
+class DefaultSyntaxStyle(object):
+
+ # Syntax styles that combines with dark backgrounds
+ STYLES = {
+ 'keyword': format('cyan'),
+ 'operator': format('orange'),
+ 'brace': format('gray'),
+ 'defclass': format('black', 'bold'),
+ 'string': format('magenta'),
+ 'string2': format('darkMagenta'),
+ 'comment': format('darkGreen', 'italic'),
+ 'self': format('black', 'italic'),
+ 'numbers': format('brown'),
+ }
+
+ def __getitem__(self, key):
+ return self.STYLES[key]
+
+
+class PythonVimSyntaxStyle(object):
+ """ It based in the colorschemme of the Vim editor for python code http://www.vim.org/scripts/script.php?script_id=790 """
+ # Syntax styles that combines with dark backgrounds
+ STYLES = {
+ 'keyword': format('yellow', darker=125),
+ 'operator': format('magenta', darker=150),
+ 'brace': format('white'),
+ 'defclass': format('orange', 'bold'),
+ 'string': format('green', lighter=160),
+ 'string2': format('lightGray', 'italic', darker=120),
+ 'comment': format('gray', 'italic'),
+ 'self': format('blue', lighter=170),
+ 'numbers': format('yellow', lighter=130),
+ }
+
+ def __getitem__(self, key):
+ return self.STYLES[key]
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py
new file mode 100644
index 0000000000..c559d7e342
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/__init__.py
@@ -0,0 +1,2 @@
+widgetClasses = ['outputwidget.outputwidget.OutPutWidget',
+ 'debuggerwidget.debuggerwidget.DebuggerWidget',]
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py
new file mode 100644
index 0000000000..f1c1445bfe
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggertable.py
@@ -0,0 +1,33 @@
+from PyQt5.QtWidgets import QTableWidget, QTableWidgetItem
+
+
+class DebuggerTable(QTableWidget):
+
+ def __init__(self, parent=None):
+ super(DebuggerTable, self).__init__(parent)
+
+ self.setColumnCount(4)
+
+ tableHeader = ['Scope', 'Name', 'Value', 'Type']
+ self.setHorizontalHeaderLabels(tableHeader)
+ self.setEditTriggers(self.NoEditTriggers)
+
+ def updateTable(self, data):
+ self.clearContents()
+ self.setRowCount(0)
+
+ if data and not data.get('quit') and not data.get('exception'):
+ locals_list = data['frame']['locals']
+ globals_list = data['frame']['globals']
+
+ all_variables = {'locals': locals_list, 'globals': globals_list}
+
+ for scope_key in all_variables:
+ for item in all_variables[scope_key]:
+ for key, value in item.items():
+ row = self.rowCount()
+ self.insertRow(row)
+ self.setItem(row, 0, QTableWidgetItem(str(scope_key)))
+ self.setItem(row, 1, QTableWidgetItem(key))
+ self.setItem(row, 2, QTableWidgetItem(value['value']))
+ self.setItem(row, 3, QTableWidgetItem(value['type']))
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggerwidget.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggerwidget.py
new file mode 100644
index 0000000000..dbd8dba6de
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/debuggerwidget.py
@@ -0,0 +1,36 @@
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QToolBar, QTableWidget, QAction
+from . import stepaction, stopaction, debuggertable
+
+
+class DebuggerWidget(QWidget):
+
+ def __init__(self, scripter, parent=None):
+ super(DebuggerWidget, self).__init__(parent)
+
+ self.scripter = scripter
+ self.setObjectName('Debugger')
+ self.layout = QVBoxLayout()
+
+ self.stopAction = stopaction.StopAction(self.scripter, self)
+ self.toolbar = QToolBar()
+ self.stepAction = stepaction.StepAction(self.scripter, self)
+ self.toolbar.addAction(self.stopAction)
+ self.toolbar.addAction(self.stepAction)
+ self.disableToolbar(True)
+
+ self.table = debuggertable.DebuggerTable()
+
+ self.layout.addWidget(self.toolbar)
+ self.layout.addWidget(self.table)
+ self.setLayout(self.layout)
+
+ def startDebugger(self):
+ self.disableToolbar(False)
+
+ def disableToolbar(self, status):
+ for action in self.toolbar.actions():
+ action.setDisabled(status)
+
+ def updateWidget(self):
+ data = self.scripter.debugcontroller.debuggerData
+ self.table.updateTable(data)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/stepaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/stepaction.py
new file mode 100644
index 0000000000..21ae1eb467
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/stepaction.py
@@ -0,0 +1,24 @@
+from PyQt5.QtWidgets import QAction
+from PyQt5.QtGui import QIcon
+from scripter import resources_rc
+
+
+class StepAction(QAction):
+
+ def __init__(self, scripter, toolbar, parent=None):
+ super(StepAction, self).__init__(parent)
+ self.scripter = scripter
+ self.toolbar = toolbar
+
+ self.triggered.connect(self.step)
+
+ self.setText('Step Over')
+ # path to the icon
+ self.setIcon(QIcon(':/icons/step.svg'))
+
+ def step(self):
+ status = self.scripter.debugcontroller.isActive
+ if status:
+ self.scripter.debugcontroller.step()
+ else:
+ self.toolbar.disableToolbar(True)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/stopaction.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/stopaction.py
new file mode 100644
index 0000000000..163f29524c
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/debuggerwidget/stopaction.py
@@ -0,0 +1,21 @@
+from PyQt5.QtWidgets import QAction
+from PyQt5.QtGui import QIcon
+from scripter import resources_rc
+
+
+class StopAction(QAction):
+
+ def __init__(self, scripter, toolbar, parent=None):
+ super(StopAction, self).__init__(parent)
+ self.scripter = scripter
+ self.toolbar = toolbar
+
+ self.triggered.connect(self.stop)
+
+ self.setText('Stop')
+ # path to the icon
+ self.setIcon(QIcon(':/icons/stop.svg'))
+
+ def stop(self):
+ self.scripter.debugcontroller.stop()
+ self.toolbar.disableToolbar(True)
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/__init__.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py
new file mode 100644
index 0000000000..6a6dc6f68b
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/ui_scripter/tabwidgets/outputwidget/outputwidget.py
@@ -0,0 +1,10 @@
+from PyQt5.QtWidgets import QPlainTextEdit
+
+
+class OutPutWidget(QPlainTextEdit):
+
+ def __init__(self, scripter, parent=None):
+ super(OutPutWidget, self).__init__(parent)
+
+ self.scripter = scripter
+ self.setObjectName('OutPut')
diff --git a/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py
new file mode 100644
index 0000000000..943ffed509
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/scripter/uicontroller.py
@@ -0,0 +1,186 @@
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import (QToolBar, QMenuBar, QTabWidget,
+ QLabel, QVBoxLayout, QMessageBox)
+from PyQt5.QtCore import Qt, QObject
+from scripter.ui_scripter.syntax import syntax, syntaxstyles
+from scripter.ui_scripter.editor import pythoneditor
+from scripter import scripterdialog
+import os
+import importlib
+
+
+class UIController(object):
+
+ def __init__(self):
+ self.mainWidget = scripterdialog.ScripterDialog(self)
+ self.actionToolbar = QToolBar('toolBar', self.mainWidget)
+ self.menu_bar = QMenuBar(self.mainWidget)
+
+ self.actionToolbar.setObjectName('toolBar')
+ self.menu_bar.setObjectName('menuBar')
+
+ self.actions = []
+
+ self.mainWidget.setWindowModality(Qt.NonModal)
+
+ def initialize(self, scripter):
+ self.editor = pythoneditor.CodeEditor(scripter)
+ self.tabWidget = QTabWidget()
+ self.statusBar = QLabel('untitled')
+ self.highlight = syntax.PythonHighlighter(self.editor.document(), syntaxstyles.DefaultSyntaxStyle())
+
+ self.scripter = scripter
+
+ self.loadMenus()
+ self.loadWidgets()
+ self.loadActions()
+ self._readSettings()
+
+ vbox = QVBoxLayout(self.mainWidget)
+ vbox.addWidget(self.menu_bar)
+ vbox.addWidget(self.actionToolbar)
+ vbox.addWidget(self.editor)
+ vbox.addWidget(self.tabWidget)
+ vbox.addWidget(self.statusBar)
+
+ self.mainWidget.resize(400, 500)
+ self.mainWidget.setWindowTitle("Scripter")
+ self.mainWidget.setSizeGripEnabled(True)
+ self.mainWidget.show()
+ self.mainWidget.activateWindow()
+
+ def loadMenus(self):
+ self.addMenu('File', 'File')
+
+ def addMenu(self, menuName, parentName):
+ parent = self.menu_bar.findChild(QObject, parentName)
+ self.newMenu = None
+
+ if parent:
+ self.newMenu = parent.addMenu(menuName)
+ else:
+ self.newMenu = self.menu_bar.addMenu(menuName)
+
+ self.newMenu.setObjectName(menuName)
+
+ return self.newMenu
+
+ def loadActions(self):
+ module_path = 'scripter.ui_scripter.actions'
+ actions_module = importlib.import_module(module_path)
+ modules = []
+
+ for class_path in actions_module.action_classes:
+ _module, _klass = class_path.rsplit('.', maxsplit=1)
+ modules.append(dict(module='{0}.{1}'.format(module_path, _module),
+ klass=_klass))
+
+ for module in modules:
+ m = importlib.import_module(module['module'])
+ action_class = getattr(m, module['klass'])
+ obj = action_class(self.scripter)
+ parent = self.mainWidget.findChild(QObject, obj.parent)
+ self.actions.append(dict(action=obj, parent=parent))
+
+ for action in self.actions:
+ action['parent'].addAction(action['action'])
+
+ def loadWidgets(self):
+ modulePath = 'scripter.ui_scripter.tabwidgets'
+ widgetsModule = importlib.import_module(modulePath)
+ modules = []
+
+ for classPath in widgetsModule.widgetClasses:
+ _module, _klass = classPath.rsplit('.', maxsplit=1)
+ modules.append(dict(module='{0}.{1}'.format(modulePath, _module),
+ klass=_klass))
+
+ for module in modules:
+ m = importlib.import_module(module['module'])
+ widgetClass = getattr(m, module['klass'])
+ obj = widgetClass(self.scripter)
+ self.tabWidget.addTab(obj, obj.objectName())
+
+ def invokeAction(self, actionName):
+ for action in self.actions:
+ if action['action'].objectName() == actionName:
+ method = getattr(action['action'], actionName)
+ if method:
+ return method()
+
+ def findTabWidget(self, widgetName):
+ for index in range(self.tabWidget.count()):
+ widget = self.tabWidget.widget(index)
+ if widget.objectName() == widgetName:
+ return widget
+
+ def showException(self, exception):
+ QMessageBox.critical(self.editor, "Error running script", str(exception))
+
+ def setDocumentEditor(self, document):
+ self.editor.clear()
+ self.editor.moveCursor(QTextCursor.Start)
+ self.editor.insertPlainText(document.data)
+ self.editor.moveCursor(QTextCursor.End)
+
+ def setStatusBar(self, value='untitled'):
+ self.statusBar.setText(value)
+
+ def setActiveWidget(self, widgetName):
+ widget = self.findTabWidget(widgetName)
+
+ if widget:
+ self.tabWidget.setCurrentWidget(widget)
+
+ def setStepped(self, status):
+ self.editor.setStepped(status)
+
+ def clearEditor(self):
+ self.editor.clear()
+
+ def repaintDebugArea(self):
+ self.editor.repaintDebugArea()
+
+ def closeScripter(self):
+ self.mainWidget.close()
+
+ def _writeSettings(self):
+ """ _writeSettings is a method invoked when the scripter starts, making
+ control inversion. Actions can implement a writeSettings method to
+ save your own settings without this method to know about it. """
+
+ self.scripter.settings.beginGroup('scripter')
+
+ document = self.scripter.documentcontroller.activeDocument
+ if document:
+ self.scripter.settings.setValue('activeDocumentPath', document.filePath)
+
+ for action in self.actions:
+ writeSettings = getattr(action['action'], "writeSettings", None)
+ if callable(writeSettings):
+ writeSettings()
+
+ self.scripter.settings.endGroup()
+
+
+ def _readSettings(self):
+ """ It's similar to _writeSettings, but reading the settings when the ScripterDialog is closed. """
+
+ self.scripter.settings.beginGroup('scripter')
+
+ activeDocumentPath = self.scripter.settings.value('activeDocumentPath', '')
+
+ if activeDocumentPath:
+ document = self.scripter.documentcontroller.openDocument(activeDocumentPath)
+ self.setStatusBar(document.filePath)
+ self.setDocumentEditor(document)
+
+ for action in self.actions:
+ readSettings = getattr(action['action'], "readSettings", None)
+ if callable(readSettings):
+ readSettings()
+
+ self.scripter.settings.endGroup()
+
+ def _saveSettings(self):
+ self.scripter.settings.sync()
diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/__init__.py b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/__init__.py
new file mode 100644
index 0000000000..dcd924e76d
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/__init__.py
@@ -0,0 +1,4 @@
+from krita import *
+from .selectionsbagdocker import *
+
+Krita.instance().addDockWidgetFactory(DockWidgetFactory("SelectionsBagDocker", DockWidgetFactory.DockRight, SelectionsBagDocker))
diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
new file mode 100644
index 0000000000..e4a634f256
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop
@@ -0,0 +1,19 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=selectionsbagdocker
+X-Python-2-Compatible=false
+Name=Selections Bag Docker
+Name[ca]=Acoblador de bossa de seleccions
+Name[nl]=Docker van zak met selecties
+Name[pt]=Área de Selecções
+Name[sv]=Dockningspanel med markeringspåse
+Name[uk]=Бічна панель позначеного
+Name[x-test]=xxSelections Bag Dockerxx
+Comment=A docker that allow to store a list of selections
+Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions
+Comment[nl]=Een docker die een lijst met selecties kan opslaan
+Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções
+Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar
+Comment[uk]=Бічна панель, на якій можна зберігати список позначеного
+Comment[x-test]=xxA docker that allow to store a list of selectionsxx
diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py
new file mode 100644
index 0000000000..ec73b3f554
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.py
@@ -0,0 +1,18 @@
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+
+from PyQt5 import uic
+from krita import *
+import os
+
+class SelectionsBagDocker(DockWidget):
+ def __init__(self):
+ super().__init__()
+ widget = QWidget(self)
+ uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/selectionsbagdocker.ui', widget)
+ self.setWidget(widget)
+ self.setWindowTitle("Selections bag")
+
+ def canvasChanged(self, canvas):
+ print("Canvas", canvas)
diff --git a/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.ui b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.ui
new file mode 100644
index 0000000000..68bd75dd44
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/selectionsbagdocker/selectionsbagdocker.ui
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Form</class>
+ <widget class="QWidget" name="Form">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="QListView" name="listView"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pushButtonSave">
+ <property name="text">
+ <string>save</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/__init__.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/__init__.py
new file mode 100644
index 0000000000..ea8c588a34
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/__init__.py
@@ -0,0 +1,2 @@
+# let's make a module
+from .tenbrushes import *
diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop
new file mode 100644
index 0000000000..aeae27a504
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/kritapykrita_tenbrushes.desktop
@@ -0,0 +1,19 @@
+[Desktop Entry]
+Type=Service
+ServiceTypes=Krita/PythonPlugin
+X-KDE-Library=tenbrushes
+X-Python-2-Compatible=false
+Name=Ten Brushes
+Name[ca]=Deu pinzells
+Name[nl]=Tien penselen
+Name[pt]=Dez Pincéis
+Name[sv]=Tio penslar
+Name[uk]=Десять пензлів
+Name[x-test]=xxTen Brushesxx
+Comment=Assign a preset to ctrl-1 to ctrl-0
+Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0
+Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0
+Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0
+Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0
+Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0
+Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx
diff --git a/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py
new file mode 100644
index 0000000000..d0faa3e4c2
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/plugins/tenbrushes/tenbrushes.py
@@ -0,0 +1,112 @@
+import sys
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from krita import *
+
+class DropButton(QPushButton):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.setFixedSize(64, 64)
+ self.setIconSize(QSize(64, 64))
+ self.preset = None
+
+ def selectPreset(self):
+ self.preset = self.presetChooser.currentPreset().name()
+ self.setIcon(QIcon(QPixmap.fromImage(self.presetChooser.currentPreset().image())))
+
+
+class TenBrushesExtension(Extension):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.buttons = []
+ self.actions = []
+
+ def setup(self):
+ action = Application.createAction("Ten Brushes")
+ action.setToolTip("Assign ten brush presets to ten shortcuts.")
+ action.triggered.connect(self.showDialog)
+
+ # Read the ten selected brush presets from the settings
+ selectedPresets = Application.readSetting("", "tenbrushes", "").split(',')
+ allPresets = Application.resources("preset")
+ # Setup up to ten actions and give them default shortcuts
+ j = 0
+ self.actions = []
+ for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']:
+ action = Application.createAction("Activate Preset " + i)
+ action.setVisible(False)
+ action.setShortcut("CTRL+" + i)
+ action.triggered.connect(self.activatePreset)
+ if j < len(selectedPresets) and selectedPresets[j] in allPresets:
+ action.preset = selectedPresets[j]
+ else:
+ action.preset = None
+ self.actions.append(action)
+ j = j + 1
+
+
+ def activatePreset(self):
+ allPresets = Application.resources("preset")
+ if Application.activeWindow() and len(Application.activeWindow().views()) > 0 and self.sender().preset in allPresets:
+ Application.activeWindow().views()[0].activateResource(allPresets[self.sender().preset])
+
+ def showDialog(self):
+ self.dialog = QDialog(Application.activeWindow().qwindow())
+
+ self.buttonBox = QDialogButtonBox(self.dialog)
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.buttonBox.accepted.connect(self.accept)
+ self.buttonBox.rejected.connect(self.dialog.reject)
+
+ vbox = QVBoxLayout(self.dialog)
+ hbox = QHBoxLayout(self.dialog)
+
+ self.presetChooser = PresetChooser(self.dialog)
+
+ allPresets = Application.resources("preset")
+ j = 0
+ self.buttons = []
+ for i in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']:
+ buttonBox = QVBoxLayout()
+ button = DropButton(self.dialog)
+ button.setObjectName(i)
+ button.clicked.connect(button.selectPreset)
+ button.presetChooser = self.presetChooser
+
+ if self.actions[j] and self.actions[j].preset and self.actions[j].preset in allPresets:
+ p = allPresets[self.actions[j].preset];
+ button.preset = p.name()
+ button.setIcon(QIcon(QPixmap.fromImage(p.image())))
+
+ buttonBox.addWidget(button)
+ label = QLabel("Ctrl+" + i)
+ label.setAlignment(Qt.AlignHCenter)
+ buttonBox.addWidget(label)
+ hbox.addLayout(buttonBox)
+ self.buttons.append(button)
+ j = j + 1
+
+ vbox.addLayout(hbox)
+ vbox.addWidget(self.presetChooser)
+ vbox.addWidget(self.buttonBox)
+ vbox.addWidget(QLabel("Select the brush preset, then click on the button you want to use to select the preset"))
+
+ self.dialog.show()
+ self.dialog.activateWindow()
+ self.dialog.exec_()
+
+
+ def accept(self):
+ i = 0
+ presets = []
+ for button in self.buttons:
+ self.actions[i].preset = button.preset
+ presets.append(button.preset)
+ i = i + 1
+ Application.writeSetting("", "tenbrushes", ','.join(map(str, presets)))
+ self.dialog.accept()
+
+Scripter.addExtension(TenBrushesExtension(Application))
diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
new file mode 100644
index 0000000000..ac98c03012
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@kogmbh.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#include "pyqtpluginsettings.h"
+
+#include "ui_manager.h"
+
+#include <QVBoxLayout>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include <QSortFilterProxyModel>
+#include <kconfiggroup.h>
+
+#include <KoIcon.h>
+
+
+#include "kis_config.h"
+
+
+PyQtPluginSettings::PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent) :
+ KisPreferenceSet(parent),
+ m_manager(new Ui::ManagerPage)
+{
+ m_manager->setupUi(this);
+
+ QSortFilterProxyModel* const proxy_model = new QSortFilterProxyModel(this);
+ proxy_model->setSourceModel(engine);
+ m_manager->pluginsList->setModel(proxy_model);
+ m_manager->pluginsList->resizeColumnToContents(0);
+ m_manager->pluginsList->sortByColumn(0, Qt::AscendingOrder);
+
+ const bool is_enabled = bool(engine);
+ const bool is_visible = !is_enabled;
+ m_manager->errorLabel->setVisible(is_visible);
+ m_manager->pluginsList->setEnabled(is_enabled);
+
+}
+
+PyQtPluginSettings::~PyQtPluginSettings()
+{
+ delete m_manager;
+}
+
+QString PyQtPluginSettings::id()
+{
+ return QString("pykritapluginmanager");
+}
+
+QString PyQtPluginSettings::name()
+{
+ return header();
+}
+
+QString PyQtPluginSettings::header()
+{
+ return QString(i18n("Python Plugin Manager"));
+}
+
+
+QIcon PyQtPluginSettings::icon()
+{
+ return koIcon("applications-development");
+}
+
+
+void PyQtPluginSettings::savePreferences() const
+{
+ Q_EMIT(settingsChanged());
+}
+
+void PyQtPluginSettings::loadPreferences()
+{
+}
+
+void PyQtPluginSettings::loadDefaultPreferences()
+{
+}
diff --git a/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
new file mode 100644
index 0000000000..37aa24dcec
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/pyqtpluginsettings.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2014 Boudewijn Rempt <boud@kogmbh.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; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifndef PYQTPLUGINSETTINGS_H
+#define PYQTPLUGINSETTINGS_H
+
+#include "kis_preference_set_registry.h"
+#include "engine.h"
+
+namespace Ui
+{
+class ManagerPage;
+}
+
+class QIcon;
+
+class PyQtPluginSettings : public KisPreferenceSet
+{
+ Q_OBJECT
+public:
+
+ PyQtPluginSettings(PyKrita::Engine *engine, QWidget *parent = 0);
+ ~PyQtPluginSettings();
+
+ virtual QString id();
+ virtual QString name();
+ virtual QString header();
+ virtual QIcon icon();
+
+public Q_SLOTS:
+ void savePreferences() const;
+ void loadPreferences();
+ void loadDefaultPreferences();
+
+Q_SIGNALS:
+ void settingsChanged() const;
+
+private:
+ Ui::ManagerPage *m_manager;
+};
+
+
+class PyQtPluginSettingsUpdateRepeater : public QObject
+{
+ Q_OBJECT
+
+Q_SIGNALS:
+ void settingsUpdated();
+
+public Q_SLOTS:
+ void updateSettings() {
+ Q_EMIT settingsUpdated();
+ }
+};
+
+
+class PyQtPluginSettingsFactory : public KisAbstractPreferenceSetFactory
+{
+public:
+
+ PyQtPluginSettingsFactory(PyKrita::Engine *engine) {
+ m_engine = engine;
+ }
+
+ KisPreferenceSet* createPreferenceSet() {
+ PyQtPluginSettings* ps = new PyQtPluginSettings(m_engine);
+ QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, SLOT(updateSettings()), Qt::UniqueConnection);
+ return ps;
+ }
+ virtual QString id() const {
+ return "PyQtSettings";
+ }
+ PyQtPluginSettingsUpdateRepeater repeater;
+ PyKrita::Engine *m_engine;
+};
+
+
+
+
+#endif // PYQTPLUGINSETTINGS_H
diff --git a/plugins/extensions/pykrita/plugin/test/CMakeLists.txt b/plugins/extensions/pykrita/plugin/test/CMakeLists.txt
new file mode 100644
index 0000000000..9d9f135b5f
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/test/CMakeLists.txt
@@ -0,0 +1,13 @@
+qt5_wrap_cpp(UNIT_TESTS_HEADERS_MOC version_checker_test.h)
+
+add_executable(
+ unit_tests
+ version_checker_test.cpp
+ ${UNIT_TESTS_HEADERS_MOC}
+ )
+
+target_link_libraries(
+ unit_tests
+ ${QT_QTTEST_LIBRARY_RELEASE}
+ ${QT_QTCORE_LIBRARY_RELEASE}
+ )
diff --git a/plugins/extensions/pykrita/plugin/test/version_checker_test.cpp b/plugins/extensions/pykrita/plugin/test/version_checker_test.cpp
new file mode 100644
index 0000000000..98c6b4bf54
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/test/version_checker_test.cpp
@@ -0,0 +1,184 @@
+/*
+ * 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>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "version_checker_test.h"
+#include "../version_checker.h"
+
+using namespace PyKrita;
+
+void version_checker_tests::version_ctor_test()
+{
+ {
+ version v;
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(0);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(0, 0);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(0, 0, 0);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(1);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(1, 2);
+ QVERIFY(v.isValid());
+ }
+ {
+ version v(1, 2, 3);
+ QVERIFY(v.isValid());
+ }
+}
+
+void version_checker_tests::version_ops_test()
+{
+ {
+ version v1;
+ version v2;
+ QVERIFY(v1 == v2);
+ }
+ {
+ version v1;
+ version v2(1);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 0, 1);
+ version v2(0, 0, 2);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 0, 1);
+ version v2(0, 1, 0);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 0, 1);
+ version v2(1, 0, 0);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+ {
+ version v1(0, 1, 0);
+ version v2(1, 0, 0);
+ QVERIFY(v1 < v2);
+ QVERIFY(v2 > v1);
+ QVERIFY(v1 != v2);
+ QVERIFY(v1 <= v2);
+ QVERIFY(v2 >= v1);
+ }
+}
+
+void version_checker_tests::version_string_test()
+{
+ QCOMPARE(QString(version(10)), QString("10.0.0"));
+ QCOMPARE(QString(version(10, 200)), QString("10.200.0"));
+ QCOMPARE(QString(version(1, 2, 3)), QString("1.2.3"));
+ QCOMPARE(QString(version(11, 222, 3333)), QString("11.222.3333"));
+}
+
+void version_checker_tests::version_checker_test()
+{
+ {
+ version rhs;
+ version_checker chkr(version_checker::equal);
+ QVERIFY(chkr.isValid());
+ chkr.bind_second(rhs);
+ QVERIFY(chkr.isValid());
+ QVERIFY(chkr(version()));
+ }
+ {
+ version rhs(1);
+ version_checker chkr(version_checker::less);
+ QVERIFY(chkr.isValid());
+ chkr.bind_second(rhs);
+ QVERIFY(chkr.isValid());
+ QVERIFY(chkr(version(0, 1)));
+ }
+ {
+ version rhs(1);
+ version_checker chkr(version_checker::greather);
+ QVERIFY(chkr.isValid());
+ chkr.bind_second(rhs);
+ QVERIFY(chkr.isValid());
+ QVERIFY(chkr(version(1, 1)));
+ }
+}
+
+void version_checker_tests::version_checker_string_test()
+{
+ {
+ version_checker chkr = version_checker::fromString(">0.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("0.0.1"));
+ QVERIFY(chkr(version(1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString(">=0.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("0.0.1"));
+ QVERIFY(chkr(version(0, 0, 1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString("<=1.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("1.0.1"));
+ QVERIFY(chkr(version(0, 0, 1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString("=1.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("1.0.1"));
+ QVERIFY(chkr(version(1, 0, 1)));
+ }
+ {
+ version_checker chkr = version_checker::fromString("1.0.1");
+ QVERIFY(chkr.isValid());
+ QCOMPARE(QString(chkr.required()), QString("1.0.1"));
+ QVERIFY(chkr(version(1, 0, 1)));
+ }
+}
+
+QTEST_MAIN(version_checker_tests)
diff --git a/plugins/extensions/pykrita/plugin/test/version_checker_test.h b/plugins/extensions/pykrita/plugin/test/version_checker_test.h
new file mode 100644
index 0000000000..eef257b1db
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/test/version_checker_test.h
@@ -0,0 +1,41 @@
+/*
+ * 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>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __VERSION_CHECKER_TEST_H__
+# define __VERSION_CHECKER_TEST_H__
+
+#include <QtTest/QtTest>
+
+class version_checker_tests : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void version_ctor_test();
+ void version_ops_test();
+ void version_string_test();
+
+ void version_checker_test();
+ void version_checker_string_test();
+};
+
+#endif // __VERSION_CHECKER_TEST_H__
diff --git a/plugins/extensions/pykrita/plugin/utilities.cpp b/plugins/extensions/pykrita/plugin/utilities.cpp
new file mode 100644
index 0000000000..b5de838e0c
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/utilities.cpp
@@ -0,0 +1,525 @@
+// 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 <algorithm>
+
+#include <cmath>
+#include <Python.h>
+
+
+#include <QLibrary>
+#include <QString>
+#include <QStringList>
+
+#include <kconfigbase.h>
+#include <kconfiggroup.h>
+#include <klocalizedstring.h>
+
+#include <kis_debug.h>
+
+#define THREADED 1
+
+namespace PyKrita
+{
+namespace
+{
+QLibrary* s_pythonLibrary = 0;
+PyThreadState* s_pythonThreadState = 0;
+} // anonymous namespace
+
+const char* Python::PYKRITA_ENGINE = "pykrita";
+
+Python::Python()
+{
+#if THREADED
+ m_state = PyGILState_Ensure();
+#endif
+}
+
+Python::~Python()
+{
+#if THREADED
+ PyGILState_Release(m_state);
+#endif
+}
+
+bool Python::prependStringToList(PyObject* const list, const QString& value)
+{
+ PyObject* const u = unicode(value);
+ bool result = !PyList_Insert(list, 0, u);
+ Py_DECREF(u);
+ if (!result)
+ traceback(QString("Failed to prepend %1").arg(value));
+ return result;
+}
+
+bool Python::functionCall(const char* const functionName, const char* const moduleName)
+{
+ PyObject* const result = functionCall(functionName, moduleName, PyTuple_New(0));
+ if (result)
+ Py_DECREF(result);
+ return bool(result);
+}
+
+PyObject* Python::functionCall(
+ const char* const functionName
+ , const char* const moduleName
+ , PyObject* const arguments
+)
+{
+ if (!arguments) {
+ errScript << "Missing arguments for" << moduleName << functionName;
+ return 0;
+ }
+ PyObject* const func = itemString(functionName, moduleName);
+ if (!func) {
+ errScript << "Failed to resolve" << moduleName << functionName;
+ return 0;
+ }
+ if (!PyCallable_Check(func)) {
+ traceback(QString("Not callable %1.%2").arg(moduleName).arg(functionName));
+ return 0;
+ }
+ PyObject* const result = PyObject_CallObject(func, arguments);
+ Py_DECREF(arguments);
+ if (!result)
+ traceback(QString("No result from %1.%2").arg(moduleName).arg(functionName));
+
+ return result;
+}
+
+bool Python::itemStringDel(const char* const item, const char* const moduleName)
+{
+ PyObject* const dict = moduleDict(moduleName);
+ const bool result = dict && PyDict_DelItemString(dict, item);
+ if (!result)
+ traceback(QString("Could not delete item string %1.%2").arg(moduleName).arg(item));
+ return result;
+}
+
+PyObject* Python::itemString(const char* const item, const char* const moduleName)
+{
+ if (PyObject* const value = itemString(item, moduleDict(moduleName)))
+ return value;
+
+ errScript << "Could not get item string" << moduleName << item;
+ return 0;
+}
+
+PyObject* Python::itemString(const char* item, PyObject* dict)
+{
+ if (dict)
+ if (PyObject* const value = PyDict_GetItemString(dict, item))
+ return value;
+ traceback(QString("Could not get item string %1").arg(item));
+ return 0;
+}
+
+bool Python::itemStringSet(const char* const item, PyObject* const value, const char* const moduleName)
+{
+ PyObject* const dict = moduleDict(moduleName);
+ const bool result = dict && !PyDict_SetItemString(dict, item, value);
+ if (!result)
+ traceback(QString("Could not set item string %1.%2").arg(moduleName).arg(item));
+ return result;
+}
+
+PyObject* Python::kritaHandler(const char* const moduleName, const char* const handler)
+{
+ if (PyObject* const module = moduleImport(moduleName))
+ return functionCall(handler, "krita", Py_BuildValue("(O)", module));
+ return 0;
+}
+
+QString Python::lastTraceback() const
+{
+ QString result;
+ result.swap(m_traceback);
+ return result;
+}
+
+void Python::libraryLoad()
+{
+ if (!s_pythonLibrary) {
+ dbgScript << "Creating s_pythonLibrary" << PYKRITA_PYTHON_LIBRARY;
+ s_pythonLibrary = new QLibrary(PYKRITA_PYTHON_LIBRARY);
+ if (!s_pythonLibrary)
+ errScript << "Could not create" << PYKRITA_PYTHON_LIBRARY;
+
+ s_pythonLibrary->setLoadHints(QLibrary::ExportExternalSymbolsHint);
+ if (!s_pythonLibrary->load())
+ errScript << "Could not load" << PYKRITA_PYTHON_LIBRARY;
+
+ Py_InitializeEx(0);
+ if (!Py_IsInitialized())
+ errScript << "Could not initialise" << PYKRITA_PYTHON_LIBRARY;
+#if THREADED
+ PyEval_InitThreads();
+ s_pythonThreadState = PyGILState_GetThisThreadState();
+ PyEval_ReleaseThread(s_pythonThreadState);
+#endif
+ }
+}
+
+void Python::libraryUnload()
+{
+ if (s_pythonLibrary) {
+ // Shut the interpreter down if it has been started.
+ if (Py_IsInitialized()) {
+#if THREADED
+ PyEval_AcquireThread(s_pythonThreadState);
+#endif
+ //Py_Finalize();
+ }
+ if (s_pythonLibrary->isLoaded()) {
+ s_pythonLibrary->unload();
+ }
+ delete s_pythonLibrary;
+ s_pythonLibrary = 0;
+ }
+}
+
+PyObject* Python::moduleActions(const char* moduleName)
+{
+ return kritaHandler(moduleName, "moduleGetActions");
+}
+
+PyObject* Python::moduleConfigPages(const char* const moduleName)
+{
+ return kritaHandler(moduleName, "moduleGetConfigPages");
+}
+
+QString Python::moduleHelp(const char* moduleName)
+{
+ QString r;
+ PyObject* const result = kritaHandler(moduleName, "moduleGetHelp");
+ if (result) {
+ r = unicode(result);
+ Py_DECREF(result);
+ }
+ return r;
+}
+
+PyObject* Python::moduleDict(const char* const moduleName)
+{
+ PyObject* const module = moduleImport(moduleName);
+ if (module)
+ if (PyObject* const dictionary = PyModule_GetDict(module))
+ return dictionary;
+
+ traceback(QString("Could not get dict %1").arg(moduleName));
+ return 0;
+}
+
+PyObject* Python::moduleImport(const char* const moduleName)
+{
+ PyObject* const module = PyImport_ImportModule(moduleName);
+ if (module)
+ return module;
+
+ traceback(QString("Could not import %1").arg(moduleName));
+ return 0;
+}
+
+void* Python::objectUnwrap(PyObject* o)
+{
+ PyObject* const arguments = Py_BuildValue("(O)", o);
+ PyObject* const result = functionCall("unwrapinstance", "sip", arguments);
+ if (!result)
+ return 0;
+
+ void* const r = reinterpret_cast<void*>(ptrdiff_t(PyLong_AsLongLong(result)));
+ Py_DECREF(result);
+ return r;
+}
+
+PyObject* Python::objectWrap(void* const o, const QString& fullClassName)
+{
+ const QString classModuleName = fullClassName.section('.', 0, -2);
+ const QString className = fullClassName.section('.', -1);
+ PyObject* const classObject = itemString(PQ(className), PQ(classModuleName));
+ if (!classObject)
+ return 0;
+
+ PyObject* const arguments = Py_BuildValue("NO", PyLong_FromVoidPtr(o), classObject);
+ PyObject* const result = functionCall("wrapinstance", "sip", arguments);
+
+ return result;
+}
+
+// Inspired by http://www.gossamer-threads.com/lists/python/python/150924.
+void Python::traceback(const QString& description)
+{
+ m_traceback.clear();
+ if (!PyErr_Occurred())
+ // Return an empty string on no error.
+ // NOTE "Return a string?" really??
+ return;
+
+ PyObject* exc_typ;
+ PyObject* exc_val;
+ PyObject* exc_tb;
+ PyErr_Fetch(&exc_typ, &exc_val, &exc_tb);
+ PyErr_NormalizeException(&exc_typ, &exc_val, &exc_tb);
+
+ // Include the traceback.
+ if (exc_tb) {
+ m_traceback = "Traceback (most recent call last):\n";
+ PyObject* const arguments = PyTuple_New(1);
+ PyTuple_SetItem(arguments, 0, exc_tb);
+ PyObject* const result = functionCall("format_tb", "traceback", arguments);
+ if (result) {
+ for (int i = 0, j = PyList_Size(result); i < j; i++) {
+ PyObject* const tt = PyList_GetItem(result, i);
+ PyObject* const t = Py_BuildValue("(O)", tt);
+ char* buffer;
+ if (!PyArg_ParseTuple(t, "s", &buffer))
+ break;
+ m_traceback += buffer;
+ }
+ Py_DECREF(result);
+ }
+ Py_DECREF(exc_tb);
+ }
+
+ // Include the exception type and value.
+ if (exc_typ) {
+ PyObject* const temp = PyObject_GetAttrString(exc_typ, "__name__");
+ if (temp) {
+ m_traceback += unicode(temp);
+ m_traceback += ": ";
+ }
+ Py_DECREF(exc_typ);
+ }
+
+ if (exc_val) {
+ PyObject* const temp = PyObject_Str(exc_val);
+ if (temp) {
+ m_traceback += unicode(temp);
+ m_traceback += "\n";
+ }
+ Py_DECREF(exc_val);
+ }
+ m_traceback += description;
+
+ QStringList l = m_traceback.split("\n");
+ Q_FOREACH(const QString &s, l) {
+ errScript << s;
+ }
+ /// \todo How about to show it somewhere else than "console output"?
+}
+
+PyObject* Python::unicode(const QString& string)
+{
+#if PY_MAJOR_VERSION < 3
+ /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */
+ PyObject* s = PyString_FromString(PQ(string));
+ PyObject* u = PyUnicode_FromEncodedObject(s, "utf-8", "strict");
+ Py_DECREF(s);
+ return u;
+#elif PY_MINOR_VERSION < 3
+ /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */
+# ifdef Py_UNICODE_WIDE
+ return PyUnicode_DecodeUTF16((const char*)string.constData(), string.length() * 2, 0, 0);
+# else
+ return PyUnicode_FromUnicode(string.constData(), string.length());
+# endif
+#else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */
+ return PyUnicode_FromKindAndData(PyUnicode_2BYTE_KIND, string.constData(), string.length());
+#endif
+}
+
+QString Python::unicode(PyObject* const string)
+{
+#if PY_MAJOR_VERSION < 3
+ /* Python 2.x. http://docs.python.org/2/c-api/unicode.html */
+ if (PyString_Check(string))
+ return QString(PyString_AsString(string));
+ else if (PyUnicode_Check(string)) {
+ const int unichars = PyUnicode_GetSize(string);
+# ifdef HAVE_USABLE_WCHAR_T
+ return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars);
+# else
+# ifdef Py_UNICODE_WIDE
+ return QString::fromUcs4((const unsigned int*)PyUnicode_AsUnicode(string), unichars);
+# else
+ return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars);
+# endif
+# endif
+ } else return QString();
+#elif PY_MINOR_VERSION < 3
+ /* Python 3.2 or less. http://docs.python.org/3.2/c-api/unicode.html#unicode-objects */
+ if (!PyUnicode_Check(string))
+ return QString();
+
+ const int unichars = PyUnicode_GetSize(string);
+# ifdef HAVE_USABLE_WCHAR_T
+ return QString::fromWCharArray(PyUnicode_AsUnicode(string), unichars);
+# else
+# ifdef Py_UNICODE_WIDE
+ return QString::fromUcs4(PyUnicode_AsUnicode(string), unichars);
+# else
+ return QString::fromUtf16(PyUnicode_AsUnicode(string), unichars);
+# endif
+# endif
+#else /* Python 3.3 or greater. http://docs.python.org/3.3/c-api/unicode.html#unicode-objects */
+ if (!PyUnicode_Check(string))
+ return QString();
+
+ const int unichars = PyUnicode_GetLength(string);
+ if (0 != PyUnicode_READY(string))
+ return QString();
+
+ switch (PyUnicode_KIND(string)) {
+ case PyUnicode_1BYTE_KIND:
+ return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(string), unichars);
+ case PyUnicode_2BYTE_KIND:
+ return QString::fromUtf16(PyUnicode_2BYTE_DATA(string), unichars);
+ case PyUnicode_4BYTE_KIND:
+ return QString::fromUcs4(PyUnicode_4BYTE_DATA(string), unichars);
+ default:
+ break;
+ }
+ return QString();
+#endif
+}
+
+bool Python::isUnicode(PyObject* const string)
+{
+#if PY_MAJOR_VERSION < 3
+ return PyString_Check(string) || PyUnicode_Check(string);
+#else
+ return PyUnicode_Check(string);
+#endif
+}
+
+void Python::updateConfigurationFromDictionary(KConfigBase* const config, PyObject* const dictionary)
+{
+ PyObject* groupKey;
+ PyObject* groupDictionary;
+ Py_ssize_t position = 0;
+ while (PyDict_Next(dictionary, &position, &groupKey, &groupDictionary)) {
+ if (!isUnicode(groupKey)) {
+ traceback(QString("Configuration group name not a string"));
+ continue;
+ }
+ QString groupName = unicode(groupKey);
+ if (!PyDict_Check(groupDictionary)) {
+ traceback(QString("Configuration group %1 top level key not a dictionary").arg(groupName));
+ continue;
+ }
+
+ // There is a group per module.
+ KConfigGroup group = config->group(groupName);
+ PyObject* key;
+ PyObject* value;
+ Py_ssize_t x = 0;
+ while (PyDict_Next(groupDictionary, &x, &key, &value)) {
+ if (!isUnicode(key)) {
+ traceback(QString("Configuration group %1 itemKey not a string").arg(groupName));
+ continue;
+ }
+ PyObject* arguments = Py_BuildValue("(Oi)", value, 0);
+ PyObject* pickled = functionCall("dumps", "pickle", arguments);
+ if (pickled) {
+#if PY_MAJOR_VERSION < 3
+ QString ascii(unicode(pickled));
+#else
+ QString ascii(PyBytes_AsString(pickled));
+#endif
+ group.writeEntry(unicode(key), ascii);
+ Py_DECREF(pickled);
+ } else {
+ errScript << "Cannot write" << groupName << unicode(key) << unicode(PyObject_Str(value));
+ }
+ }
+ }
+}
+
+void Python::updateDictionaryFromConfiguration(PyObject* const dictionary, const KConfigBase* const config)
+{
+ qDebug() << config->groupList();
+ Q_FOREACH(QString groupName, config->groupList()) {
+ KConfigGroup group = config->group(groupName);
+ PyObject* groupDictionary = PyDict_New();
+ PyDict_SetItemString(dictionary, PQ(groupName), groupDictionary);
+ Q_FOREACH(QString key, group.keyList()) {
+ QString pickled = group.readEntry(key);
+#if PY_MAJOR_VERSION < 3
+ PyObject* arguments = Py_BuildValue("(s)", PQ(pickled));
+#else
+ PyObject* arguments = Py_BuildValue("(y)", PQ(pickled));
+#endif
+ PyObject* value = functionCall("loads", "pickle", arguments);
+ if (value) {
+ PyDict_SetItemString(groupDictionary, PQ(key), value);
+ Py_DECREF(value);
+ } else {
+ errScript << "Cannot read" << groupName << key << pickled;
+ }
+ }
+ Py_DECREF(groupDictionary);
+ }
+}
+
+bool Python::prependPythonPaths(const QString& path)
+{
+ PyObject* sys_path = itemString("path", "sys");
+ return bool(sys_path) && prependPythonPaths(path, sys_path);
+}
+
+bool Python::prependPythonPaths(const QStringList& paths)
+{
+ PyObject* sys_path = itemString("path", "sys");
+ if (!sys_path)
+ return false;
+
+ /// \todo Heh, boosts' range adaptors would be good here!
+ QStringList reversed_paths;
+ std::reverse_copy(
+ paths.begin()
+ , paths.end()
+ , std::back_inserter(reversed_paths)
+ );
+
+ Q_FOREACH(const QString & path, reversed_paths)
+ if (!prependPythonPaths(path, sys_path))
+ return false;
+
+ return true;
+}
+
+bool Python::prependPythonPaths(const QString& path, PyObject* sys_path)
+{
+ Q_ASSERT("Dir entry expected to be valid" && sys_path);
+ return bool(prependStringToList(sys_path, path));
+}
+
+} // namespace PyKrita
+
+// krita: indent-width 4;
diff --git a/plugins/extensions/pykrita/plugin/utilities.h b/plugins/extensions/pykrita/plugin/utilities.h
new file mode 100644
index 0000000000..5e60410c80
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/utilities.h
@@ -0,0 +1,229 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// A couple of useful macros and functions used inside of pykrita_engine.cpp and pykrita_plugin.cpp.
+//
+// Copyright (C) 2006 Paul Giannaros <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/>.
+//
+
+#ifndef __PYKRITA_UTILITIES_H__
+# define __PYKRITA_UTILITIES_H__
+
+#include <cmath>
+#include <Python.h>
+#include <QString>
+
+class KConfigBase;
+
+/// Save us some ruddy time when printing out QStrings with UTF-8
+# define PQ(x) x.toUtf8().constData()
+
+namespace PyKrita
+{
+
+/**
+ * Instantiate this class on the stack to automatically get and release the
+ * GIL.
+ *
+ * Also, making all the utility functions members of this class means that in
+ * many cases the compiler tells us where the class in needed. In the remaining
+ * cases (i.e. bare calls to the Python C API), inspection is used to needed
+ * to add the requisite Python() object. To prevent this object being optimised
+ * away in these cases due to lack of use, all instances have the form of an
+ * assignment, e.g.:
+ *
+ * Python py = Python()
+ *
+ * This adds a little overhead, but this is a small price for consistency.
+ */
+class Python
+{
+public:
+ Python();
+ ~Python();
+
+ /**
+ * Load the Python interpreter.
+ */
+ static void libraryLoad();
+
+ /**
+ * Unload the Python interpreter.
+ */
+ static void libraryUnload();
+
+ /// Convert a QString to a Python unicode object.
+ static PyObject* unicode(const QString& string);
+
+ /// Convert a Python unicode object to a QString.
+ static QString unicode(PyObject* string);
+
+ /// Test if a Python object is compatible with a QString.
+ static bool isUnicode(PyObject* string);
+
+ /// Prepend a QString to a list as a Python unicode object
+ bool prependStringToList(PyObject* list, const QString& value);
+
+ /**
+ * Print and save (see @ref lastTraceback()) the current traceback in a
+ * form approximating what Python would print:
+ *
+ * Traceback (most recent call last):
+ * File "/home/shahhaqu/.kde/share/apps/krita/pykrita/pluginmgr.py", line 13, in <module>
+ * import kdeui
+ * ImportError: No module named kdeui
+ * Could not import pluginmgr.
+ */
+ void traceback(const QString& description);
+
+ /**
+ * Store the last traceback we handled using @ref traceback().
+ */
+ QString lastTraceback(void) const;
+
+ /**
+ * Create a Python dictionary from a KConfigBase instance, writing the
+ * string representation of the values.
+ */
+ void updateDictionaryFromConfiguration(PyObject* dictionary, const KConfigBase* config);
+
+ /**
+ * Write a Python dictionary to a configuration object, converting objects
+ * to their string representation along the way.
+ */
+ void updateConfigurationFromDictionary(KConfigBase* config, PyObject* dictionary);
+
+ /**
+ * Call the named module's named entry point.
+ */
+ bool functionCall(const char* functionName, const char* moduleName = PYKRITA_ENGINE);
+ PyObject* functionCall(const char* functionName, const char* moduleName, PyObject* arguments);
+
+ /**
+ * Delete the item from the named module's dictionary.
+ */
+ bool itemStringDel(const char* item, const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the item from the named module's dictionary.
+ *
+ * @return 0 or a borrowed reference to the item.
+ */
+ PyObject* itemString(const char* item, const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the item from the given dictionary.
+ *
+ * @return 0 or a borrowed reference to the item.
+ */
+ PyObject* itemString(const char* item, PyObject* dict);
+
+ /**
+ * Set the item in the named module's dictionary.
+ */
+ bool itemStringSet(const char* item, PyObject* value, const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the Actions defined by a module. The returned object is
+ * [ { function, ( text, icon, shortcut, menu ) }... ] for each module
+ * function decorated with @action.
+ *
+ * @return 0 or a new reference to the result.
+ */
+ PyObject* moduleActions(const char* moduleName);
+
+ /**
+ * Get the ConfigPages defined by a module. The returned object is
+ * [ { function, callable, ( name, fullName, icon ) }... ] for each module
+ * function decorated with @configPage.
+ *
+ * @return 0 or a new reference to the result.
+ */
+ PyObject* moduleConfigPages(const char* moduleName);
+
+ /**
+ * Get the named module's dictionary.
+ *
+ * @return 0 or a borrowed reference to the dictionary.
+ */
+ PyObject* moduleDict(const char* moduleName = PYKRITA_ENGINE);
+
+ /**
+ * Get the help text defined by a module.
+ */
+ QString moduleHelp(const char* moduleName);
+
+ /**
+ * Import the named module.
+ *
+ * @return 0 or a borrowed reference to the module.
+ */
+ PyObject* moduleImport(const char* moduleName);
+
+ /**
+ * A void * for an arbitrary Qt/KDE object that has been wrapped by SIP. Nifty.
+ *
+ * @param o The object to be unwrapped. The reference is borrowed.
+ */
+ void* objectUnwrap(PyObject* o);
+
+ /**
+ * A PyObject * for an arbitrary Qt/KDE object using SIP wrapping. Nifty.
+ *
+ * @param o The object to be wrapped.
+ * @param className The full class name of o, e.g. "PyQt5.QtWidgets.QWidget".
+ * @return @c 0 or a new reference to the object.
+ */
+ PyObject* objectWrap(void* o, const QString& className);
+
+ /**
+ * Add a given path to to the front of \c PYTHONPATH
+ *
+ * @param path A string (path) to be added
+ * @return @c true on success, @c false otherwise.
+ */
+ bool prependPythonPaths(const QString& path);
+
+ /**
+ * Add listed paths to to the front of \c PYTHONPATH
+ *
+ * @param paths A string list (paths) to be added
+ * @return @c true on success, @c false otherwise.
+ */
+ bool prependPythonPaths(const QStringList& paths);
+
+ static const char* PYKRITA_ENGINE;
+
+private:
+ /// @internal Helper function for @c prependPythonPaths overloads
+ bool prependPythonPaths(const QString&, PyObject*);
+ PyGILState_STATE m_state;
+ mutable QString m_traceback;
+
+ /**
+ * Run a handler function supplied by the krita module on another module.
+ *
+ * @return 0 or a new reference to the result.
+ */
+ PyObject* kritaHandler(const char* moduleName, const char* handler);
+};
+
+} // namespace PyKrita
+#endif // __PYKRITA_UTILITIES_H__
+// krita: indent-width 4;
diff --git a/plugins/extensions/pykrita/plugin/version_checker.h b/plugins/extensions/pykrita/plugin/version_checker.h
new file mode 100644
index 0000000000..bfb9c2c5fb
--- /dev/null
+++ b/plugins/extensions/pykrita/plugin/version_checker.h
@@ -0,0 +1,279 @@
+// This file is part of PyKrita, Krita' Python scripting plugin.
+//
+// Copyright (C) 2013 Alex Turbov <i.zaufi@gmail.com>
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) version 3.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public License
+// along with this library; see the file COPYING.LIB. If not, write to
+// the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+// Boston, MA 02110-1301, USA.
+
+#ifndef __VERSION_CHECKER_H__
+# define __VERSION_CHECKER_H__
+
+# include <QtCore/QString>
+# include <QtCore/QStringList>
+# include <QtCore/QtGlobal>
+
+namespace PyKrita
+{
+
+/**
+ * \brief Class \c version
+ */
+class version
+{
+ enum type {
+ undefined = -1
+ , zero = 0
+ };
+
+public:
+ /// Default constructor
+ explicit version(
+ const int major = zero
+ , const int minor = zero
+ , const int patch = zero
+ )
+ : m_major(major)
+ , m_minor(minor)
+ , m_patch(patch) {
+ }
+
+ int major() const {
+ return m_major;
+ }
+ int minor() const {
+ return m_minor;
+ }
+ int patch() const {
+ return m_patch;
+ }
+
+ bool isValid() const {
+ return major() != undefined && minor() != undefined && patch() != undefined;
+ }
+
+ operator QString() const {
+ return QString("%1.%2.%3").arg(major()).arg(minor()).arg(patch());
+ }
+
+ static version fromString(const QString& version_str) {
+ int tmp[3] = {zero, zero, zero};
+ QStringList parts = version_str.split('.');
+ for (
+ unsigned long i = 0
+ ; i < qMin(static_cast<unsigned long>(sizeof(tmp) / sizeof(int)), static_cast<unsigned long>(parts.size()))
+ ; ++i
+ ) {
+ bool ok;
+ const int num = parts[i].toInt(&ok);
+ if (ok)
+ tmp[i] = num;
+ else {
+ tmp[i] = undefined;
+ break;
+ }
+ }
+ return version(tmp[0], tmp[1], tmp[2]);
+ };
+
+ static version invalid() {
+ static version s_bad(undefined, undefined, undefined);
+ return s_bad;
+ }
+
+private:
+ int m_major;
+ int m_minor;
+ int m_patch;
+};
+
+inline bool operator==(const version& left, const version& right)
+{
+ return left.major() == right.major()
+ && left.minor() == right.minor()
+ && left.patch() == right.patch()
+ ;
+}
+
+inline bool operator!=(const version& left, const version& right)
+{
+ return !(left == right);
+}
+
+inline bool operator<(const version& left, const version& right)
+{
+ return left.major() < right.major()
+ || (left.major() == right.major() && left.minor() < right.minor())
+ || (left.major() == right.major() && left.minor() == right.minor() && left.patch() < right.patch())
+ ;
+}
+
+inline bool operator>(const version& left, const version& right)
+{
+ return left.major() > right.major()
+ || (left.major() == right.major() && left.minor() > right.minor())
+ || (left.major() == right.major() && left.minor() == right.minor() && left.patch() > right.patch())
+ ;
+}
+
+inline bool operator<=(const version& left, const version& right)
+{
+ return left == right || left < right;
+}
+
+inline bool operator>=(const version& left, const version& right)
+{
+ return left == right || left > right;
+}
+
+
+/**
+* \brief Class \c version_checker
+*/
+class version_checker
+{
+public:
+ enum operation {
+ invalid
+ , undefined
+ , less
+ , less_or_equal
+ , greather
+ , greather_or_equal
+ , not_equal
+ , equal
+ , last__
+ };
+
+ /// Default constructor
+ explicit version_checker(const operation op = invalid)
+ : m_op(op) {
+ }
+
+ bool isValid() const {
+ return m_op != invalid;
+ }
+
+ bool isEmpty() const {
+ return m_op == undefined;
+ }
+
+ void bind_second(const version& rhs) {
+ m_rhs = rhs;
+ }
+
+ bool operator()(const version& left) {
+ switch (m_op) {
+ case less:
+ return left < m_rhs;
+ case greather:
+ return left > m_rhs;
+ case equal:
+ return left == m_rhs;
+ case not_equal:
+ return left != m_rhs;
+ case less_or_equal:
+ return left <= m_rhs;
+ case greather_or_equal:
+ return left >= m_rhs;
+ default:
+ Q_ASSERT(!"Sanity check");
+ break;
+ }
+ return false;
+ }
+
+ version required() const {
+ return m_rhs;
+ }
+
+ QString operationToString() const {
+ QString result;
+ switch (m_op) {
+ case less:
+ result = " < ";
+ break;
+ case greather:
+ result = " > ";
+ break;
+ case equal:
+ result = " = ";
+ break;
+ case not_equal:
+ result = " != ";
+ break;
+ case less_or_equal:
+ result = " <= ";
+ break;
+ case greather_or_equal:
+ result = " >= ";
+ break;
+ default:
+ Q_ASSERT(!"Sanity check");
+ break;
+ }
+ return result;
+ }
+
+ static version_checker fromString(const QString& version_info) {
+ version_checker checker(invalid);
+ if (version_info.isEmpty())
+ return checker;
+
+ bool lookup_next_char = false;
+ int strip_lead_pos = 0;
+ switch (version_info.at(0).toAscii()) {
+ case '<':
+ checker.m_op = less;
+ lookup_next_char = true;
+ break;
+ case '>':
+ checker.m_op = greather;
+ lookup_next_char = true;
+ break;
+ case '=':
+ strip_lead_pos = 1;
+ checker.m_op = equal;
+ break;
+ default:
+ strip_lead_pos = 0;
+ checker.m_op = equal;
+ break;
+ }
+ if (lookup_next_char) {
+ if (version_info.at(1).toAscii() == '=') {
+ // NOTE Shift state
+ checker.m_op = operation(int(checker.m_op) + 1);
+ strip_lead_pos = 2;
+ } else {
+ strip_lead_pos = 1;
+ }
+ }
+ //
+ QString rhs_str = version_info.mid(strip_lead_pos).trimmed();
+ version rhs = version::fromString(rhs_str);
+ if (rhs.isValid())
+ checker.bind_second(rhs);
+ else
+ checker.m_op = invalid;
+ return checker;
+ }
+
+private:
+ operation m_op;
+ version m_rhs;
+};
+
+} // namespace PyKrita
+#endif // __VERSION_CHECKER_H__
diff --git a/plugins/extensions/pykrita/readme.txt b/plugins/extensions/pykrita/readme.txt
new file mode 100644
index 0000000000..a6a45c0c3c
--- /dev/null
+++ b/plugins/extensions/pykrita/readme.txt
@@ -0,0 +1,5 @@
+Useful links:
+
+https://github.com/pyqt
+https://github.com/pyqt/examples
+https://github.com/aoloe/scribus-plugin-scripter
diff --git a/plugins/extensions/pykrita/sip/CMakeLists.txt b/plugins/extensions/pykrita/sip/CMakeLists.txt
new file mode 100644
index 0000000000..9b3a12794d
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/CMakeLists.txt
@@ -0,0 +1,16 @@
+include(SIPMacros)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../libkis)
+
+set(SIP_INCLUDES ${PYQT_SIP_DIR_OVERRIDE} ./krita)
+set(SIP_CONCAT_PARTS 1)
+set(SIP_TAGS ALL WS_X11 ${PYQT5_VERSION_TAG})
+set(SIP_EXTRA_OPTIONS -g -x PyKDE_QVector)
+
+set(PYTHON_SITE_PACKAGES_INSTALL_DIR ${DATA_INSTALL_DIR}/krita/pykrita/)
+add_sip_python_module(PyKrita.krita ./krita/kritamod.sip kritalibkis kritaui kritaimage kritalibbrush)
+
+#install(FILES
+# ./__init__.py
+# DESTINATION ${PYTHON_SITE_PACKAGES_INSTALL_DIR})
+
diff --git a/plugins/extensions/pykrita/sip/filename b/plugins/extensions/pykrita/sip/filename
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/extensions/pykrita/sip/krita/Action.sip b/plugins/extensions/pykrita/sip/krita/Action.sip
new file mode 100644
index 0000000000..d33fdddf54
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Action.sip
@@ -0,0 +1,35 @@
+class Action : QObject
+{
+%TypeHeaderCode
+#include "Action.h"
+%End
+public:
+ Action(QObject* parent /TransferThis/ = 0);
+ Action(const QString & name, QAction* action, QObject* parent /TransferThis/ = 0);
+ virtual ~Action();
+ bool operator==(const Action &other) const;
+ bool operator!=(const Action &other) const;
+public Q_SLOTS:
+ QString text() const;
+ void settext(QString text);
+ QString name() const;
+ void setName(QString value);
+ bool isCheckable() const;
+ void setCheckable(bool value);
+ bool isChecked() const;
+ void setChecked(bool value);
+ QString shortcut() const;
+ void setShortcut(QString value);
+ bool isVisible() const;
+ void setVisible(bool value);
+ bool isEnabled() const;
+ void setEnabled(bool value);
+ void setToolTip(QString tooltip);
+ void trigger();
+Q_SIGNALS:
+ void triggered(bool);
+private:
+ private:
+ Action(const Action &); // Generated
+};
+
diff --git a/plugins/extensions/pykrita/sip/krita/Canvas.sip b/plugins/extensions/pykrita/sip/krita/Canvas.sip
new file mode 100644
index 0000000000..7eed0f2a71
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Canvas.sip
@@ -0,0 +1,26 @@
+class Canvas : QObject
+{
+%TypeHeaderCode
+#include "Canvas.h"
+%End
+ Canvas(const Canvas & __0);
+public:
+ bool operator==(const Canvas &other) const;
+ bool operator!=(const Canvas &other) const;
+public Q_SLOTS:
+ qreal zoomLevel() const;
+ void setZoomLevel(qreal value);
+ void resetZoom();
+ void resetRotation();
+ qreal rotation() const;
+ void setRotation(qreal angle);
+ bool mirror() const;
+ void setMirror(bool value);
+ void setWrapAroundMode(bool enable);
+ bool wrapAroundMode() const;
+ void setLevelOfDetailMode(bool enable);
+ bool levelOfDetailMode() const;
+ View *view() const /Factory/;
+Q_SIGNALS:
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Channel.sip b/plugins/extensions/pykrita/sip/krita/Channel.sip
new file mode 100644
index 0000000000..d434402ea1
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Channel.sip
@@ -0,0 +1,21 @@
+class Channel : QObject
+{
+%TypeHeaderCode
+#include "Channel.h"
+%End
+ Channel(const Channel & __0);
+public:
+ virtual ~Channel();
+ bool operator==(const Channel &other) const;
+ bool operator!=(const Channel &other) const;
+ bool visible() const;
+ void setVisible(bool value);
+ QString name() const;
+ int position() const;
+ int channelSize() const;
+ QRect bounds() const;
+ QByteArray pixelData(const QRect &rect) const;
+ void setPixelData(QByteArray value, const QRect &rect);
+private:
+
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Conversions.sip b/plugins/extensions/pykrita/sip/krita/Conversions.sip
new file mode 100644
index 0000000000..e746a48eb3
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Conversions.sip
@@ -0,0 +1,96 @@
+%MappedType QMap<QString, Resource*>
+{
+%TypeHeaderCode
+#include <QMap>
+#include <QString>
+#include <Resource.h>
+%End
+
+%ConvertFromTypeCode
+ // Create the dictionary.
+ PyObject *d = PyDict_New();
+
+ if (!d)
+ return NULL;
+
+ // Set the dictionary elements.
+ QMap<QString, Resource*>::const_iterator i = sipCpp->constBegin();
+
+ while (i != sipCpp->constEnd())
+ {
+ QString *t1 = new QString(i.key());
+
+ PyObject *t1obj = sipConvertFromNewType(t1, sipType_QString, sipTransferObj);
+ PyObject* t2obj = sipConvertFromType(i.value(), sipType_Resource, sipTransferObj);
+
+ if (t1obj == NULL || t2obj == NULL || PyDict_SetItem(d, t1obj, t2obj) < 0)
+ {
+ Py_DECREF(d);
+
+ if (t1obj) {
+ Py_DECREF(t1obj);
+ }
+
+ if (t2obj) {
+ Py_DECREF(t2obj);
+ }
+
+ return NULL;
+ }
+
+ Py_DECREF(t1obj);
+ Py_DECREF(t2obj);
+
+ ++i;
+ }
+
+ return d;
+%End
+
+%ConvertToTypeCode
+ PyObject *t1obj, *t2obj;
+ Py_ssize_t i = 0;
+
+ // Check the type if that is all that is required.
+ if (sipIsErr == NULL)
+ {
+ if (!PyDict_Check(sipPy))
+ return 0;
+
+ while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
+ {
+ if (!sipCanConvertToType(t1obj, sipType_QString, SIP_NOT_NONE))
+ return 0;
+ }
+
+ return 1;
+ }
+
+ QMap<QString, Resource*> *qm = new QMap<QString, Resource*>;
+
+ while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
+ {
+ int state;
+
+ QString *t1 = reinterpret_cast<QString *>(sipConvertToType(t1obj, sipType_QString, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+ Resource *t2 = reinterpret_cast<Resource*>(sipConvertToType(t2obj, sipType_Resource, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
+
+ if (*sipIsErr)
+ {
+ sipReleaseType(t1, sipType_QString, state);
+ delete qm;
+ return 0;
+ }
+
+ qm->insert(*t1, t2);
+
+ sipReleaseType(t1, sipType_QString, state);
+ }
+
+ *sipCppPtr = qm;
+
+ return sipGetState(sipTransferObj);
+%End
+};
+
+
diff --git a/plugins/extensions/pykrita/sip/krita/DockWidget.sip b/plugins/extensions/pykrita/sip/krita/DockWidget.sip
new file mode 100644
index 0000000000..18c42a8714
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/DockWidget.sip
@@ -0,0 +1,13 @@
+class DockWidget : public QDockWidget /NoDefaultCtors/
+{
+%TypeHeaderCode
+#include "DockWidget.h"
+%End
+
+public:
+ explicit DockWidget();
+protected Q_SLOTS:
+ Canvas* canvas() const;
+ virtual void canvasChanged(Canvas *canvas) = 0;
+
+};
diff --git a/plugins/extensions/pykrita/sip/krita/DockWidgetFactoryBase.sip b/plugins/extensions/pykrita/sip/krita/DockWidgetFactoryBase.sip
new file mode 100644
index 0000000000..7731ae74df
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/DockWidgetFactoryBase.sip
@@ -0,0 +1,20 @@
+class DockWidgetFactoryBase
+{
+%TypeHeaderCode
+#include "DockWidgetFactoryBase.h"
+%End
+
+public:
+ enum DockPosition {
+ DockTornOff, ///< Floating as its own top level window
+ DockTop, ///< Above the central widget
+ DockBottom, ///< Below the central widget
+ DockRight, ///< Right of the centra widget
+ DockLeft, ///< Left of the centra widget
+ DockMinimized ///< Not docked, but reachable via the menu
+ };
+ DockWidgetFactoryBase(const QString& _id, DockPosition _dockPosition, bool _isCollapsable = true, bool _defaultCollapsed = false);
+ virtual QDockWidget* createDockWidget() = 0 /Factory/;
+ QString id() const;
+};
+
diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip
new file mode 100644
index 0000000000..36d3b80539
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Document.sip
@@ -0,0 +1,62 @@
+class Document : QObject /NoDefaultCtors/
+{
+%TypeHeaderCode
+#include "Document.h"
+%End
+ Document(const Document & __0);
+public:
+ bool operator==(const Document &other) const;
+ bool operator!=(const Document &other) const;
+
+public Q_SLOTS:
+
+ Node * activeNode() const /Factory/;
+ void setActiveNode(Node* value);
+ QList<Node*> topLevelNodes() const /Factory/;
+ Node *nodeByName(const QString &node) const /Factory/;
+ bool batchmode() const;
+ void setBatchmode(bool value);
+ QString colorDepth() const;
+ QString colorModel() const;
+ QString colorProfile() const;
+ bool setColorProfile(const QString &colorProfile);
+ bool setColorSpace(const QString &value, const QString &colorDepth, const QString &colorProfile);
+ QString documentInfo() const;
+ void setDocumentInfo(const QString &document);
+ QString fileName() const;
+ void setFileName(QString value);
+ int height() const;
+ void setHeight(int value);
+ QString name() const;
+ void setName(QString value);
+ int resolution() const;
+ void setResolution(int value);
+ Node * rootNode() const /Factory/;
+ Selection * selection() const /Factory/;
+ void setSelection(Selection* value);
+ int width() const;
+ void setWidth(int value);
+ double xRes() const;
+ void setXRes(double xRes) const;
+ double yRes() const;
+ void setYRes(double yRes) const;
+ QByteArray pixelData(int x, int y, int w, int h) const;
+ bool close();
+ void crop(int x, int y, int w, int h);
+ bool exportImage(const QString &filename, const InfoObject & exportConfiguration);
+ void flatten();
+ void resizeImage(int w, int h);
+ bool save();
+ bool saveAs(const QString & filename);
+ Node *createNode(const QString & name, const QString & nodeType) /Factory/;
+ QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const;
+ QImage thumbnail(int w, int h) const;
+ void lock();
+ void unlock();
+ void waitForDone();
+ bool tryBarrierLock();
+ bool isIdle();
+ void refreshProjection();
+private:
+
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Extension.sip b/plugins/extensions/pykrita/sip/krita/Extension.sip
new file mode 100644
index 0000000000..78ffa9d6f0
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Extension.sip
@@ -0,0 +1,11 @@
+class Extension : QObject
+{
+
+%TypeHeaderCode
+#include "Extension.h"
+%End
+
+public:
+ explicit Extension(QObject *parent /TransferThis/ = 0);
+ virtual void setup() = 0;
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Filter.sip b/plugins/extensions/pykrita/sip/krita/Filter.sip
new file mode 100644
index 0000000000..ff61085a88
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Filter.sip
@@ -0,0 +1,22 @@
+class Filter : QObject
+{
+%TypeHeaderCode
+#include "Filter.h"
+%End
+ Filter(const Filter & __0);
+
+public:
+ Filter();
+ virtual ~Filter();
+ bool operator==(const Filter &other) const;
+ bool operator!=(const Filter &other) const;
+
+public Q_SLOTS:
+ QString name() const;
+ void setName(const QString &);
+ InfoObject * configuration() const;
+ void setConfiguration(InfoObject* value);
+ void apply(Node *node, int x, int y, int w, int h);
+ bool startFilter(Node *node, int x, int y, int w, int h);
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/InfoObject.sip b/plugins/extensions/pykrita/sip/krita/InfoObject.sip
new file mode 100644
index 0000000000..775a81354f
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/InfoObject.sip
@@ -0,0 +1,20 @@
+class InfoObject : QObject
+{
+%TypeHeaderCode
+#include "InfoObject.h"
+%End
+ InfoObject(const InfoObject & __0);
+public:
+ bool operator==(const InfoObject &other) const;
+ bool operator!=(const InfoObject &other) const;
+public:
+ InfoObject(QObject* parent /TransferThis/ = 0);
+ virtual ~InfoObject();
+ QMap<QString, QVariant> properties() const;
+ void setProperties(QMap<QString, QVariant> value);
+public Q_SLOTS:
+ void setProperty(const QString & key, QVariant value);
+ QVariant property(const QString & key);
+Q_SIGNALS:
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip b/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip
new file mode 100644
index 0000000000..08a5edd98c
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip
@@ -0,0 +1,19 @@
+class KisCubicCurve
+{
+%TypeHeaderCode
+#include "kis_cubic_curve.h"
+%End
+public:
+ KisCubicCurve();
+ qreal value(qreal x) const;
+ QList<QPointF> points() const;
+ void setPoints(const QList<QPointF>& points);
+ void setPoint(int idx, const QPointF& point);
+ int addPoint(const QPointF& point);
+ void removePoint(int idx);
+ bool isNull() const;
+ void setName(const QString& name);
+ const QString& name() const;
+ QString toString() const;
+ void fromString(const QString&);
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Krita.sip b/plugins/extensions/pykrita/sip/krita/Krita.sip
new file mode 100644
index 0000000000..07677e84ec
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Krita.sip
@@ -0,0 +1,57 @@
+
+class Krita : QObject
+{
+%TypeHeaderCode
+#include "Krita.h"
+%End
+public:
+public Q_SLOTS:
+ Krita(QObject* parent /TransferThis/ = 0);
+ virtual ~Krita();
+ Document * activeDocument() const /Factory/;
+ void setActiveDocument(Document* value);
+ bool batchmode() const;
+ void setBatchmode(bool value);
+ QList<Action *> actions() const /Factory/;
+ Action * action(const QString & name) const;
+ QList<Document *> documents() const /Factory/;
+ QStringList filters() const;
+ Filter * filter(const QString &name) const /Factory/;
+ QStringList profiles(const QString &colorModel, const QString &ColorDepth) const;
+ bool addProfile(const QString &profilePath);
+ Notifier * notifier() const;
+ QString version() const;
+ QList<View *> views() const /Factory/;
+ Window * activeWindow() const /Factory/;
+ QList<Window *> windows() const /Factory/;
+ QMap<QString, Resource *> resources(const QString &type) const /Factory/;
+ Document * createDocument(int width, int height, const QString &name, const QString &colorModel, const QString &colorDepth, const QString &profile) /Factory/;
+ Document * openDocument(const QString &filename) /Factory/;
+ Window * openWindow();
+ Action * createAction(const QString & text);
+
+ void addExtension(Extension* _extension /GetWrapper/);
+%MethodCode
+ Py_BEGIN_ALLOW_THREADS
+ sipCpp->addExtension(a0);
+ Py_END_ALLOW_THREADS
+
+ sipTransferTo(a0Wrapper, Py_None);
+%End
+ void addDockWidgetFactory(DockWidgetFactoryBase* _factory /GetWrapper/);
+%MethodCode
+ Py_BEGIN_ALLOW_THREADS
+ sipCpp->addDockWidgetFactory(a0);
+ Py_END_ALLOW_THREADS
+
+ sipTransferTo(a0Wrapper, Py_None);
+%End
+
+ void writeSetting(const QString &group, const QString &name, const QString &value);
+ QString readSetting(const QString &group, const QString &name, const QString &defaultValue);
+
+ static Krita * instance();
+ static QObject * fromVariant(const QVariant & v);
+private:
+ Krita(const Krita &); // Generated
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Node.sip b/plugins/extensions/pykrita/sip/krita/Node.sip
new file mode 100644
index 0000000000..e4c7380d36
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Node.sip
@@ -0,0 +1,57 @@
+class Node : QObject
+{
+%TypeHeaderCode
+#include "Node.h"
+%End
+ Node(const Node & __0);
+public:
+ virtual ~Node();
+ bool operator==(const Node &other) const;
+ bool operator!=(const Node &other) const;
+public Q_SLOTS:
+ bool alphaLocked() const;
+ void setAlphaLocked(bool value);
+ QString blendingMode() const;
+ void setBlendingMode(QString value);
+ QList<Channel *> channels() const;
+ QList<Node *> childNodes() const;
+ bool addChildNode(Node *child, Node *above);
+ bool removeChildNode(Node *child);
+ void setChildNodes(QList<Node *> nodes);
+ QString colorDepth() const;
+ bool animated() const;
+ void enableAnimation() const;
+ void setCollapsed(bool collapsed);
+ bool collapsed() const;
+ int colorLabel() const;
+ void setColorLabel(int value);
+ QString colorModel() const;
+ QString colorProfile() const;
+ bool setColorProfile(const QString &colorProfile);
+ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile);
+ bool inheritAlpha() const;
+ void setInheritAlpha(bool value);
+ bool locked() const;
+ void setLocked(bool value);
+ QString name() const;
+ void setName(QString value);
+ int opacity() const;
+ void setOpacity(int value);
+ Node * parentNode() const /Factory/;
+ QString type() const;
+ bool visible() const;
+ void setVisible(bool value);
+ QByteArray pixelData(int x, int y, int w, int h) const;
+ QByteArray projectionPixelData(int x, int y, int w, int h) const;
+ void setPixelData(QByteArray value, int x, int y, int w, int h);
+ QRect bounds() const;
+ void move(int x, int y);
+ QPoint position() const;
+ bool remove();
+ Node *duplicate() /Factory/;
+ void save(const QString &filename, double xRes, double yRes);
+ Node *mergeDown() /Factory/;
+ QImage thumbnail(int w, int h);
+Q_SIGNALS:
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Notifier.sip b/plugins/extensions/pykrita/sip/krita/Notifier.sip
new file mode 100644
index 0000000000..0251e66a19
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Notifier.sip
@@ -0,0 +1,21 @@
+class Notifier : QObject
+{
+%TypeHeaderCode
+#include "Notifier.h"
+%End
+ Notifier(const Notifier & __0);
+public:
+ Notifier(QObject* parent /TransferThis/ = 0);
+ virtual ~Notifier();
+ bool active() const;
+ void setActive(bool value);
+Q_SIGNALS:
+ void applicationClosing();
+ void imageCreated(Document* image);
+ void imageSaved(const QString &filename);
+ void imageClosed(const QString &filename);
+ void viewCreated(View *view);
+ void viewClosed(View *view);
+ void windowCreated(Window *window);
+ void configurationChanged();
+};
diff --git a/plugins/extensions/pykrita/sip/krita/PresetChooser.sip b/plugins/extensions/pykrita/sip/krita/PresetChooser.sip
new file mode 100644
index 0000000000..c53c004aa9
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/PresetChooser.sip
@@ -0,0 +1,20 @@
+class PresetChooser : public QWidget /NoDefaultCtors/
+{
+%TypeHeaderCode
+#include "PresetChooser.h"
+%End
+
+public:
+ PresetChooser(QWidget *parent = 0);
+
+public Q_SLOTS:
+
+ void setCurrentPreset(Resource *resource);
+ Resource *currentPreset() const /Factory/;
+
+Q_SIGNALS:
+
+ void presetSelected(Resource *resource) /Factory/;
+ void presetClicked(Resource *resource) /Factory/;
+
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Resource.sip b/plugins/extensions/pykrita/sip/krita/Resource.sip
new file mode 100644
index 0000000000..1b2d641553
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Resource.sip
@@ -0,0 +1,24 @@
+class Resource : QObject
+{
+%TypeHeaderCode
+#include "Resource.h"
+%End
+ Resource(const Resource & __0);
+public:
+ bool operator==(const Resource &other) const;
+ bool operator!=(const Resource &other) const;
+public:
+ virtual ~Resource();
+public Q_SLOTS:
+ QString type() const;
+ QString name() const;
+ void setName(QString value);
+ QString filename() const;
+ QImage image() const;
+ void setImage(QImage image);
+ QByteArray data() const;
+ bool setData(QByteArray data);
+public Q_SLOTS:
+Q_SIGNALS:
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Selection.sip b/plugins/extensions/pykrita/sip/krita/Selection.sip
new file mode 100644
index 0000000000..c429b8e61e
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Selection.sip
@@ -0,0 +1,41 @@
+class Selection : QObject
+{
+%TypeHeaderCode
+#include "Selection.h"
+%End
+ Selection(const Selection & __0);
+public:
+ Selection(QObject* parent /TransferThis/ = 0);
+ virtual ~Selection();
+ bool operator==(const Selection &other) const;
+ bool operator!=(const Selection &other) const;
+public Q_SLOTS:
+ int width() const;
+ int height() const;
+ int x() const;
+ int y() const;
+ void move(int x, int y);
+ void clear();
+ void contract(int value);
+ void copy(Node *node);
+ void cut(Node *node);
+ void paste(Node *destination, int x, int y);
+ void erode();
+ void dilate();
+ void border(int xRadius, int yRadius);
+ void feather(int radius);
+ void grow(int xradius, int yradius);
+ void shrink(int xRadius, int yRadius, bool edgeLock);
+ void smooth();
+ void invert();
+ void resize(int w, int h);
+ void select(int x, int y, int w, int h, int value);
+ void selectAll(Node* node, int value);
+ void replace(Selection *selection);
+ void add(Selection *selection);
+ void subtract(Selection *selection);
+ void intersect(Selection *selection);
+ QByteArray pixelData(int x, int y, int w, int h) const;
+ void setPixelData(QByteArray value, int x, int y, int w, int h);
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/View.sip b/plugins/extensions/pykrita/sip/krita/View.sip
new file mode 100644
index 0000000000..4cfebfbcd3
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/View.sip
@@ -0,0 +1,19 @@
+class View : QObject
+{
+%TypeHeaderCode
+#include "View.h"
+%End
+ View(const View & __0);
+public:
+ bool operator==(const View &other) const;
+ bool operator!=(const View &other) const;
+
+public Q_SLOTS:
+ Window * window() const /Factory/;
+ Document * document() const /Factory/;
+ bool visible() const;
+ void setVisible();
+ Canvas * canvas() const /Factory/;
+ void activateResource(Resource *resource);
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/Window.sip b/plugins/extensions/pykrita/sip/krita/Window.sip
new file mode 100644
index 0000000000..fc805dd677
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/Window.sip
@@ -0,0 +1,21 @@
+class Window : QObject
+{
+%TypeHeaderCode
+#include "Window.h"
+%End
+ Window(const Window & __0);
+public:
+ bool operator==(const Window &other) const;
+ bool operator!=(const Window &other) const;
+
+public Q_SLOTS:
+ QMainWindow *qwindow() const;
+ QList<View *> views() const /Factory/;
+ View *addView(Document *document) /Factory/;
+ void showView(View *view);
+ void activate();
+ void close();
+Q_SIGNALS:
+ void windowClosed();
+private:
+};
diff --git a/plugins/extensions/pykrita/sip/krita/kritamod.sip b/plugins/extensions/pykrita/sip/krita/kritamod.sip
new file mode 100644
index 0000000000..183b9ff102
--- /dev/null
+++ b/plugins/extensions/pykrita/sip/krita/kritamod.sip
@@ -0,0 +1,32 @@
+%Module PyKrita.krita
+
+%ModuleHeaderCode
+#pragma GCC visibility push(default)
+%End
+
+%Import QtCore/QtCoremod.sip
+%Import QtGui/QtGuimod.sip
+%Import QtXml/QtXmlmod.sip
+%Import QtWidgets/QtWidgetsmod.sip
+
+%Include Conversions.sip
+%Include Action.sip
+%Include Canvas.sip
+%Include Channel.sip
+%Include DockWidgetFactoryBase.sip
+%Include DockWidget.sip
+%Include Document.sip
+%Include Filter.sip
+%Include InfoObject.sip
+%Include Extension.sip
+%Include View.sip
+%Include Window.sip
+%Include Krita.sip
+%Include Node.sip
+%Include Notifier.sip
+%Include Resource.sip
+%Include Selection.sip
+%Include Extension.sip
+%Include PresetChooser.sip
+
+%Include KisCubicCurve.sip
diff --git a/plugins/extensions/pykrita/testapi.py b/plugins/extensions/pykrita/testapi.py
new file mode 100644
index 0000000000..a647187534
--- /dev/null
+++ b/plugins/extensions/pykrita/testapi.py
@@ -0,0 +1,31 @@
+#
+# Tests the PyKrita API
+#
+
+import sys
+from PyQt5.QtGui import *
+from PyQt5.QtWidgets import *
+from krita import *
+
+def __main__(args):
+ print("Arguments:", args)
+ Application.setBatchmode(True)
+ print("Batchmode: ", Application.batchmode())
+ print("Profiles:", Application.profiles("GRAYA", "U16"));
+ document = Application.openDocument(args[0])
+ print("Opened", document.fileName(), "WxH", document.width(), document.height(), "resolution", document.xRes(), document.yRes(), "in ppi", document.resolution())
+ node = document.rootNode()
+ print("Root", node.name(), "opacity", node.opacity())
+ for child in node.childNodes():
+ print("\tChild", child.name(), "opacity", node.opacity(), node.blendingMode())
+ #r = child.save(child.name() + ".png", document.xRes(), document.yRes());
+ #print("Saving result:", r)
+ for channel in child.channels():
+ print("Channel", channel.name(), "contents:", len(channel.pixelData(node.bounds())))
+
+ document.close()
+
+ document = Application.createDocument(100, 100, "test", "GRAYA", "U16", "")
+ document.setBatchmode(True)
+ #document.saveAs("test.kra")
+
diff --git a/plugins/flake/CMakeLists.txt b/plugins/flake/CMakeLists.txt
index ac791d0a98..9710fe718f 100644
--- a/plugins/flake/CMakeLists.txt
+++ b/plugins/flake/CMakeLists.txt
@@ -1,4 +1,5 @@
+add_subdirectory( imageshape )
add_subdirectory( artistictextshape )
add_subdirectory( pathshapes )
add_subdirectory( textshape )
add_subdirectory( vectorshape )
diff --git a/plugins/flake/artistictextshape/ArtisticTextShape.cpp b/plugins/flake/artistictextshape/ArtisticTextShape.cpp
index 0cd1f7338b..b478593596 100644
--- a/plugins/flake/artistictextshape/ArtisticTextShape.cpp
+++ b/plugins/flake/artistictextshape/ArtisticTextShape.cpp
@@ -1,1375 +1,1375 @@
/* 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 <KoPathShapeLoader.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 <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);
}
}
void ArtisticTextShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
applyConversion(painter, converter);
if (background()) {
background()->paint(painter, converter, paintContext, outline());
}
}
void ArtisticTextShape::saveOdf(KoShapeSavingContext &context) const
{
SvgWriter svgWriter(QList<KoShape *>() << const_cast<ArtisticTextShape *>(this), size());
QByteArray fileContent;
QBuffer fileContentDevice(&fileContent);
if (!fileContentDevice.open(QIODevice::WriteOnly)) {
return;
}
if (!svgWriter.save(fileContentDevice)) {
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());
// reset transformation
setTransformation(QTransform());
updateSizeAndPosition();
// move to correct position
- setAbsolutePosition(m_outlineOrigin, KoFlake::TopLeftCorner);
+ 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::TopLeftCorner);
+ 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);
// 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());
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().addAttributePt("x", anchorOffset);
context.shapeWriter().addAttributePt("y", baselineOffset());
context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation()));
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().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().addAttributePt("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(SvgUtil::parseTransform(p.attribute("transform")));
+ 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()));
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));
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);
}
}
//range.printDebug();
return range;
}
diff --git a/plugins/flake/artistictextshape/ArtisticTextTool.cpp b/plugins/flake/artistictextshape/ArtisticTextTool.cpp
index bc36ee6e3b..a502f8695b 100644
--- a/plugins/flake/artistictextshape/ArtisticTextTool.cpp
+++ b/plugins/flake/artistictextshape/ArtisticTextTool.cpp
@@ -1,1008 +1,1009 @@
/* 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 <QAction>
#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*)));
- KoShapeManager *manager = canvas->shapeManager();
- connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(textChanged()));
+ 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);
}
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.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);
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.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()->shapeManager()->selection();
+ 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->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 toolActivation, const QSet<KoShape *> &shapes)
+void ArtisticTextTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
- Q_UNUSED(toolActivation);
+ 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();
- KoShapeManager *manager = canvas()->shapeManager();
- connect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
+ 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;
- KoShapeManager *manager = canvas()->shapeManager();
- disconnect(manager, SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()));
+ 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->setParent(m_currentShape->parent());
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);
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()->shapeManager(), SIGNAL(selectionContentChanged()),
+ 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()->shapeManager(), SIGNAL(selectionContentChanged()),
+ 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_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()->shapeManager()->selection();
+ 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);
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/ArtisticTextTool.h b/plugins/flake/artistictextshape/ArtisticTextTool.h
index 03b2224c22..73708efba0 100644
--- a/plugins/flake/artistictextshape/ArtisticTextTool.h
+++ b/plugins/flake/artistictextshape/ArtisticTextTool.h
@@ -1,156 +1,156 @@
/* 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.
*/
#ifndef ARTISTICTEXTTOOL_H
#define ARTISTICTEXTTOOL_H
#include "ArtisticTextShape.h"
#include "ArtisticTextToolSelection.h"
#include <KoToolBase.h>
#include <QTimer>
class QAction;
class QActionGroup;
class KoInteractionStrategy;
/// This is the tool for the artistic text shape.
class ArtisticTextTool : public KoToolBase
{
Q_OBJECT
public:
explicit ArtisticTextTool(KoCanvasBase *canvas);
~ArtisticTextTool();
/// reimplemented
virtual void paint(QPainter &painter, const KoViewConverter &converter);
/// reimplemented
virtual void repaintDecorations();
/// reimplemented
virtual void mousePressEvent(KoPointerEvent *event);
/// reimplemented
virtual void mouseMoveEvent(KoPointerEvent *event);
/// reimplemented
virtual void mouseReleaseEvent(KoPointerEvent *event);
/// reimplemented
virtual void shortcutOverrideEvent(QKeyEvent *event);
/// reimplemented
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/// reimplemented
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape *> &shapes);
/// reimplemented
virtual void deactivate();
/// reimplemented
virtual QList<QPointer<QWidget> > createOptionWidgets();
/// reimplemented
virtual void keyPressEvent(QKeyEvent *event);
/// reimplemented
virtual KoToolSelection *selection();
/// reimplemented from superclass
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const;
/// Sets cursor for specified text shape it is the current text shape
void setTextCursor(ArtisticTextShape *textShape, int textCursor);
/// Returns the current text cursor position
int textCursor() const;
/**
* Determines cursor position from specified mouse position.
* @param mousePosition mouse position in document coordinates
* @return cursor position, -1 means invalid cursor
*/
int cursorFromMousePosition(const QPointF &mousePosition);
protected:
void enableTextCursor(bool enable);
void removeFromTextCursor(int from, unsigned int count);
void addToTextCursor(const QString &str);
private Q_SLOTS:
void detachPath();
void convertText();
void blinkCursor();
void textChanged();
void shapeSelectionChanged();
void setStartOffset(int offset);
void toggleFontBold(bool enabled);
void toggleFontItalic(bool enabled);
void anchorChanged(QAction *);
void setFontFamiliy(const QFont &font);
void setFontSize(int size);
void setSuperScript();
void setSubScript();
void selectAll();
void deselectAll();
Q_SIGNALS:
void shapeSelected();
private:
void updateActions();
void setTextCursorInternal(int textCursor);
void createTextCursorShape();
void updateTextCursorArea() const;
void setCurrentShape(ArtisticTextShape *currentShape);
enum FontProperty {
BoldProperty,
ItalicProperty,
FamiliyProperty,
SizeProperty
};
/// Changes the specified font property for the current text selection
void changeFontProperty(FontProperty property, const QVariant &value);
/// Toggle sub and super script
void toggleSubSuperScript(ArtisticTextRange::BaselineShift mode);
/// returns the transformation matrix for the text cursor
QTransform cursorTransform() const;
/// Returns the offset handle shape for the current text shape
QPainterPath offsetHandleShape();
ArtisticTextToolSelection m_selection; ///< the tools selection
ArtisticTextShape *m_currentShape; ///< the current text shape we are working on
ArtisticTextShape *m_hoverText; ///< the text shape the mouse cursor is hovering over
KoPathShape *m_hoverPath; ///< the path shape the mouse cursor is hovering over
QPainterPath m_textCursorShape; ///< our visual text cursor representation
bool m_hoverHandle;
QAction *m_detachPath;
QAction *m_convertText;
QAction *m_fontBold;
QAction *m_fontItalic;
QAction *m_superScript;
QAction *m_subScript;
QActionGroup *m_anchorGroup;
int m_textCursor;
QTimer m_blinkingCursor;
bool m_showCursor;
QList<QPointF> m_linefeedPositions; ///< offset positions for temporary line feeds
KoInteractionStrategy *m_currentStrategy;
};
#endif // ARTISTICTEXTTOOL_H
diff --git a/plugins/flake/imageshape/CMakeLists.txt b/plugins/flake/imageshape/CMakeLists.txt
new file mode 100644
index 0000000000..10412dbdb9
--- /dev/null
+++ b/plugins/flake/imageshape/CMakeLists.txt
@@ -0,0 +1,14 @@
+project(imageshape)
+
+set ( ImageShape_SRCS
+ ImageShapePlugin.cpp
+ ImageShape.cpp
+ ImageShapeFactory.cpp
+)
+
+add_library(krita_shape_image MODULE ${ImageShape_SRCS})
+
+target_link_libraries(krita_shape_image kritaflake kritawidgets KF5::I18n )
+
+install(TARGETS krita_shape_image DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
+
diff --git a/plugins/flake/imageshape/ImageShape.cpp b/plugins/flake/imageshape/ImageShape.cpp
new file mode 100644
index 0000000000..fd259cb6e7
--- /dev/null
+++ b/plugins/flake/imageshape/ImageShape.cpp
@@ -0,0 +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>
+
+struct Q_DECL_HIDDEN ImageShape::Private
+{
+ Private() {}
+ Private(const Private &rhs)
+ : 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(new KoTosContainerPrivate(*rhs.d_func(), this)),
+ m_d(new Private(*rhs.m_d))
+{
+}
+
+ImageShape::~ImageShape()
+{
+}
+
+KoShape *ImageShape::cloneShape() const
+{
+ return new ImageShape(*this);
+}
+
+void ImageShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+{
+ Q_UNUSED(paintContext);
+
+ const QRectF myrect(QPointF(), size());
+ applyConversion(painter, converter);
+
+ painter.save();
+ painter.setRenderHint(QPainter::SmoothPixmapTransform);
+ painter.setClipRect(QRectF(QPointF(), size()), Qt::IntersectClip);
+ painter.setTransform(m_d->viewBoxTransform, true);
+ painter.drawImage(QPoint(), m_d->image);
+ painter.restore();
+}
+
+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);
+ context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation()));
+ 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
new file mode 100644
index 0000000000..d12d405545
--- /dev/null
+++ b/plugins/flake/imageshape/ImageShape.h
@@ -0,0 +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 "KoTosContainer.h"
+#include <SvgShape.h>
+
+#define ImageShapeId "ImageShape"
+
+
+class ImageShape : public KoTosContainer, public SvgShape
+{
+public:
+ ImageShape();
+ ~ImageShape();
+
+ KoShape *cloneShape() const;
+
+ void paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext);
+
+ 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;
+};
+
+#endif // IMAGESHAPE_H
diff --git a/plugins/flake/imageshape/ImageShapeFactory.cpp b/plugins/flake/imageshape/ImageShapeFactory.cpp
new file mode 100644
index 0000000000..e44313e988
--- /dev/null
+++ b/plugins/flake/imageshape/ImageShapeFactory.cpp
@@ -0,0 +1,70 @@
+/* This file is part of the KDE project
+ *
+ * Copyright (C) 2009 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 "ImageShapeFactory.h"
+
+// ImageShape
+#include "ImageShape.h"
+//#include "ImageShapeConfigWidget.h"
+
+// Calligra
+#include <KoXmlNS.h>
+#include <KoShapeLoadingContext.h>
+#include <KoOdfLoadingContext.h>
+#include <KoIcon.h>
+
+// KDE
+#include <klocalizedstring.h>
+
+ImageShapeFactory::ImageShapeFactory()
+ : KoShapeFactoryBase(ImageShapeId, i18n("Image shape"))
+{
+ setToolTip(i18n("A shape that shows an image (PNG/JPG/TIFF)"));
+ setIconName(koIconNameCStrNeededWithSubs("a generic image image icon", "x-shape-vectorimage", "application-x-wmf"));
+
+ QList<QPair<QString, QStringList> > elementNamesList;
+ elementNamesList.append(qMakePair(QString(KoXmlNS::draw), QStringList("image")));
+ elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("image")));
+ setXmlElements(elementNamesList);
+ setLoadingPriority(1);
+}
+
+KoShape *ImageShapeFactory::createDefaultShape(KoDocumentResourceManager */*documentResources*/) const
+{
+ ImageShape *shape = new ImageShape();
+ shape->setShapeId(ImageShapeId);
+
+ return shape;
+}
+
+bool ImageShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
+{
+ Q_UNUSED(context);
+ return e.localName() == "image" &&
+ (e.namespaceURI() == KoXmlNS::draw || e.namespaceURI() == KoXmlNS::svg);
+}
+
+QList<KoShapeConfigWidgetBase *> ImageShapeFactory::createShapeOptionPanels()
+{
+ QList<KoShapeConfigWidgetBase *> result;
+ //result.append(new ImageShapeConfigWidget());
+ return result;
+}
diff --git a/plugins/flake/imageshape/ImageShapeFactory.h b/plugins/flake/imageshape/ImageShapeFactory.h
new file mode 100644
index 0000000000..fb8efc6b56
--- /dev/null
+++ b/plugins/flake/imageshape/ImageShapeFactory.h
@@ -0,0 +1,42 @@
+/* This file is part of the KDE project
+ *
+ * Copyright (C) 2009 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 IMAGESHAPE_FACTORY_H
+#define IMAGESHAPE_FACTORY_H
+
+// Calligra
+#include <KoShapeFactoryBase.h>
+
+class KoShape;
+
+class ImageShapeFactory : public KoShapeFactoryBase
+{
+
+public:
+ /// constructor
+ ImageShapeFactory();
+ ~ImageShapeFactory() {}
+
+ KoShape *createDefaultShape(KoDocumentResourceManager *documentResources = 0) const override;
+ bool supports(const KoXmlElement &e, KoShapeLoadingContext &context) const override;
+ QList<KoShapeConfigWidgetBase *> createShapeOptionPanels() override;
+};
+
+#endif
diff --git a/plugins/flake/imageshape/ImageShapePlugin.cpp b/plugins/flake/imageshape/ImageShapePlugin.cpp
new file mode 100644
index 0000000000..51c774650e
--- /dev/null
+++ b/plugins/flake/imageshape/ImageShapePlugin.cpp
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ *
+ * Copyright (C) 2009 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 "ImageShapePlugin.h"
+
+// KDE
+#include <kpluginfactory.h>
+
+// Calligra libs
+#include <KoShapeRegistry.h>
+//#include <KoToolRegistry.h>
+
+// ImageShape
+//#include "ImageToolFactory.h"
+#include "ImageShapeFactory.h"
+
+K_PLUGIN_FACTORY_WITH_JSON(ImageShapePluginFactory, "calligra_shape_image.json", registerPlugin<ImageShapePlugin>();)
+
+ImageShapePlugin::ImageShapePlugin(QObject *parent, const QVariantList &)
+ : QObject(parent)
+{
+ //KoToolRegistry::instance()->add(new ImageToolFactory());
+ KoShapeRegistry::instance()->add(new ImageShapeFactory());
+}
+
+#include <ImageShapePlugin.moc>
diff --git a/plugins/flake/imageshape/ImageShapePlugin.h b/plugins/flake/imageshape/ImageShapePlugin.h
new file mode 100644
index 0000000000..f15c6bb001
--- /dev/null
+++ b/plugins/flake/imageshape/ImageShapePlugin.h
@@ -0,0 +1,37 @@
+/* This file is part of the KDE project
+ *
+ * Copyright (C) 2009 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 IMAGESHAPE_PLUGIN_H
+#define IMAGESHAPE_PLUGIN_H
+
+// Qt
+#include <QObject>
+#include <QVariantList>
+
+class ImageShapePlugin : public QObject
+{
+ Q_OBJECT
+
+public:
+ ImageShapePlugin(QObject *parent, const QVariantList &);
+ ~ImageShapePlugin() {}
+};
+
+#endif
diff --git a/plugins/flake/imageshape/calligra_shape_image.json b/plugins/flake/imageshape/calligra_shape_image.json
new file mode 100644
index 0000000000..94a83b16a8
--- /dev/null
+++ b/plugins/flake/imageshape/calligra_shape_image.json
@@ -0,0 +1,11 @@
+{
+ "Id": "Static Vector Shape",
+ "Type": "Service",
+ "X-Flake-MinVersion": "28",
+ "X-Flake-PluginVersion": "28",
+ "X-KDE-Library": "krita_shape_vector",
+ "X-KDE-PluginInfo-Name": "vectorshape",
+ "X-KDE-ServiceTypes": [
+ "Calligra/Flake"
+ ]
+}
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp
index b3b188a4cc..df50733fcc 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShape.cpp
+++ b/plugins/flake/pathshapes/ellipse/EllipseShape.cpp
@@ -1,441 +1,546 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006,2008 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 "EllipseShape.h"
#include <KoPathPoint.h>
#include <KoShapeSavingContext.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoUnit.h>
#include <KoOdfWorkaround.h>
#include <SvgSavingContext.h>
#include <SvgLoadingContext.h>
#include <SvgUtil.h>
#include <SvgStyleWriter.h>
+#include <KoParameterShape_p.h>
+#include "kis_global.h"
+
#include <math.h>
EllipseShape::EllipseShape()
: m_startAngle(0)
, m_endAngle(0)
, m_kindAngle(M_PI)
, m_type(Arc)
{
QList<QPointF> handles;
handles.push_back(QPointF(100, 50));
handles.push_back(QPointF(100, 50));
handles.push_back(QPointF(0, 50));
setHandles(handles);
QSizeF size(100, 100);
m_radii = QPointF(size.width() / 2.0, size.height() / 2.0);
m_center = QPointF(m_radii.x(), m_radii.y());
updatePath(size);
}
+EllipseShape::EllipseShape(const EllipseShape &rhs)
+ : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)),
+ m_startAngle(rhs.m_startAngle),
+ m_endAngle(rhs.m_endAngle),
+ m_kindAngle(rhs.m_kindAngle),
+ m_center(rhs.m_center),
+ m_radii(rhs.m_radii),
+ m_type(rhs.m_type)
+{
+}
+
EllipseShape::~EllipseShape()
{
}
+KoShape *EllipseShape::cloneShape() const
+{
+ return new EllipseShape(*this);
+}
+
void EllipseShape::saveOdf(KoShapeSavingContext &context) const
{
if (isParametricShape()) {
context.xmlWriter().startElement("draw:ellipse");
saveOdfAttributes(context, OdfAllAttributes);
switch (m_type) {
case Arc:
context.xmlWriter().addAttribute("draw:kind", sweepAngle() == 360 ? "full" : "arc");
break;
case Pie:
context.xmlWriter().addAttribute("draw:kind", "section");
break;
case Chord:
context.xmlWriter().addAttribute("draw:kind", "cut");
break;
default:
context.xmlWriter().addAttribute("draw:kind", "full");
}
if (m_type != Arc || sweepAngle() != 360) {
context.xmlWriter().addAttribute("draw:start-angle", m_startAngle);
context.xmlWriter().addAttribute("draw:end-angle", m_endAngle);
}
saveOdfCommonChildElements(context);
saveText(context);
context.xmlWriter().endElement();
} else {
KoPathShape::saveOdf(context);
}
}
bool EllipseShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
QSizeF size;
bool radiusGiven = true;
QString kind = element.attributeNS(KoXmlNS::draw, "kind", "full");
if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) {
qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx"));
qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry"));
size = QSizeF(2 * rx, 2 * ry);
} else if (element.hasAttributeNS(KoXmlNS::svg, "r")) {
qreal r = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "r"));
size = QSizeF(2 * r, 2 * r);
} else {
size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
#ifndef NWORKAROUND_ODF_BUGS
radiusGiven = KoOdfWorkaround::fixEllipse(kind, context);
#else
radiusGiven = false;
#endif
}
setSize(size);
QPointF pos;
if (element.hasAttributeNS(KoXmlNS::svg, "cx") && element.hasAttributeNS(KoXmlNS::svg, "cy")) {
qreal cx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cx"));
qreal cy = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "cy"));
pos = QPointF(cx - 0.5 * size.width(), cy - 0.5 * size.height());
} else {
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
}
setPosition(pos);
if (kind == "section") {
setType(Pie);
} else if (kind == "cut") {
setType(Chord);
} else {
setType(Arc);
}
setStartAngle(element.attributeNS(KoXmlNS::draw, "start-angle", "0").toDouble());
setEndAngle(element.attributeNS(KoXmlNS::draw, "end-angle", "360").toDouble());
if (!radiusGiven) {
// is the size was given by width and height we have to reset the data as the size of the
// part of the cut/pie is given.
setSize(size);
setPosition(pos);
}
loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements);
loadText(element, context);
return true;
}
void EllipseShape::setSize(const QSizeF &newSize)
{
QTransform matrix(resizeMatrix(newSize));
m_center = matrix.map(m_center);
m_radii = matrix.map(m_radii);
KoParameterShape::setSize(newSize);
}
QPointF EllipseShape::normalize()
{
QPointF offset(KoParameterShape::normalize());
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
m_center = matrix.map(m_center);
return offset;
}
void EllipseShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
QPointF p(point);
QPointF diff(m_center - point);
diff.setX(-diff.x());
qreal angle = 0;
if (diff.x() == 0) {
angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0;
} else {
diff.setY(diff.y() * m_radii.x() / m_radii.y());
angle = atan(diff.y() / diff.x());
if (angle < 0) {
- angle = M_PI + angle;
+ angle += M_PI;
}
+
if (diff.y() < 0) {
angle += M_PI;
}
}
QList<QPointF> handles = this->handles();
switch (handleId) {
case 0:
p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y()));
- m_startAngle = angle * 180.0 / M_PI;
+ m_startAngle = kisRadiansToDegrees(angle);
handles[handleId] = p;
- updateKindHandle();
break;
case 1:
p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y()));
- m_endAngle = angle * 180.0 / M_PI;
+ m_endAngle = kisRadiansToDegrees(angle);
handles[handleId] = p;
- updateKindHandle();
break;
case 2: {
QList<QPointF> kindHandlePositions;
kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y())));
kindHandlePositions.push_back(m_center);
kindHandlePositions.push_back((handles[0] + handles[1]) / 2.0);
QPointF diff = m_center * 2.0;
int handlePos = 0;
for (int i = 0; i < kindHandlePositions.size(); ++i) {
QPointF pointDiff(p - kindHandlePositions[i]);
if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) {
diff = pointDiff;
handlePos = i;
}
}
handles[handleId] = kindHandlePositions[handlePos];
m_type = EllipseType(handlePos);
}
break;
}
setHandles(handles);
+
+ if (handleId != 2) {
+ updateKindHandle();
+ }
}
void EllipseShape::updatePath(const QSizeF &size)
{
+ Q_D(KoParameterShape);
+
Q_UNUSED(size);
QPointF startpoint(handles()[0]);
QPointF curvePoints[12];
+ const qreal distance = sweepAngle();
- int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, sweepAngle(), startpoint, curvePoints);
+ const bool sameAngles = distance > 359.9;
+ int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, distance, startpoint, curvePoints);
+ KIS_SAFE_ASSERT_RECOVER_RETURN(pointCnt);
int curvePointCount = 1 + pointCnt / 3;
int requiredPointCount = curvePointCount;
if (m_type == Pie) {
requiredPointCount++;
- } else if (m_type == Arc && m_startAngle == m_endAngle) {
+ } else if (m_type == Arc && sameAngles) {
curvePointCount--;
requiredPointCount--;
}
createPoints(requiredPointCount);
- KoSubpath &points = *m_subpaths[0];
+ KoSubpath &points = *d->subpaths[0];
int curveIndex = 0;
points[0]->setPoint(startpoint);
points[0]->removeControlPoint1();
points[0]->setProperty(KoPathPoint::StartSubpath);
for (int i = 1; i < curvePointCount; ++i) {
points[i - 1]->setControlPoint2(curvePoints[curveIndex++]);
points[i]->setControlPoint1(curvePoints[curveIndex++]);
points[i]->setPoint(curvePoints[curveIndex++]);
points[i]->removeControlPoint2();
}
if (m_type == Pie) {
points[requiredPointCount - 1]->setPoint(m_center);
points[requiredPointCount - 1]->removeControlPoint1();
points[requiredPointCount - 1]->removeControlPoint2();
- } else if (m_type == Arc && m_startAngle == m_endAngle) {
+ } else if (m_type == Arc && sameAngles) {
points[curvePointCount - 1]->setControlPoint2(curvePoints[curveIndex]);
points[0]->setControlPoint1(curvePoints[++curveIndex]);
}
for (int i = 0; i < requiredPointCount; ++i) {
points[i]->unsetProperty(KoPathPoint::StopSubpath);
points[i]->unsetProperty(KoPathPoint::CloseSubpath);
}
- m_subpaths[0]->last()->setProperty(KoPathPoint::StopSubpath);
- if (m_type == Arc && m_startAngle != m_endAngle) {
- m_subpaths[0]->first()->unsetProperty(KoPathPoint::CloseSubpath);
- m_subpaths[0]->last()->unsetProperty(KoPathPoint::CloseSubpath);
+ d->subpaths[0]->last()->setProperty(KoPathPoint::StopSubpath);
+ if (m_type == Arc && !sameAngles) {
+ d->subpaths[0]->first()->unsetProperty(KoPathPoint::CloseSubpath);
+ d->subpaths[0]->last()->unsetProperty(KoPathPoint::CloseSubpath);
} else {
- m_subpaths[0]->first()->setProperty(KoPathPoint::CloseSubpath);
- m_subpaths[0]->last()->setProperty(KoPathPoint::CloseSubpath);
+ d->subpaths[0]->first()->setProperty(KoPathPoint::CloseSubpath);
+ d->subpaths[0]->last()->setProperty(KoPathPoint::CloseSubpath);
}
normalize();
}
void EllipseShape::createPoints(int requiredPointCount)
{
- if (m_subpaths.count() != 1) {
+ Q_D(KoParameterShape);
+
+ if (d->subpaths.count() != 1) {
clear();
- m_subpaths.append(new KoSubpath());
+ d->subpaths.append(new KoSubpath());
}
- int currentPointCount = m_subpaths[0]->count();
+ int currentPointCount = d->subpaths[0]->count();
if (currentPointCount > requiredPointCount) {
for (int i = 0; i < currentPointCount - requiredPointCount; ++i) {
- delete m_subpaths[0]->front();
- m_subpaths[0]->pop_front();
+ delete d->subpaths[0]->front();
+ d->subpaths[0]->pop_front();
}
} else if (requiredPointCount > currentPointCount) {
for (int i = 0; i < requiredPointCount - currentPointCount; ++i) {
- m_subpaths[0]->append(new KoPathPoint(this, QPointF()));
+ d->subpaths[0]->append(new KoPathPoint(this, QPointF()));
}
}
}
void EllipseShape::updateKindHandle()
{
- m_kindAngle = (m_startAngle + m_endAngle) * M_PI / 360.0;
+ qreal angle = 0.5 * (m_startAngle + m_endAngle);
if (m_startAngle > m_endAngle) {
- m_kindAngle += M_PI;
+ angle += 180.0;
}
+
+ m_kindAngle = normalizeAngle(kisDegreesToRadians(angle));
+
QList<QPointF> handles = this->handles();
switch (m_type) {
case Arc:
handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y());
break;
case Pie:
handles[2] = m_center;
break;
case Chord:
handles[2] = (handles[0] + handles[1]) / 2.0;
break;
}
setHandles(handles);
}
void EllipseShape::updateAngleHandles()
{
- qreal startRadian = m_startAngle * M_PI / 180.0;
- qreal endRadian = m_endAngle * M_PI / 180.0;
+ qreal startRadian = kisDegreesToRadians(normalizeAngleDegrees(m_startAngle));
+ qreal endRadian = kisDegreesToRadians(normalizeAngleDegrees(m_endAngle));
QList<QPointF> handles = this->handles();
handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y());
handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y());
setHandles(handles);
}
qreal EllipseShape::sweepAngle() const
{
- qreal sAngle = m_endAngle - m_startAngle;
- // treat also as full circle
- if (sAngle == 0 || sAngle == -360) {
- sAngle = 360;
+ const qreal a1 = normalizeAngle(kisDegreesToRadians(m_startAngle));
+ const qreal a2 = normalizeAngle(kisDegreesToRadians(m_endAngle));
+
+ qreal sAngle = a2 - a1;
+
+ if (a1 > a2) {
+ sAngle = 2 * M_PI + sAngle;
}
- if (m_startAngle > m_endAngle) {
- sAngle = 360 - m_startAngle + m_endAngle;
+
+ if (qAbs(a1 - a2) < 0.05 / M_PI) {
+ sAngle = 2 * M_PI;
}
- return sAngle;
+
+ return kisRadiansToDegrees(sAngle);
}
void EllipseShape::setType(EllipseType type)
{
m_type = type;
updateKindHandle();
updatePath(size());
}
EllipseShape::EllipseType EllipseShape::type() const
{
return m_type;
}
void EllipseShape::setStartAngle(qreal angle)
{
m_startAngle = angle;
updateKindHandle();
updateAngleHandles();
updatePath(size());
}
qreal EllipseShape::startAngle() const
{
return m_startAngle;
}
void EllipseShape::setEndAngle(qreal angle)
{
m_endAngle = angle;
updateKindHandle();
updateAngleHandles();
updatePath(size());
}
qreal EllipseShape::endAngle() const
{
return m_endAngle;
}
QString EllipseShape::pathShapeId() const
{
return EllipseShapeId;
}
bool EllipseShape::saveSvg(SvgSavingContext &context)
{
if (type() == EllipseShape::Arc && startAngle() == endAngle()) {
const QSizeF size = this->size();
const bool isCircle = size.width() == size.height();
context.shapeWriter().startElement(isCircle ? "circle" : "ellipse");
context.shapeWriter().addAttribute("id", context.getID(this));
context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation()));
if (isCircle) {
context.shapeWriter().addAttributePt("r", 0.5 * size.width());
} else {
context.shapeWriter().addAttributePt("rx", 0.5 * size.width());
context.shapeWriter().addAttributePt("ry", 0.5 * size.height());
}
context.shapeWriter().addAttributePt("cx", 0.5 * size.width());
context.shapeWriter().addAttributePt("cy", 0.5 * size.height());
SvgStyleWriter::saveSvgStyle(this, context);
context.shapeWriter().endElement();
} else {
- // the svg writer takes care of saving this shape as a path shape
- return false;
+ context.shapeWriter().startElement("path");
+ context.shapeWriter().addAttribute("id", context.getID(this));
+ context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation()));
+
+ context.shapeWriter().addAttribute("sodipodi:type", "arc");
+
+ context.shapeWriter().addAttributePt("sodipodi:rx", m_radii.x());
+ context.shapeWriter().addAttributePt("sodipodi:ry", m_radii.y());
+
+ context.shapeWriter().addAttributePt("sodipodi:cx", m_center.x());
+ context.shapeWriter().addAttributePt("sodipodi:cy", m_center.y());
+
+ context.shapeWriter().addAttribute("sodipodi:start", 2 * M_PI - kisDegreesToRadians(endAngle()));
+ context.shapeWriter().addAttribute("sodipodi:end", 2 * M_PI - kisDegreesToRadians(startAngle()));
+
+ switch (type()) {
+ case Pie:
+ // noop
+ break;
+ case Chord:
+ context.shapeWriter().addAttribute("sodipodi:arc-type", "chord");
+ case Arc:
+ context.shapeWriter().addAttribute("sodipodi:open", "true");
+ break;
+ }
+
+ context.shapeWriter().addAttribute("d", this->toString(context.userSpaceTransform()));
+
+ SvgStyleWriter::saveSvgStyle(this, context);
+
+ context.shapeWriter().endElement();
}
return true;
}
bool EllipseShape::loadSvg(const KoXmlElement &element, SvgLoadingContext &context)
{
qreal rx = 0, ry = 0;
+ qreal cx = 0;
+ qreal cy = 0;
+ qreal start = 0;
+ qreal end = 0;
+ EllipseType type = Arc;
+
+ const QString extendedNamespace =
+ element.attribute("sodipodi:type") == "arc" ? "sodipodi" :
+ element.attribute("krita:type") == "arc" ? "krita" : "";
+
if (element.tagName() == "ellipse") {
rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("rx"));
ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute("ry"));
+ cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0"));
+ cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0"));
} else if (element.tagName() == "circle") {
rx = ry = SvgUtil::parseUnitXY(context.currentGC(), element.attribute("r"));
+ cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0"));
+ cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0"));
+
+ } else if (element.tagName() == "path" && !extendedNamespace.isEmpty()) {
+ rx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":rx"));
+ ry = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":ry"));
+ cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute(extendedNamespace + ":cx", "0"));
+ cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute(extendedNamespace + ":cy", "0"));
+ start = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":end"));
+ end = 2 * M_PI - SvgUtil::parseNumber(element.attribute(extendedNamespace + ":start"));
+
+ const QString kritaArcType =
+ element.attribute("sodipodi:arc-type", element.attribute("krita:arcType"));
+
+ if (kritaArcType.isEmpty()) {
+ if (element.attribute("sodipodi:open", "false") == "false") {
+ type = Pie;
+ }
+ } else if (kritaArcType == "pie") {
+ type = Pie;
+ } else if (kritaArcType == "chord") {
+ type = Chord;
+ }
} else {
return false;
}
- const qreal cx = SvgUtil::parseUnitX(context.currentGC(), element.attribute("cx", "0"));
- const qreal cy = SvgUtil::parseUnitY(context.currentGC(), element.attribute("cy", "0"));
setSize(QSizeF(2 * rx, 2 * ry));
setPosition(QPointF(cx - rx, cy - ry));
if (rx == 0.0 || ry == 0.0) {
setVisible(false);
}
+ if (start != 0 || start != end) {
+ setStartAngle(kisRadiansToDegrees(start));
+ setEndAngle(kisRadiansToDegrees(end));
+ setType(type);
+ }
+
return true;
}
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShape.h b/plugins/flake/pathshapes/ellipse/EllipseShape.h
index 1b439ed764..c392616cab 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShape.h
+++ b/plugins/flake/pathshapes/ellipse/EllipseShape.h
@@ -1,118 +1,122 @@
/* This file is part of the KDE project
Copyright (C) 2006-2007 Thorsten Zachmann <zachmann@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.
*/
#ifndef KOELLIPSESHAPE_H
#define KOELLIPSESHAPE_H
#include "KoParameterShape.h"
#include <SvgShape.h>
#define EllipseShapeId "EllipseShape"
/**
* This class adds support for arc, pie, chord, circle and ellipse
* shapes. The ellipse/circle radii are defined by the actual size
* of the ellipse shape which can be changed with the setSize
* method.
*/
class EllipseShape : public KoParameterShape, public SvgShape
{
public:
/// the possible ellipse types
enum EllipseType {
Arc = 0, ///< an ellipse arc
Pie = 1, ///< an ellipse pie
Chord = 2 ///< an ellipse chord
};
EllipseShape();
virtual ~EllipseShape();
+ KoShape* cloneShape() const override;
+
void setSize(const QSizeF &newSize);
virtual QPointF normalize();
/**
* Sets the type of the ellipse.
* @param type the new ellipse type
*/
void setType(EllipseType type);
/// Returns the actual ellipse type
EllipseType type() const;
/**
* Sets the start angle of the ellipse.
* @param angle the new start angle in degree
*/
void setStartAngle(qreal angle);
/// Returns the actual ellipse start angle in degree
qreal startAngle() const;
/**
* Sets the end angle of the ellipse.
* @param angle the new end angle in degree
*/
void setEndAngle(qreal angle);
/// Returns the actual ellipse end angle in degree
qreal endAngle() const;
/// reimplemented
virtual QString pathShapeId() const;
/// reimplemented from SvgShape
virtual bool saveSvg(SvgSavingContext &context);
/// reimplemented from SvgShape
virtual bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context);
protected:
// reimplemented
virtual void saveOdf(KoShapeSavingContext &context) const;
// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void updatePath(const QSizeF &size);
void createPoints(int requiredPointCount);
private:
qreal sweepAngle() const;
void updateKindHandle();
void updateAngleHandles();
+ EllipseShape(const EllipseShape &rhs);
+
// start angle in degree
qreal m_startAngle;
// end angle in degree
qreal m_endAngle;
// angle for modifying the kind in radiant
qreal m_kindAngle;
// the center of the ellipse
QPointF m_center;
// the radii of the ellips
QPointF m_radii;
// the actual ellipse type
EllipseType m_type;
};
#endif /* KOELLIPSESHAPE_H */
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.cpp b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.cpp
index 969ced80cb..64aedfb8ed 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.cpp
+++ b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.cpp
@@ -1,75 +1,95 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "EllipseShapeConfigCommand.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
EllipseShapeConfigCommand::EllipseShapeConfigCommand(EllipseShape *ellipse, EllipseShape::EllipseType type, qreal startAngle, qreal endAngle, KUndo2Command *parent)
: KUndo2Command(parent)
, m_ellipse(ellipse)
, m_newType(type)
, m_newStartAngle(startAngle)
, m_newEndAngle(endAngle)
{
Q_ASSERT(m_ellipse);
setText(kundo2_i18n("Change ellipse"));
m_oldType = m_ellipse->type();
m_oldStartAngle = m_ellipse->startAngle();
m_oldEndAngle = m_ellipse->endAngle();
}
void EllipseShapeConfigCommand::redo()
{
KUndo2Command::redo();
m_ellipse->update();
if (m_oldType != m_newType) {
m_ellipse->setType(m_newType);
}
if (m_oldStartAngle != m_newStartAngle) {
m_ellipse->setStartAngle(m_newStartAngle);
}
if (m_oldEndAngle != m_newEndAngle) {
m_ellipse->setEndAngle(m_newEndAngle);
}
m_ellipse->update();
}
void EllipseShapeConfigCommand::undo()
{
KUndo2Command::undo();
m_ellipse->update();
if (m_oldType != m_newType) {
m_ellipse->setType(m_oldType);
}
if (m_oldStartAngle != m_newStartAngle) {
m_ellipse->setStartAngle(m_oldStartAngle);
}
if (m_oldEndAngle != m_newEndAngle) {
m_ellipse->setEndAngle(m_oldEndAngle);
}
m_ellipse->update();
}
+
+int EllipseShapeConfigCommand::id() const
+{
+ return KisCommandUtils::ChangeEllipseShapeId;
+}
+bool EllipseShapeConfigCommand::mergeWith(const KUndo2Command *command)
+{
+ const EllipseShapeConfigCommand *other = dynamic_cast<const EllipseShapeConfigCommand*>(command);
+
+ if (!other || other->m_ellipse != m_ellipse) {
+ return false;
+ }
+
+ m_newType = other->m_newType;
+ m_newStartAngle = other->m_newStartAngle;
+ m_newEndAngle = other->m_newEndAngle;
+
+ return true;
+}
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.h b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.h
index 16a23b8818..90904bb417 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.h
+++ b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigCommand.h
@@ -1,54 +1,58 @@
/* 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 ELLIPSESHAPECONFIGCOMMAND_H
#define ELLIPSESHAPECONFIGCOMMAND_H
#include "EllipseShape.h"
#include <kundo2command.h>
/// The undo / redo command for configuring an ellipse shape
class EllipseShapeConfigCommand : public KUndo2Command
{
public:
/**
* Configures an ellipse shape
* @param ellipse the ellipse shape to configure
* @param type the ellipse type
* @param startAngle the start angle
* @param endAngle the end angle
* @param parent the optional parent command
*/
EllipseShapeConfigCommand(EllipseShape *ellipse, EllipseShape::EllipseType type, qreal startAngle, qreal startEndAngle, KUndo2Command *parent = 0);
/// redo the command
- virtual void redo();
+ void redo() override;
/// revert the actions done in redo
- virtual void undo();
+ void undo() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
EllipseShape *m_ellipse;
EllipseShape::EllipseType m_oldType;
qreal m_oldStartAngle;
qreal m_oldEndAngle;
EllipseShape::EllipseType m_newType;
qreal m_newStartAngle;
qreal m_newEndAngle;
};
#endif // ELLIPSESHAPECONFIGCOMMAND_H
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.cpp b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.cpp
index 211b5b1b4c..b9cc1ed2f7 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.cpp
+++ b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.cpp
@@ -1,98 +1,114 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "EllipseShapeConfigWidget.h"
#include "EllipseShapeConfigCommand.h"
#include <klocalizedstring.h>
+#include <kis_signals_blocker.h>
+#include "kis_assert.h"
EllipseShapeConfigWidget::EllipseShapeConfigWidget()
+ : m_ellipse(0)
{
widget.setupUi(this);
widget.ellipseType->clear();
widget.ellipseType->addItem(i18n("Arc"));
widget.ellipseType->addItem(i18n("Pie"));
widget.ellipseType->addItem(i18n("Chord"));
widget.startAngle->setMinimum(0.0);
widget.startAngle->setMaximum(360.0);
widget.endAngle->setMinimum(0.0);
widget.endAngle->setMaximum(360.0);
connect(widget.ellipseType, SIGNAL(currentIndexChanged(int)), this, SIGNAL(propertyChanged()));
- connect(widget.startAngle, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged()));
- connect(widget.endAngle, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged()));
+ connect(widget.startAngle, SIGNAL(valueChanged(double)), this, SIGNAL(propertyChanged()));
+ connect(widget.endAngle, SIGNAL(valueChanged(double)), this, SIGNAL(propertyChanged()));
connect(widget.closeEllipse, SIGNAL(clicked(bool)), this, SLOT(closeEllipse()));
}
void EllipseShapeConfigWidget::open(KoShape *shape)
{
- m_ellipse = dynamic_cast<EllipseShape *>(shape);
- if (!m_ellipse) {
- return;
+ if (m_ellipse) {
+ m_ellipse->removeShapeChangeListener(this);
}
- widget.ellipseType->blockSignals(true);
- widget.startAngle->blockSignals(true);
- widget.endAngle->blockSignals(true);
+ m_ellipse = dynamic_cast<EllipseShape *>(shape);
+ if (!m_ellipse) return;
- widget.ellipseType->setCurrentIndex(m_ellipse->type());
- widget.startAngle->setValue(m_ellipse->startAngle());
- widget.endAngle->setValue(m_ellipse->endAngle());
+ loadPropertiesFromShape(m_ellipse);
- widget.ellipseType->blockSignals(false);
- widget.startAngle->blockSignals(false);
- widget.endAngle->blockSignals(false);
+ m_ellipse->addShapeChangeListener(this);
+}
+
+void EllipseShapeConfigWidget::loadPropertiesFromShape(EllipseShape *shape)
+{
+ KisSignalsBlocker b(widget.ellipseType, widget.startAngle, widget.endAngle);
+
+ widget.ellipseType->setCurrentIndex(shape->type());
+ widget.startAngle->setValue(shape->startAngle());
+ widget.endAngle->setValue(shape->endAngle());
}
+
void EllipseShapeConfigWidget::save()
{
if (!m_ellipse) {
return;
}
m_ellipse->setType(static_cast<EllipseShape::EllipseType>(widget.ellipseType->currentIndex()));
m_ellipse->setStartAngle(widget.startAngle->value());
m_ellipse->setEndAngle(widget.endAngle->value());
}
KUndo2Command *EllipseShapeConfigWidget::createCommand()
{
if (!m_ellipse) {
return 0;
} else {
EllipseShape::EllipseType type = static_cast<EllipseShape::EllipseType>(widget.ellipseType->currentIndex());
return new EllipseShapeConfigCommand(m_ellipse, type, widget.startAngle->value(), widget.endAngle->value());
}
}
+void EllipseShapeConfigWidget::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_ellipse && shape == m_ellipse);
+
+ if (type == KoShape::ParameterChanged) {
+ open(m_ellipse);
+ }
+}
+
void EllipseShapeConfigWidget::closeEllipse()
{
widget.startAngle->blockSignals(true);
widget.endAngle->blockSignals(true);
widget.startAngle->setValue(0.0);
widget.endAngle->setValue(360.0);
widget.startAngle->blockSignals(false);
widget.endAngle->blockSignals(false);
emit propertyChanged();
}
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.h b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.h
index a2cb9b3c92..e24b21aa7f 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.h
+++ b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.h
@@ -1,51 +1,59 @@
/* 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 ELLIPSESHAPECONFIGWIDGET_H
#define ELLIPSESHAPECONFIGWIDGET_H
#include "EllipseShape.h"
#include <ui_EllipseShapeConfigWidget.h>
#include <KoShapeConfigWidgetBase.h>
+#include <KoShape.h>
-class EllipseShapeConfigWidget : public KoShapeConfigWidgetBase
+class EllipseShapeConfigWidget : public KoShapeConfigWidgetBase, public KoShape::ShapeChangeListener
{
Q_OBJECT
public:
EllipseShapeConfigWidget();
/// reimplemented
- virtual void open(KoShape *shape);
+ void open(KoShape *shape) override;
/// reimplemented
- virtual void save();
+ void save() override;
/// reimplemented
- virtual bool showOnShapeCreate()
+ bool showOnShapeCreate() override
{
return false;
}
/// reimplemented
- virtual KUndo2Command *createCommand();
+ KUndo2Command *createCommand() override;
+
+ void notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) override;
+
private Q_SLOTS:
void closeEllipse();
+
+private:
+ void loadPropertiesFromShape(EllipseShape *shape);
+
private:
Ui::EllipseShapeConfigWidget widget;
EllipseShape *m_ellipse;
};
#endif // ELLIPSESHAPECONFIGWIDGET_H
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.ui b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.ui
index 22ae46b850..8c821d947b 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.ui
+++ b/plugins/flake/pathshapes/ellipse/EllipseShapeConfigWidget.ui
@@ -1,92 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EllipseShapeConfigWidget</class>
<widget class="QWidget" name="EllipseShapeConfigWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>188</width>
<height>173</height>
</rect>
</property>
<property name="windowTitle">
<string>Ellipse Shape</string>
</property>
<layout class="QGridLayout">
+ <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>
+ <property name="verticalSpacing">
+ <number>3</number>
+ </property>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="ellipseType"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Start angle:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisDoubleParseSpinBox" name="startAngle">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string>°</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>End angle:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="KisDoubleParseSpinBox" name="endAngle">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string>°</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QPushButton" name="closeEllipse">
<property name="text">
<string>Close ellipse</string>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>40</height>
+ <height>6</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KisDoubleParseSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>kis_double_parse_spin_box.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/plugins/flake/pathshapes/ellipse/EllipseShapeFactory.cpp b/plugins/flake/pathshapes/ellipse/EllipseShapeFactory.cpp
index 0e4d7d74e5..5b30d53dc3 100644
--- a/plugins/flake/pathshapes/ellipse/EllipseShapeFactory.cpp
+++ b/plugins/flake/pathshapes/ellipse/EllipseShapeFactory.cpp
@@ -1,76 +1,80 @@
/* 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.
*/
#include "EllipseShapeFactory.h"
#include "EllipseShape.h"
#include "EllipseShapeConfigWidget.h"
#include <KoShapeStroke.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoGradientBackground.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
+#include "kis_pointer_utils.h"
+
EllipseShapeFactory::EllipseShapeFactory()
: KoShapeFactoryBase(EllipseShapeId, i18n("Ellipse"))
{
setToolTip(i18n("An ellipse"));
setIconName(koIconNameCStr("ellipse-shape"));
setFamily("geometric");
setLoadingPriority(1);
QList<QPair<QString, QStringList> > elementNamesList;
elementNamesList.append(qMakePair(QString(KoXmlNS::draw), QStringList("circle")));
elementNamesList.append(qMakePair(QString(KoXmlNS::draw), QStringList("ellipse")));
elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("circle")));
elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("ellipse")));
+ elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("sodipodi:arc")));
+ elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("krita:arc")));
setXmlElements(elementNamesList);
}
KoShape *EllipseShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
EllipseShape *ellipse = new EllipseShape();
- ellipse->setStroke(new KoShapeStroke(1.0));
+ ellipse->setStroke(toQShared(new KoShapeStroke(1.0)));
ellipse->setShapeId(KoPathShapeId);
QRadialGradient *gradient = new QRadialGradient(QPointF(0.5, 0.5), 0.5, QPointF(0.25, 0.25));
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
gradient->setColorAt(0.0, Qt::white);
gradient->setColorAt(1.0, Qt::green);
ellipse->setBackground(QSharedPointer<KoGradientBackground>(new KoGradientBackground(gradient)));
return ellipse;
}
bool EllipseShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
return (e.localName() == "ellipse" || e.localName() == "circle")
&& e.namespaceURI() == KoXmlNS::draw;
}
QList<KoShapeConfigWidgetBase *> EllipseShapeFactory::createShapeOptionPanels()
{
QList<KoShapeConfigWidgetBase *> panels;
panels.append(new EllipseShapeConfigWidget());
return panels;
}
diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp
index a65c6278b7..b131fe8516 100644
--- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp
+++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.cpp
@@ -1,709 +1,742 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2010,2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2009-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2010 Carlos Licea <carlos@kdab.com>
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
* Contact: Suresh Chande suresh.chande@nokia.com
* Copyright (C) 2009-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 <KoParameterShape_p.h>
+
#include "EnhancedPathShape.h"
#include "EnhancedPathCommand.h"
#include "EnhancedPathParameter.h"
#include "EnhancedPathHandle.h"
#include "EnhancedPathFormula.h"
#include <KoXmlNS.h>
#include <KoXmlWriter.h>
#include <KoXmlReader.h>
#include <KoShapeSavingContext.h>
#include <KoUnit.h>
#include <KoOdfWorkaround.h>
#include <KoPathPoint.h>
EnhancedPathShape::EnhancedPathShape(const QRect &viewBox)
: m_viewBox(viewBox)
, m_viewBoxOffset(0.0, 0.0)
, m_mirrorVertically(false)
, m_mirrorHorizontally(false)
, m_pathStretchPointX(-1)
, m_pathStretchPointY(-1)
, m_cacheResults(false)
{
}
+EnhancedPathShape::EnhancedPathShape(const EnhancedPathShape &rhs)
+ : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)),
+ m_viewBox(rhs.m_viewBox),
+ m_viewBound(rhs.m_viewBound),
+ m_viewMatrix(rhs.m_viewMatrix),
+ m_mirrorMatrix(rhs.m_mirrorMatrix),
+ m_viewBoxOffset(rhs.m_viewBoxOffset),
+ m_textArea(rhs.m_textArea),
+ m_commands(rhs.m_commands),
+ m_enhancedHandles(rhs.m_enhancedHandles),
+ m_formulae(rhs.m_formulae),
+ m_modifiers(rhs.m_modifiers),
+ m_parameters(rhs.m_parameters),
+ m_mirrorVertically(rhs.m_mirrorVertically),
+ m_mirrorHorizontally(rhs.m_mirrorHorizontally),
+ m_pathStretchPointX(rhs.m_pathStretchPointX),
+ m_pathStretchPointY(rhs.m_pathStretchPointY),
+ m_resultChache(rhs.m_resultChache),
+ m_cacheResults(rhs.m_cacheResults)
+{
+}
+
EnhancedPathShape::~EnhancedPathShape()
{
reset();
}
+KoShape *EnhancedPathShape::cloneShape() const
+{
+ return new EnhancedPathShape(*this);
+}
+
void EnhancedPathShape::reset()
{
qDeleteAll(m_commands);
m_commands.clear();
qDeleteAll(m_enhancedHandles);
m_enhancedHandles.clear();
setHandles(QList<QPointF>());
qDeleteAll(m_formulae);
m_formulae.clear();
qDeleteAll(m_parameters);
m_parameters.clear();
m_modifiers.clear();
m_viewMatrix.reset();
m_viewBoxOffset = QPointF();
clear();
m_textArea.clear();
}
void EnhancedPathShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
EnhancedPathHandle *handle = m_enhancedHandles[ handleId ];
if (handle) {
handle->changePosition(shapeToViewbox(point));
}
}
void EnhancedPathShape::updatePath(const QSizeF &size)
{
+ Q_D(KoParameterShape);
+
if (isParametricShape()) {
clear();
enableResultCache(true);
foreach (EnhancedPathCommand *cmd, m_commands) {
cmd->execute();
}
enableResultCache(false);
qreal stretchPointsScale = 1;
bool isStretched = useStretchPoints(size, stretchPointsScale);
m_viewBound = outline().boundingRect();
m_mirrorMatrix.reset();
m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y());
m_mirrorMatrix.scale(m_mirrorHorizontally ? -1 : 1, m_mirrorVertically ? -1 : 1);
m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y());
QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y());
// if stretch points are set than stretch the path manually
if (isStretched) {
//if the path was stretched manually the stretch matrix is not more valid
//and it has to be recalculated so that stretching in x and y direction is the same
matrix.scale(stretchPointsScale, stretchPointsScale);
matrix = m_mirrorMatrix * matrix;
} else {
matrix = m_mirrorMatrix * m_viewMatrix * matrix;
}
- foreach (KoSubpath *subpath, m_subpaths) {
+ foreach (KoSubpath *subpath, d->subpaths) {
foreach (KoPathPoint *point, *subpath) {
point->map(matrix);
}
}
const int handleCount = m_enhancedHandles.count();
QList<QPointF> handles;
for (int i = 0; i < handleCount; ++i) {
handles.append(matrix.map(m_enhancedHandles[i]->position()));
}
setHandles(handles);
normalize();
}
}
void EnhancedPathShape::setSize(const QSizeF &newSize)
{
// handle offset
KoParameterShape::setSize(newSize);
// calculate scaling factors from viewbox size to shape size
qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width() / m_viewBound.width();
qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height() / m_viewBound.height();
// create view matrix, take mirroring into account
m_viewMatrix.reset();
m_viewMatrix.scale(xScale, yScale);
updatePath(newSize);
}
QPointF EnhancedPathShape::normalize()
{
QPointF offset = KoParameterShape::normalize();
m_viewBoxOffset -= offset;
return offset;
}
QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const
{
return (m_mirrorMatrix * m_viewMatrix).inverted().map(point - m_viewBoxOffset);
}
void EnhancedPathShape::evaluateHandles()
{
const int handleCount = m_enhancedHandles.count();
QList<QPointF> handles;
for (int i = 0; i < handleCount; ++i) {
handles.append(m_enhancedHandles[i]->position());
}
setHandles(handles);
}
QRect EnhancedPathShape::viewBox() const
{
return m_viewBox;
}
qreal EnhancedPathShape::evaluateReference(const QString &reference)
{
if (reference.isEmpty()) {
return 0.0;
}
const char c = reference[0].toLatin1();
qreal res = 0.0;
switch (c) {
// referenced modifier
case '$': {
bool success = false;
int modifierIndex = reference.mid(1).toInt(&success);
res = m_modifiers.value(modifierIndex);
break;
}
// referenced formula
case '?': {
QString fname = reference.mid(1);
if (m_cacheResults && m_resultChache.contains(fname)) {
res = m_resultChache.value(fname);
} else {
FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname);
if (formulaIt != m_formulae.constEnd()) {
EnhancedPathFormula *formula = formulaIt.value();
if (formula) {
res = formula->evaluate();
if (m_cacheResults) {
m_resultChache.insert(fname, res);
}
}
}
}
break;
}
// maybe an identifier ?
default:
EnhancedPathNamedParameter p(reference, this);
res = p.evaluate();
break;
}
return res;
}
qreal EnhancedPathShape::evaluateConstantOrReference(const QString &val)
{
bool ok = true;
qreal res = val.toDouble(&ok);
if (ok) {
return res;
}
return evaluateReference(val);
}
void EnhancedPathShape::modifyReference(const QString &reference, qreal value)
{
if (reference.isEmpty()) {
return;
}
const char c = reference[0].toLatin1();
if (c == '$') {
bool success = false;
int modifierIndex = reference.mid(1).toInt(&success);
if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) {
m_modifiers[modifierIndex] = value;
}
}
}
EnhancedPathParameter *EnhancedPathShape::parameter(const QString &text)
{
Q_ASSERT(! text.isEmpty());
ParameterStore::const_iterator parameterIt = m_parameters.constFind(text);
if (parameterIt != m_parameters.constEnd()) {
return parameterIt.value();
} else {
EnhancedPathParameter *parameter = 0;
const char c = text[0].toLatin1();
if (c == '$' || c == '?') {
parameter = new EnhancedPathReferenceParameter(text, this);
} else {
bool success = false;
qreal constant = text.toDouble(&success);
if (success) {
parameter = new EnhancedPathConstantParameter(constant, this);
} else {
Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text);
if (identifier != IdentifierUnknown) {
parameter = new EnhancedPathNamedParameter(identifier, this);
}
}
}
if (parameter) {
m_parameters[text] = parameter;
}
return parameter;
}
}
void EnhancedPathShape::addFormula(const QString &name, const QString &formula)
{
if (name.isEmpty() || formula.isEmpty()) {
return;
}
m_formulae[name] = new EnhancedPathFormula(formula, this);
}
void EnhancedPathShape::addHandle(const QMap<QString, QVariant> &handle)
{
if (handle.isEmpty()) {
return;
}
if (!handle.contains("draw:handle-position")) {
return;
}
QVariant position = handle.value("draw:handle-position");
QStringList tokens = position.toString().simplified().split(' ');
if (tokens.count() < 2) {
return;
}
EnhancedPathHandle *newHandle = new EnhancedPathHandle(this);
newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1]));
// check if we have a polar handle
if (handle.contains("draw:handle-polar")) {
QVariant polar = handle.value("draw:handle-polar");
QStringList tokens = polar.toString().simplified().split(' ');
if (tokens.count() == 2) {
newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1]));
QVariant minRadius = handle.value("draw:handle-radius-range-minimum");
QVariant maxRadius = handle.value("draw:handle-radius-range-maximum");
if (minRadius.isValid() && maxRadius.isValid()) {
newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString()));
}
}
} else {
QVariant minX = handle.value("draw:handle-range-x-minimum");
QVariant maxX = handle.value("draw:handle-range-x-maximum");
if (minX.isValid() && maxX.isValid()) {
newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString()));
}
QVariant minY = handle.value("draw:handle-range-y-minimum");
QVariant maxY = handle.value("draw:handle-range-y-maximum");
if (minY.isValid() && maxY.isValid()) {
newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString()));
}
}
m_enhancedHandles.append(newHandle);
evaluateHandles();
}
void EnhancedPathShape::addModifiers(const QString &modifiers)
{
if (modifiers.isEmpty()) {
return;
}
QStringList tokens = modifiers.simplified().split(' ');
int tokenCount = tokens.count();
for (int i = 0; i < tokenCount; ++i) {
m_modifiers.append(tokens[i].toDouble());
}
}
void EnhancedPathShape::addCommand(const QString &command)
{
addCommand(command, true);
}
void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate)
{
QString commandStr = command.simplified();
if (commandStr.isEmpty()) {
return;
}
// the first character is the command
EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this);
// strip command char
commandStr = commandStr.mid(1).simplified();
// now parse the command parameters
if (!commandStr.isEmpty()) {
QStringList tokens = commandStr.split(' ');
for (int i = 0; i < tokens.count(); ++i) {
cmd->addParameter(parameter(tokens[i]));
}
}
m_commands.append(cmd);
if (triggerUpdate) {
updatePath(size());
}
}
bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale)
{
+ Q_D(KoParameterShape);
+
bool retval = false;
if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) {
qreal scaleX = size.width();
qreal scaleY = size.height();
if (m_viewBox.width() / m_viewBox.height() < scaleX / scaleY) {
qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width();
- foreach (KoSubpath *subpath, m_subpaths) {
+ foreach (KoSubpath *subpath, d->subpaths) {
foreach (KoPathPoint *currPoint, *subpath) {
if (currPoint->point().x() >= m_pathStretchPointX &&
currPoint->controlPoint1().x() >= m_pathStretchPointX &&
currPoint->controlPoint2().x() >= m_pathStretchPointX) {
currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y()));
currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX,
currPoint->controlPoint1().y()));
currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX,
currPoint->controlPoint2().y()));
retval = true;
}
}
}
scale = scaleY / m_viewBox.height();
} else if (m_viewBox.width() / m_viewBox.height() > scaleX / scaleY) {
qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height();
- foreach (KoSubpath *subpath, m_subpaths) {
+ foreach (KoSubpath *subpath, d->subpaths) {
foreach (KoPathPoint *currPoint, *subpath) {
if (currPoint->point().y() >= m_pathStretchPointY &&
currPoint->controlPoint1().y() >= m_pathStretchPointY &&
currPoint->controlPoint2().y() >= m_pathStretchPointY) {
currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY));
currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(),
currPoint->controlPoint1().y() + deltaY));
currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(),
currPoint->controlPoint2().y() + deltaY));
retval = true;
}
}
}
scale = scaleX / m_viewBox.width();
}
}
return retval;
}
void EnhancedPathShape::saveOdf(KoShapeSavingContext &context) const
{
if (isParametricShape()) {
context.xmlWriter().startElement("draw:custom-shape");
const QSizeF currentSize = outline().boundingRect().size();
// save the right position so that when loading we fit the viewbox
// to the right position without getting any wrong scaling
// -> calculate the right position from the current 0 position / viewbound ratio
// this is e.g. the case when there is a callout that goes into negative viewbound coordinates
QPointF topLeft = m_viewBound.topLeft();
QPointF diff;
if (qAbs(topLeft.x()) > 1E-5) {
diff.setX(topLeft.x()*currentSize.width() / m_viewBound.width());
}
if (qAbs(topLeft.y()) > 1E-5) {
diff.setY(topLeft.y()*currentSize.height() / m_viewBound.height());
}
if (diff.isNull()) {
saveOdfAttributes(context, OdfAllAttributes & ~OdfSize);
} else {
//FIXME: this needs to be fixed for shapes that are transformed by rotation or skewing
QTransform offset(context.shapeOffset(this));
QTransform newOffset(offset);
newOffset.translate(-diff.x(), -diff.y());
context.addShapeOffset(this, newOffset);
saveOdfAttributes(context, OdfAllAttributes & ~OdfSize);
if (offset.isIdentity()) {
context.removeShapeOffset(this);
} else {
context.addShapeOffset(this, offset);
}
}
// save the right size so that when loading we fit the viewbox
// to the right size without getting any wrong scaling
// -> calculate the right size from the current size/viewbound ratio
context.xmlWriter().addAttributePt("svg:width", currentSize.width() == 0 ? 0 : m_viewBox.width()*currentSize.width() / m_viewBound.width());
context.xmlWriter().addAttributePt("svg:height", currentSize.height() == 0 ? 0 : m_viewBox.height()*currentSize.height() / m_viewBound.height());
saveText(context);
context.xmlWriter().startElement("draw:enhanced-geometry");
context.xmlWriter().addAttribute("svg:viewBox", QString("%1 %2 %3 %4").arg(m_viewBox.x()).arg(m_viewBox.y()).arg(m_viewBox.width()).arg(m_viewBox.height()));
if (m_pathStretchPointX != -1) {
context.xmlWriter().addAttribute("draw:path-stretchpoint-x", m_pathStretchPointX);
}
if (m_pathStretchPointY != -1) {
context.xmlWriter().addAttribute("draw:path-stretchpoint-y", m_pathStretchPointY);
}
if (m_mirrorHorizontally) {
context.xmlWriter().addAttribute("draw:mirror-horizontal", "true");
}
if (m_mirrorVertically) {
context.xmlWriter().addAttribute("draw:mirror-vertical", "true");
}
QString modifiers;
foreach (qreal modifier, m_modifiers) {
modifiers += QString::number(modifier) + ' ';
}
context.xmlWriter().addAttribute("draw:modifiers", modifiers.trimmed());
if (m_textArea.size() >= 4) {
context.xmlWriter().addAttribute("draw:text-areas", m_textArea.join(" "));
}
QString path;
foreach (EnhancedPathCommand *c, m_commands) {
path += c->toString() + ' ';
}
context.xmlWriter().addAttribute("draw:enhanced-path", path.trimmed());
FormulaStore::const_iterator i = m_formulae.constBegin();
for (; i != m_formulae.constEnd(); ++i) {
context.xmlWriter().startElement("draw:equation");
context.xmlWriter().addAttribute("draw:name", i.key());
context.xmlWriter().addAttribute("draw:formula", i.value()->toString());
context.xmlWriter().endElement(); // draw:equation
}
foreach (EnhancedPathHandle *handle, m_enhancedHandles) {
handle->saveOdf(context);
}
context.xmlWriter().endElement(); // draw:enhanced-geometry
saveOdfCommonChildElements(context);
context.xmlWriter().endElement(); // draw:custom-shape
} else {
KoPathShape::saveOdf(context);
}
}
bool EnhancedPathShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
reset();
const KoXmlElement enhancedGeometry(KoXml::namedItemNS(element, KoXmlNS::draw, "enhanced-geometry"));
if (!enhancedGeometry.isNull()) {
setPathStretchPointX(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-x", "-1").toDouble());
setPathStretchPointY(enhancedGeometry.attributeNS(KoXmlNS::draw, "path-stretchpoint-y", "-1").toDouble());
// load the modifiers
QString modifiers = enhancedGeometry.attributeNS(KoXmlNS::draw, "modifiers", "");
if (!modifiers.isEmpty()) {
addModifiers(modifiers);
}
m_textArea = enhancedGeometry.attributeNS(KoXmlNS::draw, "text-areas", "").split(' ');
if (m_textArea.size() >= 4) {
setResizeBehavior(TextFollowsPreferredTextRect);
}
KoXmlElement grandChild;
forEachElement(grandChild, enhancedGeometry) {
if (grandChild.namespaceURI() != KoXmlNS::draw) {
continue;
}
if (grandChild.localName() == "equation") {
QString name = grandChild.attributeNS(KoXmlNS::draw, "name");
QString formula = grandChild.attributeNS(KoXmlNS::draw, "formula");
addFormula(name, formula);
} else if (grandChild.localName() == "handle") {
EnhancedPathHandle *handle = new EnhancedPathHandle(this);
if (handle->loadOdf(grandChild, context)) {
m_enhancedHandles.append(handle);
evaluateHandles();
} else {
delete handle;
}
}
}
setMirrorHorizontally(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-horizontal") == "true");
setMirrorVertically(enhancedGeometry.attributeNS(KoXmlNS::draw, "mirror-vertical") == "true");
// load the enhanced path data
QString path = enhancedGeometry.attributeNS(KoXmlNS::draw, "enhanced-path", "");
#ifndef NWORKAROUND_ODF_BUGS
KoOdfWorkaround::fixEnhancedPath(path, enhancedGeometry, context);
#endif
// load the viewbox
m_viewBox = loadOdfViewbox(enhancedGeometry);
if (!path.isEmpty()) {
parsePathData(path);
}
if (m_viewBox.isEmpty()) {
// if there is no view box defined make it is big as the path.
m_viewBox = m_viewBound.toAlignedRect();
}
}
QSizeF size;
size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
// the viewbox is to be fitted into the size of the shape, so before setting
// the size we just loaded // we set the viewbox to be the basis to calculate
// the viewbox matrix from
m_viewBound = m_viewBox;
setSize(size);
QPointF pos;
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
setPosition(pos - m_viewMatrix.map(QPointF(0, 0)) - m_viewBoxOffset);
loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements);
loadText(element, context);
return true;
}
void EnhancedPathShape::parsePathData(const QString &data)
{
if (data.isEmpty()) {
return;
}
int start = -1;
bool separator = true;
for (int i = 0; i < data.length(); ++i) {
QChar ch = data.at(i);
ushort uni_ch = ch.unicode();
if (separator && (uni_ch == 'M' || uni_ch == 'L'
|| uni_ch == 'C' || uni_ch == 'Z'
|| uni_ch == 'N' || uni_ch == 'F'
|| uni_ch == 'S' || uni_ch == 'T'
|| uni_ch == 'U' || uni_ch == 'A'
|| uni_ch == 'B' || uni_ch == 'W'
|| uni_ch == 'V' || uni_ch == 'X'
|| uni_ch == 'Y' || uni_ch == 'Q')) {
if (start != -1) { // process last chars
addCommand(data.mid(start, i - start), false);
}
start = i;
}
separator = ch.isSpace();
}
if (start < data.length()) {
addCommand(data.mid(start));
}
if (start != -1) {
updatePath(size());
}
}
void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally)
{
if (m_mirrorHorizontally != mirrorHorizontally) {
m_mirrorHorizontally = mirrorHorizontally;
updatePath(size());
}
}
void EnhancedPathShape::setMirrorVertically(bool mirrorVertically)
{
if (m_mirrorVertically != mirrorVertically) {
m_mirrorVertically = mirrorVertically;
updatePath(size());
}
}
void EnhancedPathShape::shapeChanged(ChangeType type, KoShape *shape)
{
KoParameterShape::shapeChanged(type, shape);
if (!shape || shape == this) {
if (type == ParentChanged || type == ParameterChanged) {
updateTextArea();
}
}
}
void EnhancedPathShape::updateTextArea()
{
if (m_textArea.size() >= 4) {
QRectF r = m_viewBox;
r.setLeft(evaluateConstantOrReference(m_textArea[0]));
r.setTop(evaluateConstantOrReference(m_textArea[1]));
r.setRight(evaluateConstantOrReference(m_textArea[2]));
r.setBottom(evaluateConstantOrReference(m_textArea[3]));
r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset);
setPreferredTextRect(r);
}
}
void EnhancedPathShape::enableResultCache(bool enable)
{
m_resultChache.clear();
m_cacheResults = enable;
}
void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX)
{
if (m_pathStretchPointX != pathStretchPointX) {
m_pathStretchPointX = pathStretchPointX;
}
}
void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY)
{
if (m_pathStretchPointY != pathStretchPointY) {
m_pathStretchPointY = pathStretchPointY;
}
}
diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h
index 33ba24aa37..ba3166038a 100644
--- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h
+++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShape.h
@@ -1,181 +1,185 @@
/* This file is part of the KDE project
* Copyright (C) 2007,2010,2011 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2010 Carlos Licea <carlos@kdab.com>
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
* Contact: Suresh Chande suresh.chande@nokia.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 KOENHANCEDPATHSHAPE_H
#define KOENHANCEDPATHSHAPE_H
#include <KoParameterShape.h>
#include <QList>
#include <QMap>
#include <QRectF>
#include <QStringList>
#define EnhancedPathShapeId "EnhancedPathShape"
class EnhancedPathCommand;
class EnhancedPathHandle;
class EnhancedPathFormula;
class EnhancedPathParameter;
class KoShapeSavingContext;
class KoShapeLoadingContext;
/**
* An enhanced shape is a custom shape which can be defined
* by enhanced geometry data.
* The data consists of a list of commands like moveto,
* lineto, curveto, etc. which are used to create the outline
* of the shape. The coordinates or parameters of the commands
* can be constant values, named variables (identifiers),
* modifiers, functions or formulae.
*/
class EnhancedPathShape : public KoParameterShape
{
public:
- explicit EnhancedPathShape(const QRect &viewBox);
+ EnhancedPathShape(const QRect &viewBox);
virtual ~EnhancedPathShape();
+ KoShape* cloneShape() const override;
+
/**
* Evaluates the given reference to a identifier, modifier or formula.
* @param reference the reference to evaluate
* @return the result of the evaluation
*/
qreal evaluateReference(const QString &reference);
/**
* Evaluates the given constant or reference to a identifier, modifier
* or formula.
* @param val the value to evaluate
* @return the result of the evaluation
*/
qreal evaluateConstantOrReference(const QString &val);
/**
* Attempts to modify a given reference.
*
* Only modifiers can me modified, others silently ignore the attempt.
*
* @param reference the reference to modify
* @param value the new value
*/
void modifyReference(const QString &reference, qreal value);
// from KoShape
virtual void setSize(const QSizeF &newSize);
// from KoParameterShape
virtual QPointF normalize();
/// Add formula with given name and textual represenation
void addFormula(const QString &name, const QString &formula);
/// Add a single handle with format: x y minX maxX minY maxY
void addHandle(const QMap<QString, QVariant> &handle);
/// Add modifiers with format: modifier0 modifier1 modifier2 ...
void addModifiers(const QString &modifiers);
/// Add command for instance "M 0 0"
void addCommand(const QString &command);
/// Returns the viewbox of the enhanced path shape
QRect viewBox() const;
/// Converts from shape coordinates to viewbox coordinates
QPointF shapeToViewbox(const QPointF &point) const;
/// Sets if the shape is to be mirrored horizontally before aplying any other transformations
//NOTE: in the standard nothing is mentioned about the priorities of the transformations"
//it's assumed like this because of the behavior shwon in OOo
void setMirrorHorizontally(bool mirrorHorizontally);
/// Sets if the shape is to be mirrored vertically before aplying any other transformations
//NOTE: in the standard nothing is mentioned about the priorities of the transformations"
//it's assumed like this because of the behavior shwon in OOo
void setMirrorVertically(bool mirrorVertically);
// Sets member variable representing draw:path-stretchpoint-x attribute
void setPathStretchPointX(qreal pathStretchPointX);
// Sets member variable representing draw:path-stretchpoint-y attribute
void setPathStretchPointY(qreal pathStretchPointY);
/// Returns parameter from given textual representation
EnhancedPathParameter *parameter(const QString &text);
protected:
// from KoShape
virtual void saveOdf(KoShapeSavingContext &context) const;
// from KoShape
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
//from KoShape
virtual void shapeChanged(ChangeType type, KoShape *shape = 0);
// from KoParameterShape
virtual void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
// from KoParameterShape
virtual void updatePath(const QSizeF &size);
private:
+ EnhancedPathShape(const EnhancedPathShape &rhs);
+
void evaluateHandles();
void reset();
/// parses the enhanced path data
void parsePathData(const QString &data);
/// Adds a new command
void addCommand(const QString &command, bool triggerUpdate);
/// Updates the size and position of an optionally existing text-on-shape text area
void updateTextArea();
/// Enables chaching results
void enableResultCache(bool enable);
// This function checks if draw:path-stretchpoint-x or draw:path-stretchpoint-y attributes are set.
// If the attributes are set the path shape coordinates (m_subpaths) are changed so that the form
// of the shape is preserved after stretching. It is needed for example in round-rectangles, to
// have the corners round after stretching. Without it the corners would be eliptical.
// Returns true if any points were actually changed, otherwise false.
bool useStretchPoints(const QSizeF &size, qreal &scale);
typedef QMap<QString, EnhancedPathFormula *> FormulaStore;
typedef QList<qreal> ModifierStore;
typedef QMap<QString, EnhancedPathParameter *> ParameterStore;
QRect m_viewBox; ///< the viewbox rectangle
QRectF m_viewBound; ///< the bounding box of the path in viewbox coordinates
QTransform m_viewMatrix; ///< matrix to convert from viewbox coordinates to shape coordinates
QTransform m_mirrorMatrix; ///< matrix to used for mirroring
QPointF m_viewBoxOffset;
QStringList m_textArea;
QList<EnhancedPathCommand *> m_commands; ///< the commands creating the outline
QList<EnhancedPathHandle *> m_enhancedHandles; ///< the handles for modifiying the shape
FormulaStore m_formulae; ///< the formulae
ModifierStore m_modifiers; ///< the modifier values
ParameterStore m_parameters; ///< the shared parameters
bool m_mirrorVertically; ///<whether or not the shape is to be mirrored vertically before transforming it
bool m_mirrorHorizontally; ///<whether or not the shape is to be mirrored horizontally before transforming it
qreal m_pathStretchPointX; ///< draw:path-stretchpoint-x attribute
qreal m_pathStretchPointY; ///< draw:path-stretchpoint-y attribute
QHash<QString, qreal> m_resultChache; ///< cache for intermediate results used when evaluating path
bool m_cacheResults; ///< indicates if result cache is enabled
};
#endif // KOENHANCEDPATHSHAPE_H
diff --git a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp
index 8fbd1c9298..a549de1ef4 100644
--- a/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp
+++ b/plugins/flake/pathshapes/enhancedpath/EnhancedPathShapeFactory.cpp
@@ -1,567 +1,568 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "enhancedpath/EnhancedPathShapeFactory.h"
#include "enhancedpath/EnhancedPathShape.h"
#include <KoShapeStroke.h>
#include <KoProperties.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoColorBackground.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
+#include "kis_pointer_utils.h"
#include <QString>
#include <math.h>
EnhancedPathShapeFactory::EnhancedPathShapeFactory()
: KoShapeFactoryBase(EnhancedPathShapeId, i18n("An enhanced path shape"))
{
setToolTip(i18n("An enhanced path"));
setIconName(koIconNameCStr("enhancedpath"));
setXmlElementNames(KoXmlNS::draw, QStringList("custom-shape"));
setLoadingPriority(1);
addCross();
addArrow();
addCallout();
addSmiley();
addCircularArrow();
addGearhead();
}
KoShape *EnhancedPathShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
EnhancedPathShape *shape = new EnhancedPathShape(QRect(0, 0, 100, 100));
- shape->setStroke(new KoShapeStroke(1.0));
+ shape->setStroke(toQShared(new KoShapeStroke(1.0)));
shape->setShapeId(KoPathShapeId);
shape->addModifiers("35");
shape->addFormula("Right", "width - $0");
shape->addFormula("Bottom", "height - $0");
shape->addFormula("Half", "min(0.5 * height, 0.5 * width)");
shape->addCommand("M $0 0");
shape->addCommand("L ?Right 0 ?Right $0 width $0 width ?Bottom ?Right ?Bottom");
shape->addCommand("L ?Right height $0 height $0 ?Bottom 0 ?Bottom 0 $0 $0 $0");
shape->addCommand("Z");
ComplexType handle;
handle["draw:handle-position"] = "$0 0";
handle["draw:handle-range-x-minimum"] = '0';
handle["draw:handle-range-x-maximum"] = "?Half";
shape->addHandle(handle);
shape->setSize(QSize(100, 100));
return shape;
}
KoShape *EnhancedPathShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *) const
{
QVariant viewboxData;
const QRect viewBox = (params->property(QLatin1String("viewBox"), viewboxData)) ?
viewboxData.toRect() :
QRect(0, 0, 100, 100);
EnhancedPathShape *shape = new EnhancedPathShape(viewBox);
shape->setShapeId(KoPathShapeId);
- shape->setStroke(new KoShapeStroke(1.0));
+ shape->setStroke(toQShared(new KoShapeStroke(1.0)));
shape->addModifiers(params->stringProperty("modifiers"));
ListType handles = params->property("handles").toList();
foreach (const QVariant &v, handles) {
shape->addHandle(v.toMap());
}
ComplexType formulae = params->property("formulae").toMap();
ComplexType::const_iterator formula = formulae.constBegin();
ComplexType::const_iterator lastFormula = formulae.constEnd();
for (; formula != lastFormula; ++formula) {
shape->addFormula(formula.key(), formula.value().toString());
}
QStringList commands = params->property("commands").toStringList();
foreach (const QString &cmd, commands) {
shape->addCommand(cmd);
}
QVariant color;
if (params->property("background", color)) {
shape->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(color.value<QColor>())));
}
QSizeF size = shape->size();
if (size.width() > size.height()) {
shape->setSize(QSizeF(100, 100 * size.height() / size.width()));
} else {
shape->setSize(QSizeF(100 * size.width() / size.height(), 100));
}
return shape;
}
KoProperties *EnhancedPathShapeFactory::dataToProperties(
const QString &modifiers, const QStringList &commands,
const ListType &handles, const ComplexType &formulae) const
{
KoProperties *props = new KoProperties();
props->setProperty("modifiers", modifiers);
props->setProperty("commands", commands);
props->setProperty("handles", handles);
props->setProperty("formulae", formulae);
props->setProperty("background", QVariant::fromValue<QColor>(QColor(Qt::red)));
return props;
}
void EnhancedPathShapeFactory::addCross()
{
QString modifiers("35");
QStringList commands;
commands.append("M $0 0");
commands.append("L ?Right 0 ?Right $0 width $0 width ?Bottom ?Right ?Bottom");
commands.append("L ?Right height $0 height $0 ?Bottom 0 ?Bottom 0 $0 $0 $0");
commands.append("Z");
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 0";
handle["draw:handle-range-x-minimum"] = '0';
handle["draw:handle-range-x-maximum"] = "?Half";
handles.append(QVariant(handle));
ComplexType formulae;
formulae["Right"] = "width - $0";
formulae["Bottom"] = "height - $0";
formulae["Half"] = "min(0.5 * height, 0.5 * width)";
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "cross";
t.name = i18n("Cross");
t.family = "funny";
t.toolTip = i18n("A cross");
t.iconName = koIconName("cross-shape");
t.properties = dataToProperties(modifiers, commands, handles, formulae);
addTemplate(t);
}
void EnhancedPathShapeFactory::addArrow()
{
{
// arrow right
QString modifiers("60 35");
QStringList commands;
commands.append("M $0 $1");
commands.append("L $0 0 width ?HalfHeight $0 height $0 ?LowerCorner 0 ?LowerCorner 0 $1");
commands.append("Z");
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 $1";
handle["draw:handle-range-x-minimum"] = '0';
handle["draw:handle-range-x-maximum"] = "width";
handle["draw:handle-range-y-minimum"] = '0';
handle["draw:handle-range-y-maximum"] = "?HalfHeight";
handles.append(QVariant(handle));
ComplexType formulae;
formulae["HalfHeight"] = "0.5 * height";
formulae["LowerCorner"] = "height - $1";
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "arrow_right";
t.name = i18n("Arrow");
t.family = "arrow";
t.toolTip = i18n("An arrow");
t.iconName = koIconName("draw-arrow-forward");
t.properties = dataToProperties(modifiers, commands, handles, formulae);
addTemplate(t);
}
{
// arrow left
QString modifiers("40 35");
QStringList commands;
commands.append("M $0 $1");
commands.append("L $0 0 0 ?HalfHeight $0 height $0 ?LowerCorner width ?LowerCorner width $1");
commands.append("Z");
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 $1";
handle["draw:handle-range-x-minimum"] = '0';
handle["draw:handle-range-x-maximum"] = "width";
handle["draw:handle-range-y-minimum"] = '0';
handle["draw:handle-range-y-maximum"] = "?HalfHeight";
handles.append(QVariant(handle));
ComplexType formulae;
formulae["HalfHeight"] = "0.5 * height";
formulae["LowerCorner"] = "height - $1";
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "arrow_left";
t.name = i18n("Arrow");
t.family = "arrow";
t.toolTip = i18n("An arrow");
t.iconName = koIconName("draw-arrow-back");
t.properties = dataToProperties(modifiers, commands, handles, formulae);
addTemplate(t);
}
{
// arrow top
QString modifiers("35 40");
QStringList commands;
commands.append("M $0 $1");
commands.append("L 0 $1 ?HalfWidth 0 width $1 ?RightCorner $1 ?RightCorner height $0 height");
commands.append("Z");
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 $1";
handle["draw:handle-range-x-minimum"] = '0';
handle["draw:handle-range-x-maximum"] = "?HalfWidth";
handle["draw:handle-range-y-minimum"] = '0';
handle["draw:handle-range-y-maximum"] = "height";
handles.append(QVariant(handle));
ComplexType formulae;
formulae["HalfWidth"] = "0.5 * width";
formulae["RightCorner"] = "width - $0";
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "arrow_top";
t.name = i18n("Arrow");
t.family = "arrow";
t.toolTip = i18n("An arrow");
t.iconName = koIconName("draw-arrow-up");
t.properties = dataToProperties(modifiers, commands, handles, formulae);
addTemplate(t);
}
{
// arrow bottom
QString modifiers("35 60");
QStringList commands;
commands.append("M $0 $1");
commands.append("L 0 $1 ?HalfWidth height width $1 ?RightCorner $1 ?RightCorner 0 $0 0");
commands.append("Z");
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 $1";
handle["draw:handle-range-x-minimum"] = '0';
handle["draw:handle-range-x-maximum"] = "?HalfWidth";
handle["draw:handle-range-y-minimum"] = '0';
handle["draw:handle-range-y-maximum"] = "height";
handles.append(QVariant(handle));
ComplexType formulae;
formulae["HalfWidth"] = "0.5 * width";
formulae["RightCorner"] = "width - $0";
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "arrow_bottom";
t.name = i18n("Arrow");
t.family = "arrow";
t.toolTip = i18n("An arrow");
t.iconName = koIconName("draw-arrow-down");
t.properties = dataToProperties(modifiers, commands, handles, formulae);
addTemplate(t);
}
}
void EnhancedPathShapeFactory::addCallout()
{
QString modifiers("4250 45000");
QStringList commands;
commands.append("M 3590 0");
commands.append("X 0 3590");
commands.append("L ?f2 ?f3 0 8970 0 12630 ?f4 ?f5 0 18010");
commands.append("Y 3590 21600");
commands.append("L ?f6 ?f7 8970 21600 12630 21600 ?f8 ?f9 18010 21600");
commands.append("X 21600 18010");
commands.append("L ?f10 ?f11 21600 12630 21600 8970 ?f12 ?f13 21600 3590");
commands.append("Y 18010 0");
commands.append("L ?f14 ?f15 12630 0 8970 0 ?f16 ?f17");
commands.append("Z");
commands.append("N");
ComplexType formulae;
formulae["f0"] = "$0 -10800";
formulae["f1"] = "$1 -10800";
formulae["f2"] = "if(?f18 ,$0 ,0)";
formulae["f3"] = "if(?f18 ,$1 ,6280)";
formulae["f4"] = "if(?f23 ,$0 ,0)";
formulae["f5"] = "if(?f23 ,$1 ,15320)";
formulae["f6"] = "if(?f26 ,$0 ,6280)";
formulae["f7"] = "if(?f26 ,$1 ,21600)";
formulae["f8"] = "if(?f29 ,$0 ,15320)";
formulae["f9"] = "if(?f29 ,$1 ,21600)";
formulae["f10"] = "if(?f32 ,$0 ,21600)";
formulae["f11"] = "if(?f32 ,$1 ,15320)";
formulae["f12"] = "if(?f34 ,$0 ,21600)";
formulae["f13"] = "if(?f34 ,$1 ,6280)";
formulae["f14"] = "if(?f36 ,$0 ,15320)";
formulae["f15"] = "if(?f36 ,$1 ,0)";
formulae["f16"] = "if(?f38 ,$0 ,6280)";
formulae["f17"] = "if(?f38 ,$1 ,0)";
formulae["f18"] = "if($0 ,-1,?f19)";
formulae["f19"] = "if(?f1 ,-1,?f22)";
formulae["f20"] = "abs(?f0)";
formulae["f21"] = "abs(?f1)";
formulae["f22"] = "?f20 -?f21";
formulae["f23"] = "if($0 ,-1,?f24)";
formulae["f24"] = "if(?f1 ,?f22 ,-1)";
formulae["f25"] = "$1 -21600";
formulae["f26"] = "if(?f25 ,?f27 ,-1)";
formulae["f27"] = "if(?f0 ,-1,?f28)";
formulae["f28"] = "?f21 -?f20";
formulae["f29"] = "if(?f25 ,?f30 ,-1)";
formulae["f30"] = "if(?f0 ,?f28 ,-1)";
formulae["f31"] = "$0 -21600";
formulae["f32"] = "if(?f31 ,?f33 ,-1)";
formulae["f33"] = "if(?f1 ,?f22 ,-1)";
formulae["f34"] = "if(?f31 ,?f35 ,-1)";
formulae["f35"] = "if(?f1 ,-1,?f22)";
formulae["f36"] = "if($1 ,-1,?f37)";
formulae["f37"] = "if(?f0 ,?f28 ,-1)";
formulae["f38"] = "if($1 ,-1,?f39)";
formulae["f39"] = "if(?f0 ,-1,?f28)";
formulae["f40"] = "$0";
formulae["f41"] = "$1";
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 $1";
handles.append(QVariant(handle));
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "callout";
t.name = i18n("Callout");
t.family = "funny";
t.toolTip = i18n("A callout");
t.iconName = koIconName("callout-shape");
KoProperties *properties = dataToProperties(modifiers, commands, handles, formulae);
properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 21600, 21600));
t.properties = properties;
addTemplate(t);
}
void EnhancedPathShapeFactory::addSmiley()
{
QString modifiers("17520");
QStringList commands;
commands.append("U 10800 10800 10800 10800 0 23592960");
commands.append("Z");
commands.append("N");
commands.append("U 7305 7515 1165 1165 0 23592960");
commands.append("Z");
commands.append("N");
commands.append("U 14295 7515 1165 1165 0 23592960");
commands.append("Z");
commands.append("N");
commands.append("M 4870 ?f1");
commands.append("C 8680 ?f2 12920 ?f2 16730 ?f1");
commands.append("Z");
commands.append("F");
commands.append("N");
ComplexType formulae;
formulae["f0"] = "$0 -15510";
formulae["f1"] = "17520-?f0";
formulae["f2"] = "15510+?f0";
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "10800 $0";
handle["draw:handle-range-y-minimum"] = "15510";
handle["draw:handle-range-y-maximum"] = "17520";
handles.append(QVariant(handle));
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "smiley";
t.name = i18n("Smiley");
t.family = "funny";
t.toolTip = i18n("Smiley");
t.iconName = koIconName("smiley-shape");
KoProperties *properties = dataToProperties(modifiers, commands, handles, formulae);
properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 21600, 21600));
t.properties = properties;
addTemplate(t);
}
void EnhancedPathShapeFactory::addCircularArrow()
{
QString modifiers("180 0 5500");
QStringList commands;
commands.append("B ?f3 ?f3 ?f20 ?f20 ?f19 ?f18 ?f17 ?f16");
commands.append("W 0 0 21600 21600 ?f9 ?f8 ?f11 ?f10");
commands.append("L ?f24 ?f23 ?f36 ?f35 ?f29 ?f28");
commands.append("Z");
commands.append("N");
ComplexType formulae;
formulae["f0"] = "$0";
formulae["f1"] = "$1";
formulae["f2"] = "$2";
formulae["f3"] = "10800+$2";
formulae["f4"] = "10800*sin($0 *(pi/180))";
formulae["f5"] = "10800*cos($0 *(pi/180))";
formulae["f6"] = "10800*sin($1 *(pi/180))";
formulae["f7"] = "10800*cos($1 *(pi/180))";
formulae["f8"] = "?f4 +10800";
formulae["f9"] = "?f5 +10800";
formulae["f10"] = "?f6 +10800";
formulae["f11"] = "?f7 +10800";
formulae["f12"] = "?f3 *sin($0 *(pi/180))";
formulae["f13"] = "?f3 *cos($0 *(pi/180))";
formulae["f14"] = "?f3 *sin($1 *(pi/180))";
formulae["f15"] = "?f3 *cos($1 *(pi/180))";
formulae["f16"] = "?f12 +10800";
formulae["f17"] = "?f13 +10800";
formulae["f18"] = "?f14 +10800";
formulae["f19"] = "?f15 +10800";
formulae["f20"] = "21600-?f3";
formulae["f21"] = "13500*sin($1 *(pi/180))";
formulae["f22"] = "13500*cos($1 *(pi/180))";
formulae["f23"] = "?f21 +10800";
formulae["f24"] = "?f22 +10800";
formulae["f25"] = "$2 -2700";
formulae["f26"] = "?f25 *sin($1 *(pi/180))";
formulae["f27"] = "?f25 *cos($1 *(pi/180))";
formulae["f28"] = "?f26 +10800";
formulae["f29"] = "?f27 +10800";
formulae["f30"] = "($1+45)*pi/180";
formulae["f31"] = "sqrt(((?f29-?f24)*(?f29-?f24))+((?f28-?f23)*(?f28-?f23)))";
formulae["f32"] = "sqrt(2)/2*?f31";
formulae["f33"] = "?f32*sin(?f30)";
formulae["f34"] = "?f32*cos(?f30)";
formulae["f35"] = "?f28+?f33";
formulae["f36"] = "?f29+?f34";
ListType handles;
ComplexType handle;
handle["draw:handle-position"] = "$0 10800";
handle["draw:handle-polar"] = "10800 10800";
handle["draw:handle-radius-range-minimum"] = "10800";
handle["draw:handle-radius-range-maximum"] = "10800";
handles.append(QVariant(handle));
handle.clear();
handle["draw:handle-position"] = "$1 $2";
handle["draw:handle-polar"] = "10800 10800";
handle["draw:handle-radius-range-minimum"] = '0';
handle["draw:handle-radius-range-maximum"] = "10800";
handles.append(QVariant(handle));
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "circulararrow";
t.name = i18n("Circular Arrow");
t.family = "arrow";
t.toolTip = i18n("A circular-arrow");
t.iconName = koIconName("circular-arrow-shape");
KoProperties *properties = dataToProperties(modifiers, commands, handles, formulae);
properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 21600, 21600));
t.properties = properties;
addTemplate(t);
}
void EnhancedPathShapeFactory::addGearhead()
{
QStringList commands;
commands.append("M 20 70");
commands.append("L 20 100 30 100 30 50 30 70 40 70 40 40 0 40 0 70 10 70 10 50 10 100 20 100");
commands.append("Z");
commands.append("N");
uint toothCount = 10;
qreal toothAngle = 360.0 / qreal(toothCount);
//kDebug() <<"toothAngle =" << toothAngle;
qreal outerRadius = 0.5 * 25.0;
qreal innerRadius = 0.5 * 17.0;
QPointF center(20, 25);
qreal radian = (270.0 - 0.35 * toothAngle) * M_PI / 180.0;
commands.append(QString("M %1 %2").arg(center.x() + innerRadius * cos(radian)).arg(center.y() + innerRadius * sin(radian)));
QString cmd("L");
for (uint i = 0; i < toothCount; ++i) {
radian += 0.15 * toothAngle * M_PI / 180.0;
cmd += QString(" %1 %2").arg(center.x() + outerRadius * cos(radian)).arg(center.y() + outerRadius * sin(radian));
radian += 0.35 * toothAngle * M_PI / 180.0;
cmd += QString(" %1 %2").arg(center.x() + outerRadius * cos(radian)).arg(center.y() + outerRadius * sin(radian));
radian += 0.15 * toothAngle * M_PI / 180.0;
cmd += QString(" %1 %2").arg(center.x() + innerRadius * cos(radian)).arg(center.y() + innerRadius * sin(radian));
radian += 0.35 * toothAngle * M_PI / 180.0;
cmd += QString(" %1 %2").arg(center.x() + innerRadius * cos(radian)).arg(center.y() + innerRadius * sin(radian));
}
//kDebug() <<"gear command =" << cmd;
commands.append(cmd);
commands.append("Z");
commands.append("N");
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "gearhead";
t.name = i18n("Gearhead");
t.family = "funny";
t.toolTip = i18n("A gearhead");
t.iconName = koIconName("gearhead-shape");
KoProperties *properties = dataToProperties(QString(), commands, ListType(), ComplexType());
properties->setProperty("background", QVariant::fromValue<QColor>(QColor(Qt::blue)));
properties->setProperty(QLatin1String("viewBox"), QRect(0, 0, 40, 90));
t.properties = properties;
addTemplate(t);
}
bool EnhancedPathShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
return (e.localName() == "custom-shape" && e.namespaceURI() == KoXmlNS::draw);
}
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp
index 61638a18f4..6f264b3448 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShape.cpp
+++ b/plugins/flake/pathshapes/rectangle/RectangleShape.cpp
@@ -1,365 +1,382 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2008 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 "RectangleShape.h"
+#include <KoParameterShape_p.h>
#include <KoPathPoint.h>
#include <KoShapeSavingContext.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoUnit.h>
#include <SvgSavingContext.h>
#include <SvgLoadingContext.h>
#include <SvgUtil.h>
#include <SvgStyleWriter.h>
RectangleShape::RectangleShape()
: m_cornerRadiusX(0)
, m_cornerRadiusY(0)
{
QList<QPointF> handles;
handles.push_back(QPointF(100, 0));
handles.push_back(QPointF(100, 0));
setHandles(handles);
QSizeF size(100, 100);
updatePath(size);
}
+RectangleShape::RectangleShape(const RectangleShape &rhs)
+ : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)),
+ m_cornerRadiusX(rhs.m_cornerRadiusX),
+ m_cornerRadiusY(rhs.m_cornerRadiusY)
+{
+}
+
RectangleShape::~RectangleShape()
{
}
+KoShape *RectangleShape::cloneShape() const
+{
+ return new RectangleShape(*this);
+}
+
bool RectangleShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
loadOdfAttributes(element, context, OdfMandatories | OdfGeometry | OdfAdditionalAttributes | OdfCommonChildElements);
if (element.hasAttributeNS(KoXmlNS::svg, "rx") && element.hasAttributeNS(KoXmlNS::svg, "ry")) {
qreal rx = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "rx", "0"));
qreal ry = KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "ry", "0"));
m_cornerRadiusX = rx / (0.5 * size().width()) * 100;
m_cornerRadiusY = ry / (0.5 * size().height()) * 100;
} else {
QString cornerRadius = element.attributeNS(KoXmlNS::draw, "corner-radius", "");
if (!cornerRadius.isEmpty()) {
qreal radius = KoUnit::parseValue(cornerRadius);
m_cornerRadiusX = qMin<qreal>(radius / (0.5 * size().width()) * 100, qreal(100));
m_cornerRadiusY = qMin<qreal>(radius / (0.5 * size().height()) * 100, qreal(100));
}
}
updatePath(size());
updateHandles();
loadOdfAttributes(element, context, OdfTransformation);
loadText(element, context);
return true;
}
void RectangleShape::saveOdf(KoShapeSavingContext &context) const
{
if (isParametricShape()) {
context.xmlWriter().startElement("draw:rect");
saveOdfAttributes(context, OdfAllAttributes);
if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) {
context.xmlWriter().addAttributePt("svg:rx", m_cornerRadiusX * (0.5 * size().width()) / 100.0);
context.xmlWriter().addAttributePt("svg:ry", m_cornerRadiusY * (0.5 * size().height()) / 100.0);
}
saveOdfCommonChildElements(context);
saveText(context);
context.xmlWriter().endElement();
} else {
KoPathShape::saveOdf(context);
}
}
void RectangleShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
QPointF p(point);
qreal width2 = size().width() / 2.0;
qreal height2 = size().height() / 2.0;
switch (handleId) {
case 0:
if (p.x() < width2) {
p.setX(width2);
} else if (p.x() > size().width()) {
p.setX(size().width());
}
p.setY(0);
m_cornerRadiusX = (size().width() - p.x()) / width2 * 100.0;
if (!(modifiers & Qt::ControlModifier)) {
m_cornerRadiusY = (size().width() - p.x()) / height2 * 100.0;
}
break;
case 1:
if (p.y() < 0) {
p.setY(0);
} else if (p.y() > height2) {
p.setY(height2);
}
p.setX(size().width());
m_cornerRadiusY = p.y() / height2 * 100.0;
if (!(modifiers & Qt::ControlModifier)) {
m_cornerRadiusX = p.y() / width2 * 100.0;
}
break;
}
// this is needed otherwise undo/redo might not end in the same result
if (100 - m_cornerRadiusX < 1e-10) {
m_cornerRadiusX = 100;
}
if (100 - m_cornerRadiusY < 1e-10) {
m_cornerRadiusY = 100;
}
updateHandles();
}
void RectangleShape::updateHandles()
{
QList<QPointF> handles;
handles.append(QPointF(size().width() - m_cornerRadiusX / 100.0 * 0.5 * size().width(), 0.0));
handles.append(QPointF(size().width(), m_cornerRadiusY / 100.0 * 0.5 * size().height()));
setHandles(handles);
}
void RectangleShape::updatePath(const QSizeF &size)
{
+ Q_D(KoParameterShape);
+
qreal rx = 0;
qreal ry = 0;
if (m_cornerRadiusX > 0 && m_cornerRadiusY > 0) {
rx = size.width() / 200.0 * m_cornerRadiusX;
ry = size.height() / 200.0 * m_cornerRadiusY;
}
qreal x2 = size.width() - rx;
qreal y2 = size.height() - ry;
QPointF curvePoints[12];
int requiredCurvePointCount = 4;
if (rx && m_cornerRadiusX < 100) {
requiredCurvePointCount += 2;
}
if (ry && m_cornerRadiusY < 100) {
requiredCurvePointCount += 2;
}
createPoints(requiredCurvePointCount);
- KoSubpath &points = *m_subpaths[0];
+ KoSubpath &points = *d->subpaths[0];
int cp = 0;
// first path starts and closes path
points[cp]->setProperty(KoPathPoint::StartSubpath);
points[cp]->setProperty(KoPathPoint::CloseSubpath);
points[cp]->setPoint(QPointF(rx, 0));
points[cp]->removeControlPoint1();
points[cp]->removeControlPoint2();
if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) {
// end point of the top edge
points[++cp]->setPoint(QPointF(x2, 0));
points[cp]->removeControlPoint1();
points[cp]->removeControlPoint2();
}
if (rx) {
// the top right radius
arcToCurve(rx, ry, 90, -90, points[cp]->point(), curvePoints);
points[cp]->setControlPoint2(curvePoints[0]);
points[++cp]->setControlPoint1(curvePoints[1]);
points[cp]->setPoint(curvePoints[2]);
points[cp]->removeControlPoint2();
}
if (m_cornerRadiusY < 100 || m_cornerRadiusX == 0) {
// the right edge
points[++cp]->setPoint(QPointF(size.width(), y2));
points[cp]->removeControlPoint1();
points[cp]->removeControlPoint2();
}
if (rx) {
// the bottom right radius
arcToCurve(rx, ry, 0, -90, points[cp]->point(), curvePoints);
points[cp]->setControlPoint2(curvePoints[0]);
points[++cp]->setControlPoint1(curvePoints[1]);
points[cp]->setPoint(curvePoints[2]);
points[cp]->removeControlPoint2();
}
if (m_cornerRadiusX < 100 || m_cornerRadiusY == 0) {
// the bottom edge
points[++cp]->setPoint(QPointF(rx, size.height()));
points[cp]->removeControlPoint1();
points[cp]->removeControlPoint2();
}
if (rx) {
// the bottom left radius
arcToCurve(rx, ry, 270, -90, points[cp]->point(), curvePoints);
points[cp]->setControlPoint2(curvePoints[0]);
points[++cp]->setControlPoint1(curvePoints[1]);
points[cp]->setPoint(curvePoints[2]);
points[cp]->removeControlPoint2();
}
if ((m_cornerRadiusY < 100 || m_cornerRadiusX == 0) && ry) {
// the right edge
points[++cp]->setPoint(QPointF(0, ry));
points[cp]->removeControlPoint1();
points[cp]->removeControlPoint2();
}
if (rx) {
// the top left radius
arcToCurve(rx, ry, 180, -90, points[cp]->point(), curvePoints);
points[cp]->setControlPoint2(curvePoints[0]);
points[0]->setControlPoint1(curvePoints[1]);
points[0]->setPoint(curvePoints[2]);
}
// unset all stop/close path properties
for (int i = 1; i < cp; ++i) {
points[i]->unsetProperty(KoPathPoint::StopSubpath);
points[i]->unsetProperty(KoPathPoint::CloseSubpath);
}
// last point stops and closes path
points.last()->setProperty(KoPathPoint::StopSubpath);
points.last()->setProperty(KoPathPoint::CloseSubpath);
}
void RectangleShape::createPoints(int requiredPointCount)
{
- if (m_subpaths.count() != 1) {
+ Q_D(KoParameterShape);
+
+ if (d->subpaths.count() != 1) {
clear();
- m_subpaths.append(new KoSubpath());
+ d->subpaths.append(new KoSubpath());
}
- int currentPointCount = m_subpaths[0]->count();
+ int currentPointCount = d->subpaths[0]->count();
if (currentPointCount > requiredPointCount) {
for (int i = 0; i < currentPointCount - requiredPointCount; ++i) {
- delete m_subpaths[0]->front();
- m_subpaths[0]->pop_front();
+ delete d->subpaths[0]->front();
+ d->subpaths[0]->pop_front();
}
} else if (requiredPointCount > currentPointCount) {
for (int i = 0; i < requiredPointCount - currentPointCount; ++i) {
- m_subpaths[0]->append(new KoPathPoint(this, QPointF()));
+ d->subpaths[0]->append(new KoPathPoint(this, QPointF()));
}
}
}
qreal RectangleShape::cornerRadiusX() const
{
return m_cornerRadiusX;
}
void RectangleShape::setCornerRadiusX(qreal radius)
{
if (radius >= 0.0 && radius <= 100.0) {
m_cornerRadiusX = radius;
updatePath(size());
updateHandles();
}
}
qreal RectangleShape::cornerRadiusY() const
{
return m_cornerRadiusY;
}
void RectangleShape::setCornerRadiusY(qreal radius)
{
if (radius >= 0.0 && radius <= 100.0) {
m_cornerRadiusY = radius;
updatePath(size());
updateHandles();
}
}
QString RectangleShape::pathShapeId() const
{
return RectangleShapeId;
}
bool RectangleShape::saveSvg(SvgSavingContext &context)
{
context.shapeWriter().startElement("rect");
context.shapeWriter().addAttribute("id", context.getID(this));
context.shapeWriter().addAttribute("transform", SvgUtil::transformToString(transformation()));
SvgStyleWriter::saveSvgStyle(this, context);
const QSizeF size = this->size();
context.shapeWriter().addAttributePt("width", size.width());
context.shapeWriter().addAttributePt("height", size.height());
double rx = cornerRadiusX();
if (rx > 0.0) {
context.shapeWriter().addAttributePt("rx", 0.01 * rx * 0.5 * size.width());
}
double ry = cornerRadiusY();
if (ry > 0.0) {
context.shapeWriter().addAttributePt("ry", 0.01 * ry * 0.5 * size.height());
}
context.shapeWriter().endElement();
return true;
}
bool RectangleShape::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"));
const QString rxStr = element.attribute("rx");
const QString ryStr = element.attribute("ry");
qreal rx = rxStr.isEmpty() ? 0.0 : SvgUtil::parseUnitX(context.currentGC(), rxStr);
qreal ry = ryStr.isEmpty() ? 0.0 : SvgUtil::parseUnitY(context.currentGC(), ryStr);
// if one radius is given but not the other, use the same value for both
if (!rxStr.isEmpty() && ryStr.isEmpty()) {
ry = rx;
}
if (rxStr.isEmpty() && !ryStr.isEmpty()) {
rx = ry;
}
setSize(QSizeF(w, h));
setPosition(QPointF(x, y));
if (rx >= 0.0) {
setCornerRadiusX(qMin(qreal(100.0), qreal(rx / (0.5 * w) * 100.0)));
}
if (ry >= 0.0) {
setCornerRadiusY(qMin(qreal(100.0), qreal(ry / (0.5 * h) * 100.0)));
}
if (w == 0.0 || h == 0.0) {
setVisible(false);
}
return true;
}
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShape.h b/plugins/flake/pathshapes/rectangle/RectangleShape.h
index 608a1d8b4a..36ff22081d 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShape.h
+++ b/plugins/flake/pathshapes/rectangle/RectangleShape.h
@@ -1,94 +1,97 @@
/* This file is part of the KDE project
Copyright (C) 2006-2007 Thorsten Zachmann <zachmann@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 KORECTANGLESHAPE_H
#define KORECTANGLESHAPE_H
#include "KoParameterShape.h"
#include <SvgShape.h>
#define RectangleShapeId "RectangleShape"
/**
* The RectangleShape is a shape that represents a rectangle.
* The rectangle can have rounded corners, with different corner
* radii in x- and y-direction.
*/
class RectangleShape : public KoParameterShape, public SvgShape
{
public:
RectangleShape();
~RectangleShape();
+ KoShape* cloneShape() const override;
+
/// Returns the corner radius in x-direction
qreal cornerRadiusX() const;
/**
* Sets the corner radius in x-direction.
*
* The corner x-radius is a percent value (a number between 0 and 100)
* of the half size of the rectangles width.
*
* @param radius the new corner radius in x-direction
*/
void setCornerRadiusX(qreal radius);
/// Returns the corner radius in y-direction
qreal cornerRadiusY() const;
/**
* Sets the corner radius in y-direction.
*
* The corner y-radius is a percent value (a number between 0 and 100)
* of the half size of the rectangles height.
*
* @param radius the new corner radius in y-direction
*/
void setCornerRadiusY(qreal radius);
/// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
/// reimplemented
virtual void saveOdf(KoShapeSavingContext &context) const;
/// reimplemented
virtual QString pathShapeId() const;
/// reimplemented from SvgShape
virtual bool saveSvg(SvgSavingContext &context);
/// reimplemented from SvgShape
virtual bool loadSvg(const KoXmlElement &element, SvgLoadingContext &context);
protected:
+ RectangleShape(const RectangleShape &rhs);
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void updatePath(const QSizeF &size);
void createPoints(int requiredPointCount);
void updateHandles();
private:
qreal m_cornerRadiusX; ///< in percent of half of the rectangle width (a number between 0 and 100)
qreal m_cornerRadiusY; ///< in percent of half of the rectangle height (a number between 0 and 100)
};
#endif /* KORECTANGLESHAPE_H */
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.cpp b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.cpp
index 81782570c8..6d99ad533a 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.cpp
+++ b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.cpp
@@ -1,68 +1,89 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "RectangleShapeConfigCommand.h"
#include "RectangleShape.h"
#include <klocalizedstring.h>
+#include "kis_command_ids.h"
+
RectangleShapeConfigCommand::RectangleShapeConfigCommand(RectangleShape *rectangle, qreal cornerRadiusX, qreal cornerRadiusY, KUndo2Command *parent)
: KUndo2Command(parent)
, m_rectangle(rectangle)
, m_newCornerRadiusX(cornerRadiusX)
, m_newCornerRadiusY(cornerRadiusY)
{
Q_ASSERT(m_rectangle);
setText(kundo2_i18n("Change rectangle"));
m_oldCornerRadiusX = m_rectangle->cornerRadiusX();
m_oldCornerRadiusY = m_rectangle->cornerRadiusY();
}
void RectangleShapeConfigCommand::redo()
{
KUndo2Command::redo();
m_rectangle->update();
if (m_oldCornerRadiusX != m_newCornerRadiusX) {
m_rectangle->setCornerRadiusX(m_newCornerRadiusX);
}
if (m_oldCornerRadiusY != m_newCornerRadiusY) {
m_rectangle->setCornerRadiusY(m_newCornerRadiusY);
}
m_rectangle->update();
}
void RectangleShapeConfigCommand::undo()
{
KUndo2Command::undo();
m_rectangle->update();
if (m_oldCornerRadiusX != m_newCornerRadiusX) {
m_rectangle->setCornerRadiusX(m_oldCornerRadiusX);
}
if (m_oldCornerRadiusY != m_newCornerRadiusY) {
m_rectangle->setCornerRadiusY(m_oldCornerRadiusY);
}
m_rectangle->update();
}
+
+int RectangleShapeConfigCommand::id() const
+{
+ return KisCommandUtils::ChangeRectangleShapeId;
+}
+
+bool RectangleShapeConfigCommand::mergeWith(const KUndo2Command *command)
+{
+ const RectangleShapeConfigCommand *other = dynamic_cast<const RectangleShapeConfigCommand*>(command);
+
+ if (!other || other->m_rectangle != m_rectangle) {
+ return false;
+ }
+
+ m_newCornerRadiusX = other->m_newCornerRadiusX;
+ m_newCornerRadiusY = other->m_newCornerRadiusY;
+
+ return true;
+}
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.h b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.h
index beda4b0ab2..c9f860ae7e 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.h
+++ b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigCommand.h
@@ -1,52 +1,56 @@
/* 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 RECTANGLESHAPECONFIGCOMMAND_H
#define RECTANGLESHAPECONFIGCOMMAND_H
#include <kundo2command.h>
class RectangleShape;
/// The undo / redo command for configuring a rectangle shape
class RectangleShapeConfigCommand : public KUndo2Command
{
public:
/**
* Configures a rectangle shape
* @param Rectangle the rectangle shape to configure
* @param cornerRadiusX the x corner radius
* @param cornerRadiusY the y corner radius
* @param parent the optional parent command
*/
RectangleShapeConfigCommand(RectangleShape *rectangle, qreal cornerRadiusX, qreal cornerRadiusY, KUndo2Command *parent = 0);
/// redo the command
- virtual void redo();
+ void redo() override;
/// revert the actions done in redo
- virtual void undo();
+ void undo() override;
+
+ int id() const override;
+ bool mergeWith(const KUndo2Command *command) override;
+
private:
RectangleShape *m_rectangle;
qreal m_oldCornerRadiusX;
qreal m_oldCornerRadiusY;
qreal m_newCornerRadiusX;
qreal m_newCornerRadiusY;
};
#endif // RECTANGLESHAPECONFIGCOMMAND_H
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp
index 927cd9421d..5f633cbd2f 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp
+++ b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp
@@ -1,82 +1,101 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "RectangleShapeConfigWidget.h"
#include "RectangleShape.h"
#include "RectangleShapeConfigCommand.h"
+#include "kis_signals_blocker.h"
+#include "kis_assert.h"
+
+#include "kis_document_aware_spin_box_unit_manager.h"
RectangleShapeConfigWidget::RectangleShapeConfigWidget()
+ : m_rectangle(0)
{
widget.setupUi(this);
- connect(widget.cornerRadiusX, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged()));
- connect(widget.cornerRadiusY, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged()));
+ connect(widget.cornerRadiusX, SIGNAL(valueChangedPt(qreal)), this, SIGNAL(propertyChanged()));
+ connect(widget.cornerRadiusY, SIGNAL(valueChangedPt(qreal)), this, SIGNAL(propertyChanged()));
}
void RectangleShapeConfigWidget::setUnit(const KoUnit &unit)
{
widget.cornerRadiusX->setUnit(unit);
widget.cornerRadiusY->setUnit(unit);
}
void RectangleShapeConfigWidget::open(KoShape *shape)
{
- m_rectangle = dynamic_cast<RectangleShape *>(shape);
- if (!m_rectangle) {
- return;
+ if (m_rectangle) {
+ m_rectangle->removeShapeChangeListener(this);
}
- widget.cornerRadiusX->blockSignals(true);
- widget.cornerRadiusY->blockSignals(true);
+ m_rectangle = dynamic_cast<RectangleShape *>(shape);
+ if (!m_rectangle) return;
- QSizeF size = m_rectangle->size();
+ loadPropertiesFromShape(m_rectangle);
+
+ m_rectangle->addShapeChangeListener(this);
+}
+
+void RectangleShapeConfigWidget::loadPropertiesFromShape(RectangleShape *shape)
+{
+ KisSignalsBlocker b(widget.cornerRadiusX, widget.cornerRadiusY);
+
+ QSizeF size = shape->size();
widget.cornerRadiusX->setMaximum(0.5 * size.width());
- widget.cornerRadiusX->changeValue(0.01 * m_rectangle->cornerRadiusX() * 0.5 * size.width());
+ widget.cornerRadiusX->changeValue(0.01 * shape->cornerRadiusX() * 0.5 * size.width());
widget.cornerRadiusY->setMaximum(0.5 * size.height());
- widget.cornerRadiusY->changeValue(0.01 * m_rectangle->cornerRadiusY() * 0.5 * size.height());
-
- widget.cornerRadiusX->blockSignals(false);
- widget.cornerRadiusY->blockSignals(false);
+ widget.cornerRadiusY->changeValue(0.01 * shape->cornerRadiusY() * 0.5 * size.height());
}
void RectangleShapeConfigWidget::save()
{
if (!m_rectangle) {
return;
}
QSizeF size = m_rectangle->size();
m_rectangle->setCornerRadiusX(100.0 * widget.cornerRadiusX->value() / (0.5 * size.width()));
m_rectangle->setCornerRadiusY(100.0 * widget.cornerRadiusY->value() / (0.5 * size.height()));
}
KUndo2Command *RectangleShapeConfigWidget::createCommand()
{
if (!m_rectangle) {
return 0;
}
QSizeF size = m_rectangle->size();
qreal cornerRadiusX = 100.0 * widget.cornerRadiusX->value() / (0.5 * size.width());
qreal cornerRadiusY = 100.0 * widget.cornerRadiusY->value() / (0.5 * size.height());
return new RectangleShapeConfigCommand(m_rectangle, cornerRadiusX, cornerRadiusY);
}
+
+void RectangleShapeConfigWidget::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(m_rectangle && shape == m_rectangle);
+
+ if (type == KoShape::ParameterChanged) {
+ loadPropertiesFromShape(m_rectangle);
+ }
+}
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.h b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.h
index 4cf52c310d..237359ddf8 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.h
+++ b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.h
@@ -1,53 +1,59 @@
/* 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 RECTANGLESHAPECONFIGWIDGET_H
#define RECTANGLESHAPECONFIGWIDGET_H
#include <ui_RectangleShapeConfigWidget.h>
#include <KoShapeConfigWidgetBase.h>
+#include <KoShape.h>
class RectangleShape;
-class RectangleShapeConfigWidget : public KoShapeConfigWidgetBase
+class RectangleShapeConfigWidget : public KoShapeConfigWidgetBase, public KoShape::ShapeChangeListener
{
Q_OBJECT
public:
RectangleShapeConfigWidget();
/// reimplemented
- virtual void open(KoShape *shape);
+ void open(KoShape *shape) override;
/// reimplemented
- virtual void save();
+ void save() override;
/// reimplemented
- virtual void setUnit(const KoUnit &unit);
+ void setUnit(const KoUnit &unit) override;
/// reimplemented
- virtual bool showOnShapeCreate()
+ bool showOnShapeCreate() override
{
return false;
}
/// reimplemented
- virtual KUndo2Command *createCommand();
+ KUndo2Command *createCommand() override;
+
+ void notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) override;
+
+private:
+ void loadPropertiesFromShape(RectangleShape *shape);
private:
Ui::RectangleShapeConfigWidget widget;
RectangleShape *m_rectangle;
};
#endif // RECTANGLESHAPECONFIGWIDGET_H
diff --git a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.ui b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.ui
index 88af5754e4..67e6a92ce0 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.ui
+++ b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.ui
@@ -1,61 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RectangleShapeConfigWidget</class>
<widget class="QWidget" name="RectangleShapeConfigWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>108</height>
</rect>
</property>
<property name="windowTitle">
<string>Rectangle Shape</string>
</property>
<layout class="QGridLayout">
+ <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>
+ <property name="horizontalSpacing">
+ <number>6</number>
+ </property>
+ <property name="verticalSpacing">
+ <number>3</number>
+ </property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Corner radius x:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="KisDoubleParseUnitSpinBox" name="cornerRadiusX"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Corner radius y:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="KisDoubleParseUnitSpinBox" name="cornerRadiusY"/>
</item>
<item row="2" column="0">
- <spacer>
+ <spacer name="spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
- <height>40</height>
+ <height>6</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/flake/pathshapes/rectangle/RectangleShapeFactory.cpp b/plugins/flake/pathshapes/rectangle/RectangleShapeFactory.cpp
index d7174771be..7a5538670a 100644
--- a/plugins/flake/pathshapes/rectangle/RectangleShapeFactory.cpp
+++ b/plugins/flake/pathshapes/rectangle/RectangleShapeFactory.cpp
@@ -1,75 +1,77 @@
/* 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.
*/
#include "RectangleShapeFactory.h"
#include "RectangleShape.h"
#include "RectangleShapeConfigWidget.h"
#include "KoShapeStroke.h"
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoGradientBackground.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
+#include "kis_pointer_utils.h"
+
RectangleShapeFactory::RectangleShapeFactory()
: KoShapeFactoryBase(RectangleShapeId, i18n("Rectangle"))
{
setToolTip(i18n("A rectangle"));
setIconName(koIconNameCStr("rectangle-shape"));
setFamily("geometric");
setLoadingPriority(1);
QList<QPair<QString, QStringList> > elementNamesList;
elementNamesList.append(qMakePair(QString(KoXmlNS::draw), QStringList("rect")));
elementNamesList.append(qMakePair(QString(KoXmlNS::svg), QStringList("rect")));
setXmlElements(elementNamesList);
}
KoShape *RectangleShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
RectangleShape *rect = new RectangleShape();
- rect->setStroke(new KoShapeStroke(1.0));
+ rect->setStroke(toQShared(new KoShapeStroke(1.0)));
rect->setShapeId(KoPathShapeId);
QLinearGradient *gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1));
gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
gradient->setColorAt(0.0, Qt::white);
gradient->setColorAt(1.0, Qt::green);
rect->setBackground(QSharedPointer<KoGradientBackground>(new KoGradientBackground(gradient)));
return rect;
}
bool RectangleShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &/*context*/) const
{
Q_UNUSED(e);
return (e.localName() == "rect" && e.namespaceURI() == KoXmlNS::draw);
}
QList<KoShapeConfigWidgetBase *> RectangleShapeFactory::createShapeOptionPanels()
{
QList<KoShapeConfigWidgetBase *> panels;
panels.append(new RectangleShapeConfigWidget());
return panels;
}
diff --git a/plugins/flake/pathshapes/spiral/SpiralShape.cpp b/plugins/flake/pathshapes/spiral/SpiralShape.cpp
index 3f0859daea..361b7bf168 100644
--- a/plugins/flake/pathshapes/spiral/SpiralShape.cpp
+++ b/plugins/flake/pathshapes/spiral/SpiralShape.cpp
@@ -1,290 +1,317 @@
/* This file is part of the KDE project
Copyright (C) 2007 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 "SpiralShape.h"
+#include <KoParameterShape_p.h>
#include <KoPathPoint.h>
#include <KoShapeSavingContext.h>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <math.h>
+#include "kis_assert.h"
+
SpiralShape::SpiralShape()
: m_fade(.9)
, m_kindAngle(M_PI)
, m_radii(100.0, 100.0)
, m_type(Curve)
, m_clockwise(true)
{
//m_handles.push_back(QPointF(50, 0));
//m_handles.push_back(QPointF(50, 50));
//m_handles.push_back(QPointF(0, 50));
createPath(QSizeF(m_radii.x(), m_radii.y()));
}
+SpiralShape::SpiralShape(const SpiralShape &rhs)
+ : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)),
+ m_fade(rhs.m_fade),
+ m_kindAngle(rhs.m_kindAngle),
+ m_center(rhs.m_center),
+ m_radii(rhs.m_radii),
+ m_type(rhs.m_type),
+ m_clockwise(rhs.m_clockwise)
+
+{
+ Q_FOREACH(KoPathPoint *point, rhs.m_points) {
+ KIS_ASSERT_RECOVER(point) { continue; }
+ m_points << new KoPathPoint(*point, this);
+ }
+}
+
+
SpiralShape::~SpiralShape()
{
}
+KoShape *SpiralShape::cloneShape() const
+{
+ return new SpiralShape(*this);
+}
+
void SpiralShape::saveOdf(KoShapeSavingContext &context) const
{
// TODO?
KoPathShape::saveOdf(context);
}
bool SpiralShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &/*context*/)
{
Q_UNUSED(element);
// TODO?
return true;
}
void SpiralShape::setSize(const QSizeF &newSize)
{
QTransform matrix(resizeMatrix(newSize));
m_center = matrix.map(m_center);
m_radii = matrix.map(m_radii);
KoParameterShape::setSize(newSize);
}
QPointF SpiralShape::normalize()
{
QPointF offset(KoParameterShape::normalize());
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
m_center = matrix.map(m_center);
return offset;
}
void SpiralShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(handleId);
Q_UNUSED(point);
Q_UNUSED(modifiers);
#if 0
QPointF p(point);
QPointF diff(m_center - point);
diff.setX(-diff.x());
qreal angle = 0;
if (diff.x() == 0) {
angle = (diff.y() < 0 ? 270 : 90) * M_PI / 180.0;
} else {
diff.setY(diff.y() * m_radii.x() / m_radii.y());
angle = atan(diff.y() / diff.x());
if (angle < 0) {
angle = M_PI + angle;
}
if (diff.y() < 0) {
angle += M_PI;
}
}
switch (handleId) {
case 0:
p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y()));
m_handles[handleId] = p;
updateKindHandle();
break;
case 1:
p = QPointF(m_center + QPointF(cos(angle) * m_radii.x(), -sin(angle) * m_radii.y()));
m_handles[handleId] = p;
updateKindHandle();
break;
case 2: {
QList<QPointF> kindHandlePositions;
kindHandlePositions.push_back(QPointF(m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y())));
kindHandlePositions.push_back(m_center);
kindHandlePositions.push_back((m_handles[0] + m_handles[1]) / 2.0);
QPointF diff = m_center * 2.0;
int handlePos = 0;
for (int i = 0; i < kindHandlePositions.size(); ++i) {
QPointF pointDiff(p - kindHandlePositions[i]);
if (i == 0 || qAbs(pointDiff.x()) + qAbs(pointDiff.y()) < qAbs(diff.x()) + qAbs(diff.y())) {
diff = pointDiff;
handlePos = i;
}
}
m_handles[handleId] = kindHandlePositions[handlePos];
m_type = SpiralType(handlePos);
} break;
}
#endif
}
void SpiralShape::updatePath(const QSizeF &size)
{
createPath(size);
normalize();
#if 0
Q_UNUSED(size);
QPointF startpoint(m_handles[0]);
QPointF curvePoints[12];
int pointCnt = arcToCurve(m_radii.x(), m_radii.y(), m_startAngle, sweepAngle(), startpoint, curvePoints);
int cp = 0;
m_points[cp]->setPoint(startpoint);
m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint1);
for (int i = 0; i < pointCnt; i += 3) {
m_points[cp]->setControlPoint2(curvePoints[i]);
m_points[++cp]->setControlPoint1(curvePoints[i + 1]);
m_points[cp]->setPoint(curvePoints[i + 2]);
m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint2);
}
if (m_type == Curve) {
m_points[++cp]->setPoint(m_center);
m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint1);
m_points[cp]->unsetProperty(KoPathPoint::HasControlPoint2);
} else if (m_type == Line && m_startAngle == m_endAngle) {
m_points[0]->setControlPoint1(m_points[cp]->controlPoint1());
m_points[0]->setPoint(m_points[cp]->point());
--cp;
}
- m_subpaths[0]->clear();
+ d->m_subpaths[0]->clear();
for (int i = 0; i <= cp; ++i) {
if (i < cp || (m_type == Line && m_startAngle != m_endAngle)) {
m_points[i]->unsetProperty(KoPathPoint::CloseSubpath);
} else {
m_points[i]->setProperty(KoPathPoint::CloseSubpath);
}
- m_subpaths[0]->push_back(m_points[i]);
+ d->m_subpaths[0]->push_back(m_points[i]);
}
#endif
}
void SpiralShape::createPath(const QSizeF &size)
{
+ Q_D(KoParameterShape);
+
Q_UNUSED(size);
clear();
QPointF center = QPointF(m_radii.x() / 2.0, m_radii.y() / 2.0);
//moveTo(QPointF(size.width(), m_radii.y()));
qreal adv_ang = (m_clockwise ? -1.0 : 1.0) * M_PI_2;
// radius of first segment is non-faded radius:
qreal m_radius = m_radii.x() / 2.0;
qreal r = m_radius;
QPointF oldP(center.x(), (m_clockwise ? -1.0 : 1.0) * m_radius + center.y());
QPointF newP;
QPointF newCenter(center);
moveTo(oldP);
uint m_segments = 10;
//m_handles[0] = oldP;
for (uint i = 0; i < m_segments; ++i) {
newP.setX(r * cos(adv_ang * (i + 2)) + newCenter.x());
newP.setY(r * sin(adv_ang * (i + 2)) + newCenter.y());
if (m_type == Curve) {
qreal rx = qAbs(oldP.x() - newP.x());
qreal ry = qAbs(oldP.y() - newP.y());
if (m_clockwise) {
arcTo(rx, ry, ((i + 1) % 4) * 90, 90);
} else {
arcTo(rx, ry, 360 - ((i + 1) % 4) * 90, -90);
}
} else {
lineTo(newP);
}
newCenter += (newP - newCenter) * (1.0 - m_fade);
oldP = newP;
r *= m_fade;
}
//m_handles[1] = QPointF(center.x(), (m_clockwise ? -1.0 : 1.0) * m_radius + center.y());
- m_points = *m_subpaths[0];
+ m_points = *d->subpaths[0];
}
void SpiralShape::updateKindHandle()
{
/*
m_kindAngle = (m_startAngle + m_endAngle) * M_PI / 360.0;
if (m_startAngle > m_endAngle)
{
m_kindAngle += M_PI;
}
switch (m_type)
{
case Curve:
m_handles[2] = m_center + QPointF(cos(m_kindAngle) * m_radii.x(), -sin(m_kindAngle) * m_radii.y());
break;
case Line:
m_handles[2] = m_center;
break;
}
*/
}
void SpiralShape::updateAngleHandles()
{
// qreal startRadian = m_startAngle * M_PI / 180.0;
// qreal endRadian = m_endAngle * M_PI / 180.0;
// m_handles[0] = m_center + QPointF(cos(startRadian) * m_radii.x(), -sin(startRadian) * m_radii.y());
// m_handles[1] = m_center + QPointF(cos(endRadian) * m_radii.x(), -sin(endRadian) * m_radii.y());
}
void SpiralShape::setType(SpiralType type)
{
m_type = type;
updateKindHandle();
updatePath(size());
}
SpiralShape::SpiralType SpiralShape::type() const
{
return m_type;
}
void SpiralShape::setFade(qreal fade)
{
m_fade = fade;
updateKindHandle();
//updateAngleHandles();
updatePath(size());
}
qreal SpiralShape::fade() const
{
return m_fade;
}
bool SpiralShape::clockWise() const
{
return m_clockwise;
}
void SpiralShape::setClockWise(bool clockWise)
{
m_clockwise = clockWise;
updateKindHandle();
//updateAngleHandles();
updatePath(size());
}
QString SpiralShape::pathShapeId() const
{
return SpiralShapeId;
}
diff --git a/plugins/flake/pathshapes/spiral/SpiralShape.h b/plugins/flake/pathshapes/spiral/SpiralShape.h
index fd00157192..f60b698b9f 100644
--- a/plugins/flake/pathshapes/spiral/SpiralShape.h
+++ b/plugins/flake/pathshapes/spiral/SpiralShape.h
@@ -1,101 +1,105 @@
/* This file is part of the KDE project
Copyright (C) 2007 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 KOSPIRALSHAPE_H
#define KOSPIRALSHAPE_H
#include "KoParameterShape.h"
#define SpiralShapeId "SpiralShape"
/**
* This class adds support for the spiral
* shape.
*/
class SpiralShape : public KoParameterShape
{
public:
/// the possible spiral types
enum SpiralType {
Curve = 0, ///< spiral uses curves
Line = 1 ///< spiral uses lines
};
SpiralShape();
~SpiralShape();
+ KoShape* cloneShape() const override;
+
void setSize(const QSizeF &newSize);
virtual QPointF normalize();
/**
* Sets the type of the spiral.
* @param type the new spiral type
*/
void setType(SpiralType type);
/// Returns the actual spiral type
SpiralType type() const;
/**
* Sets the fade parameter of the spiral.
* @param angle the new start angle in degree
*/
void setFade(qreal fade);
/// Returns the actual fade parameter
qreal fade() const;
bool clockWise() const;
void setClockWise(bool clockwise);
/// reimplemented
virtual QString pathShapeId() const;
protected:
+ SpiralShape(const SpiralShape &rhs);
+
// reimplemented
virtual void saveOdf(KoShapeSavingContext &context) const;
// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void updatePath(const QSizeF &size);
void createPath(const QSizeF &size);
private:
void updateKindHandle();
void updateAngleHandles();
// fade parameter
qreal m_fade;
// angle for modifying the kind in radiant
qreal m_kindAngle;
// the center of the spiral
QPointF m_center;
// the radii of the spiral
QPointF m_radii;
// the actual spiral type
SpiralType m_type;
//
bool m_clockwise;
KoSubpath m_points;
};
#endif /* KOSPIRALSHAPE_H */
diff --git a/plugins/flake/pathshapes/spiral/SpiralShapeFactory.cpp b/plugins/flake/pathshapes/spiral/SpiralShapeFactory.cpp
index 598b4dedd6..8452229c23 100644
--- a/plugins/flake/pathshapes/spiral/SpiralShapeFactory.cpp
+++ b/plugins/flake/pathshapes/spiral/SpiralShapeFactory.cpp
@@ -1,60 +1,62 @@
/* This file is part of the KDE project
* Copyright (C) 2007 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 "SpiralShapeFactory.h"
#include "SpiralShape.h"
#include "SpiralShapeConfigWidget.h"
#include <KoShapeStroke.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
+#include "kis_pointer_utils.h"
+
SpiralShapeFactory::SpiralShapeFactory()
: KoShapeFactoryBase(SpiralShapeId, i18n("Spiral"))
{
setToolTip(i18n("A spiral shape"));
setIconName(koIconNameCStr("spiral-shape"));
setFamily("geometric");
setLoadingPriority(1);
}
KoShape *SpiralShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
SpiralShape *spiral = new SpiralShape();
- spiral->setStroke(new KoShapeStroke(1.0));
+ spiral->setStroke(toQShared(new KoShapeStroke(1.0)));
spiral->setShapeId(KoPathShapeId);
return spiral;
}
bool SpiralShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(e);
Q_UNUSED(context);
return false;
}
QList<KoShapeConfigWidgetBase *> SpiralShapeFactory::createShapeOptionPanels()
{
QList<KoShapeConfigWidgetBase *> panels;
panels.append(new SpiralShapeConfigWidget());
return panels;
}
diff --git a/plugins/flake/pathshapes/star/StarShape.cpp b/plugins/flake/pathshapes/star/StarShape.cpp
index 5c41de282a..90119085d8 100644
--- a/plugins/flake/pathshapes/star/StarShape.cpp
+++ b/plugins/flake/pathshapes/star/StarShape.cpp
@@ -1,431 +1,457 @@
/* This file is part of the KDE project
Copyright (C) 2006-2009 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 "StarShape.h"
+#include <KoParameterShape_p.h>
#include <KoPathPoint.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeSavingContext.h>
#include <KoXmlReader.h>
#include <KoXmlNS.h>
#include <KoXmlWriter.h>
#include <QStringList>
#include <math.h>
StarShape::StarShape()
: m_cornerCount(5)
, m_zoomX(1.0)
, m_zoomY(1.0)
, m_convex(false)
{
m_radius[base] = 25.0;
m_radius[tip] = 50.0;
m_angles[base] = m_angles[tip] = defaultAngleRadian();
m_roundness[base] = m_roundness[tip] = 0.0f;
m_center = QPointF(50, 50);
updatePath(QSize(100, 100));
}
+StarShape::StarShape(const StarShape &rhs)
+ : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)),
+ m_cornerCount(rhs.m_cornerCount),
+ m_radius(rhs.m_radius),
+ m_angles(rhs.m_angles),
+ m_zoomX(rhs.m_zoomX),
+ m_zoomY(rhs.m_zoomY),
+ m_roundness(rhs.m_roundness),
+ m_center(rhs.m_center),
+ m_convex(rhs.m_convex)
+{
+}
+
StarShape::~StarShape()
{
}
+KoShape *StarShape::cloneShape() const
+{
+ return new StarShape(*this);
+}
+
+
void StarShape::setCornerCount(uint cornerCount)
{
if (cornerCount >= 3) {
double oldDefaultAngle = defaultAngleRadian();
m_cornerCount = cornerCount;
double newDefaultAngle = defaultAngleRadian();
m_angles[base] += newDefaultAngle - oldDefaultAngle;
m_angles[tip] += newDefaultAngle - oldDefaultAngle;
updatePath(QSize());
}
}
uint StarShape::cornerCount() const
{
return m_cornerCount;
}
void StarShape::setBaseRadius(qreal baseRadius)
{
m_radius[base] = fabs(baseRadius);
updatePath(QSize());
}
qreal StarShape::baseRadius() const
{
return m_radius[base];
}
void StarShape::setTipRadius(qreal tipRadius)
{
m_radius[tip] = fabs(tipRadius);
updatePath(QSize());
}
qreal StarShape::tipRadius() const
{
return m_radius[tip];
}
void StarShape::setBaseRoundness(qreal baseRoundness)
{
m_roundness[base] = baseRoundness;
updatePath(QSize());
}
void StarShape::setTipRoundness(qreal tipRoundness)
{
m_roundness[tip] = tipRoundness;
updatePath(QSize());
}
void StarShape::setConvex(bool convex)
{
m_convex = convex;
updatePath(QSize());
}
bool StarShape::convex() const
{
return m_convex;
}
QPointF StarShape::starCenter() const
{
return m_center;
}
void StarShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
{
if (modifiers & Qt::ShiftModifier) {
QPointF handle = handles()[handleId];
QPointF tangentVector = point - handle;
qreal distance = sqrt(tangentVector.x() * tangentVector.x() + tangentVector.y() * tangentVector.y());
QPointF radialVector = handle - m_center;
// cross product to determine in which direction the user is dragging
qreal moveDirection = radialVector.x() * tangentVector.y() - radialVector.y() * tangentVector.x();
// make the roundness stick to zero if distance is under a certain value
float snapDistance = 3.0;
if (distance >= 0.0) {
distance = distance < snapDistance ? 0.0 : distance - snapDistance;
} else {
distance = distance > -snapDistance ? 0.0 : distance + snapDistance;
}
// control changes roundness on both handles, else only the actual handle roundness is changed
if (modifiers & Qt::ControlModifier) {
m_roundness[handleId] = moveDirection < 0.0f ? distance : -distance;
} else {
m_roundness[base] = m_roundness[tip] = moveDirection < 0.0f ? distance : -distance;
}
} else {
QPointF distVector = point - m_center;
// unapply scaling
distVector.rx() /= m_zoomX;
distVector.ry() /= m_zoomY;
m_radius[handleId] = sqrt(distVector.x() * distVector.x() + distVector.y() * distVector.y());
qreal angle = atan2(distVector.y(), distVector.x());
if (angle < 0.0) {
angle += 2.0 * M_PI;
}
qreal diffAngle = angle - m_angles[handleId];
qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount);
if (handleId == tip) {
m_angles[tip] += diffAngle - radianStep;
m_angles[base] += diffAngle - radianStep;
} else {
// control make the base point move freely
if (modifiers & Qt::ControlModifier) {
m_angles[base] += diffAngle - 2 * radianStep;
} else {
m_angles[base] = m_angles[tip];
}
}
}
}
void StarShape::updatePath(const QSizeF &size)
{
+ Q_D(KoParameterShape);
+
Q_UNUSED(size);
qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount);
createPoints(m_convex ? m_cornerCount : 2 * m_cornerCount);
- KoSubpath &points = *m_subpaths[0];
+ KoSubpath &points = *d->subpaths[0];
uint index = 0;
for (uint i = 0; i < 2 * m_cornerCount; ++i) {
uint cornerType = i % 2;
if (cornerType == base && m_convex) {
continue;
}
qreal radian = static_cast<qreal>((i + 1) * radianStep) + m_angles[cornerType];
QPointF cornerPoint = QPointF(m_zoomX * m_radius[cornerType] * cos(radian), m_zoomY * m_radius[cornerType] * sin(radian));
points[index]->setPoint(m_center + cornerPoint);
points[index]->unsetProperty(KoPathPoint::StopSubpath);
points[index]->unsetProperty(KoPathPoint::CloseSubpath);
if (m_roundness[cornerType] > 1e-10 || m_roundness[cornerType] < -1e-10) {
// normalized cross product to compute tangential vector for handle point
QPointF tangentVector(cornerPoint.y() / m_radius[cornerType], -cornerPoint.x() / m_radius[cornerType]);
points[index]->setControlPoint2(points[index]->point() - m_roundness[cornerType] * tangentVector);
points[index]->setControlPoint1(points[index]->point() + m_roundness[cornerType] * tangentVector);
} else {
points[index]->removeControlPoint1();
points[index]->removeControlPoint2();
}
index++;
}
// first path starts and closes path
points[0]->setProperty(KoPathPoint::StartSubpath);
points[0]->setProperty(KoPathPoint::CloseSubpath);
// last point stops and closes path
points.last()->setProperty(KoPathPoint::StopSubpath);
points.last()->setProperty(KoPathPoint::CloseSubpath);
normalize();
QList<QPointF> handles;
handles.push_back(points.at(tip)->point());
if (!m_convex) {
handles.push_back(points.at(base)->point());
}
setHandles(handles);
m_center = computeCenter();
}
void StarShape::createPoints(int requiredPointCount)
{
- if (m_subpaths.count() != 1) {
+ Q_D(KoParameterShape);
+
+ if (d->subpaths.count() != 1) {
clear();
- m_subpaths.append(new KoSubpath());
+ d->subpaths.append(new KoSubpath());
}
- int currentPointCount = m_subpaths[0]->count();
+ int currentPointCount = d->subpaths[0]->count();
if (currentPointCount > requiredPointCount) {
for (int i = 0; i < currentPointCount - requiredPointCount; ++i) {
- delete m_subpaths[0]->front();
- m_subpaths[0]->pop_front();
+ delete d->subpaths[0]->front();
+ d->subpaths[0]->pop_front();
}
} else if (requiredPointCount > currentPointCount) {
for (int i = 0; i < requiredPointCount - currentPointCount; ++i) {
- m_subpaths[0]->append(new KoPathPoint(this, QPointF()));
+ d->subpaths[0]->append(new KoPathPoint(this, QPointF()));
}
}
}
void StarShape::setSize(const QSizeF &newSize)
{
QTransform matrix(resizeMatrix(newSize));
m_zoomX *= matrix.m11();
m_zoomY *= matrix.m22();
// this transforms the handles
KoParameterShape::setSize(newSize);
m_center = computeCenter();
}
QPointF StarShape::computeCenter() const
{
- KoSubpath &points = *m_subpaths[0];
+ Q_D(const KoParameterShape);
+
+ KoSubpath &points = *d->subpaths[0];
QPointF center(0, 0);
for (uint i = 0; i < m_cornerCount; ++i) {
if (m_convex) {
center += points[i]->point();
} else {
center += points[2 * i]->point();
}
}
return center / static_cast<qreal>(m_cornerCount);
}
bool StarShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
bool loadAsCustomShape = false;
if (element.localName() == "custom-shape") {
QString drawEngine = element.attributeNS(KoXmlNS::draw, "engine", "");
if (drawEngine != "calligra:star") {
return false;
}
loadAsCustomShape = true;
} else if (element.localName() != "regular-polygon") {
return false;
}
m_radius[tip] = 50;
m_center = QPointF(50, 50);
if (!loadAsCustomShape) {
QString corners = element.attributeNS(KoXmlNS::draw, "corners", "");
if (!corners.isEmpty()) {
m_cornerCount = corners.toUInt();
// initialize default angles of tip and base
m_angles[base] = m_angles[tip] = defaultAngleRadian();
}
m_convex = (element.attributeNS(KoXmlNS::draw, "concave", "false") == "false");
if (m_convex) {
m_radius[base] = m_radius[tip];
} else {
// sharpness is radius of ellipse on which inner polygon points are located
// 0% means all polygon points are on a single ellipse
// 100% means inner points are located at polygon center point
QString sharpness = element.attributeNS(KoXmlNS::draw, "sharpness", "");
if (!sharpness.isEmpty() && sharpness.right(1) == "%") {
float percent = sharpness.left(sharpness.length() - 1).toFloat();
m_radius[base] = m_radius[tip] * (100 - percent) / 100;
}
}
} else {
QString drawData = element.attributeNS(KoXmlNS::draw, "data");
if (drawData.isEmpty()) {
return false;
}
QStringList properties = drawData.split(';');
if (properties.count() == 0) {
return false;
}
foreach (const QString &property, properties) {
QStringList pair = property.split(':');
if (pair.count() != 2) {
continue;
}
if (pair[0] == "corners") {
m_cornerCount = pair[1].toInt();
} else if (pair[0] == "concave") {
m_convex = (pair[1] == "false");
} else if (pair[0] == "baseRoundness") {
m_roundness[base] = pair[1].toDouble();
} else if (pair[0] == "tipRoundness") {
m_roundness[tip] = pair[1].toDouble();
} else if (pair[0] == "baseAngle") {
m_angles[base] = pair[1].toDouble();
} else if (pair[0] == "tipAngle") {
m_angles[tip] = pair[1].toDouble();
} else if (pair[0] == "sharpness") {
float percent = pair[1].left(pair[1].length() - 1).toFloat();
m_radius[base] = m_radius[tip] * (100 - percent) / 100;
}
}
if (m_convex) {
m_radius[base] = m_radius[tip];
}
}
updatePath(QSizeF());
// reset transformation
setTransformation(QTransform());
loadOdfAttributes(element, context, OdfAllAttributes);
loadText(element, context);
return true;
}
void StarShape::saveOdf(KoShapeSavingContext &context) const
{
if (isParametricShape()) {
double defaultAngle = defaultAngleRadian();
bool hasRoundness = m_roundness[tip] != 0.0f || m_roundness[base] != 0.0f;
bool hasAngleOffset = m_angles[base] != defaultAngle || m_angles[tip] != defaultAngle;
if (hasRoundness || hasAngleOffset) {
// draw:regular-polygon has no means of saving roundness
// so we save as a custom shape with a specific draw:engine
context.xmlWriter().startElement("draw:custom-shape");
saveOdfAttributes(context, OdfAllAttributes);
// now write the special shape data
context.xmlWriter().addAttribute("draw:engine", "calligra:star");
// create the data attribute
QString drawData = QString("corners:%1;").arg(m_cornerCount);
drawData += m_convex ? "concave:false;" : "concave:true;";
if (!m_convex) {
// sharpness is radius of ellipse on which inner polygon points are located
// 0% means all polygon points are on a single ellipse
// 100% means inner points are located at polygon center point
qreal percent = (m_radius[tip] - m_radius[base]) / m_radius[tip] * 100.0;
drawData += QString("sharpness:%1%;").arg(percent);
}
if (m_roundness[base] != 0.0f) {
drawData += QString("baseRoundness:%1;").arg(m_roundness[base]);
}
if (m_roundness[tip] != 0.0f) {
drawData += QString("tipRoundness:%1;").arg(m_roundness[tip]);
}
drawData += QString("baseAngle:%1;").arg(m_angles[base]);
drawData += QString("tipAngle:%1;").arg(m_angles[tip]);
context.xmlWriter().addAttribute("draw:data", drawData);
saveOdfCommonChildElements(context);
saveText(context);
// write a enhanced geometry element for compatibility with other applications
context.xmlWriter().startElement("draw:enhanced-geometry");
context.xmlWriter().addAttribute("draw:enhanced-path", toString(transformation()));
context.xmlWriter().endElement(); // draw:enhanced-geometry
context.xmlWriter().endElement(); // draw:custom-shape
} else {
context.xmlWriter().startElement("draw:regular-polygon");
saveOdfAttributes(context, OdfAllAttributes);
context.xmlWriter().addAttribute("draw:corners", m_cornerCount);
context.xmlWriter().addAttribute("draw:concave", m_convex ? "false" : "true");
if (!m_convex) {
// sharpness is radius of ellipse on which inner polygon points are located
// 0% means all polygon points are on a single ellipse
// 100% means inner points are located at polygon center point
qreal percent = (m_radius[tip] - m_radius[base]) / m_radius[tip] * 100.0;
context.xmlWriter().addAttribute("draw:sharpness", QString("%1%").arg(percent));
}
saveOdfCommonChildElements(context);
saveText(context);
context.xmlWriter().endElement();
}
} else {
KoPathShape::saveOdf(context);
}
}
QString StarShape::pathShapeId() const
{
return StarShapeId;
}
double StarShape::defaultAngleRadian() const
{
qreal radianStep = M_PI / static_cast<qreal>(m_cornerCount);
return M_PI_2 - 2 * radianStep;
}
diff --git a/plugins/flake/pathshapes/star/StarShape.h b/plugins/flake/pathshapes/star/StarShape.h
index 9c5d19b5af..78dc7fe9a2 100644
--- a/plugins/flake/pathshapes/star/StarShape.h
+++ b/plugins/flake/pathshapes/star/StarShape.h
@@ -1,145 +1,150 @@
/* This file is part of the KDE project
Copyright (C) 2006-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 KOSTARSHAPE_H
#define KOSTARSHAPE_H
+#include <array>
#include <KoParameterShape.h>
#define StarShapeId "StarShape"
/**
* The star shape is a shape that can represent a star or
* a regular polygon. There a some properties which can
* be changed to control the appearance of the shape
* like the number of corners, the inner/outer radius
* and the corner roundness.
*/
class StarShape : public KoParameterShape
{
public:
StarShape();
~StarShape();
+ KoShape* cloneShape() const override;
+
/**
* Sets the number of corners.
*
* The minimum accepted number of corners is 3.
* If the star is set to be convex (like a regular polygon),
* the corner count equals the number of polygon points.
* For a real star it represents the number of legs the star has.
*
* @param cornerCount the new number of corners
*/
void setCornerCount(uint cornerCount);
/// Returns the number of corners
uint cornerCount() const;
/**
* Sets the radius of the base points.
* The base radius has no meaning if the star is set convex.
* @param baseRadius the new base radius
*/
void setBaseRadius(qreal baseRadius);
/// Returns the base radius
qreal baseRadius() const;
/**
* Sets the radius of the tip points.
* @param tipRadius the new tip radius
*/
void setTipRadius(qreal tipRadius);
/// Returns the tip radius
qreal tipRadius() const;
/**
* Sets the roundness at the base points.
*
* A roundness value of zero disables the roundness.
*
* @param baseRoundness the new base roundness
*/
void setBaseRoundness(qreal baseRoundness);
/**
* Sets the roundness at the tip points.
*
* A roundness value of zero disables the roundness.
*
* @param tipRoundness the new base roundness
*/
void setTipRoundness(qreal tipRoundness);
/**
* Sets the star to be convex, looking like a polygon.
* @param convex if true makes shape behave like regular polygon
*/
void setConvex(bool convex);
/// Returns if the star represents a regular polygon.
bool convex() const;
/**
* Returns the star center point in shape coordinates.
*
* The star center is the weight center of the star and not necessarily
* coincident with the shape center point.
*/
QPointF starCenter() const;
/// reimplemented
virtual void setSize(const QSizeF &newSize);
/// reimplemented
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
/// reimplemented
virtual void saveOdf(KoShapeSavingContext &context) const;
/// reimplemented
virtual QString pathShapeId() const;
protected:
+ StarShape(const StarShape &rhs);
+
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers = Qt::NoModifier);
void updatePath(const QSizeF &size);
/// recreates the path points when the corner count or convexity changes
void createPoints(int requiredPointCount);
private:
/// Computes the star center point from the inner points
QPointF computeCenter() const;
/// Returns the default offset angle in radian
double defaultAngleRadian() const;
/// the handle types
enum Handles { tip = 0, base = 1 };
uint m_cornerCount; ///< number of corners
- qreal m_radius[2]; ///< the different radii
- qreal m_angles[2]; ///< the offset angles
+ std::array<qreal, 2> m_radius; ///< the different radii
+ std::array<qreal, 2> m_angles; ///< the offset angles
qreal m_zoomX; ///< scaling in x
qreal m_zoomY; ///< scaling in y
- qreal m_roundness[2]; ///< the roundness at the handles
+ std::array<qreal, 2> m_roundness; ///< the roundness at the handles
QPointF m_center; ///< the star center point
bool m_convex; ///< controls if the star is convex
};
#endif /* KOSTARSHAPE_H */
diff --git a/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp b/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp
index 502f1511cd..0d203d231b 100644
--- a/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp
+++ b/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp
@@ -1,93 +1,95 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "StarShapeConfigWidget.h"
#include "StarShape.h"
#include "StarShapeConfigCommand.h"
+#include "kis_document_aware_spin_box_unit_manager.h"
+
StarShapeConfigWidget::StarShapeConfigWidget()
{
widget.setupUi(this);
connect(widget.corners, SIGNAL(valueChanged(int)), this, SIGNAL(propertyChanged()));
connect(widget.innerRadius, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged()));
connect(widget.outerRadius, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged()));
connect(widget.convex, SIGNAL(stateChanged(int)), this, SIGNAL(propertyChanged()));
connect(widget.convex, SIGNAL(clicked()), this, SLOT(typeChanged()));
}
void StarShapeConfigWidget::setUnit(const KoUnit &unit)
{
widget.innerRadius->setUnit(unit);
widget.outerRadius->setUnit(unit);
}
void StarShapeConfigWidget::open(KoShape *shape)
{
m_star = dynamic_cast<StarShape *>(shape);
if (!m_star) {
return;
}
widget.corners->blockSignals(true);
widget.innerRadius->blockSignals(true);
widget.outerRadius->blockSignals(true);
widget.convex->blockSignals(true);
widget.corners->setValue(m_star->cornerCount());
widget.innerRadius->changeValue(m_star->baseRadius());
widget.outerRadius->changeValue(m_star->tipRadius());
widget.convex->setCheckState(m_star->convex() ? Qt::Checked : Qt::Unchecked);
typeChanged();
widget.corners->blockSignals(false);
widget.innerRadius->blockSignals(false);
widget.outerRadius->blockSignals(false);
widget.convex->blockSignals(false);
}
void StarShapeConfigWidget::save()
{
if (!m_star) {
return;
}
m_star->setCornerCount(widget.corners->value());
m_star->setBaseRadius(widget.innerRadius->value());
m_star->setTipRadius(widget.outerRadius->value());
m_star->setConvex(widget.convex->checkState() == Qt::Checked);
}
KUndo2Command *StarShapeConfigWidget::createCommand()
{
if (!m_star) {
return 0;
} else
return new StarShapeConfigCommand(m_star, widget.corners->value(), widget.innerRadius->value(),
widget.outerRadius->value(), widget.convex->checkState() == Qt::Checked);
}
void StarShapeConfigWidget::typeChanged()
{
if (widget.convex->checkState() == Qt::Checked) {
widget.innerRadius->setEnabled(false);
} else {
widget.innerRadius->setEnabled(true);
}
}
diff --git a/plugins/flake/pathshapes/star/StarShapeFactory.cpp b/plugins/flake/pathshapes/star/StarShapeFactory.cpp
index 9388afa127..68e4685eef 100644
--- a/plugins/flake/pathshapes/star/StarShapeFactory.cpp
+++ b/plugins/flake/pathshapes/star/StarShapeFactory.cpp
@@ -1,159 +1,161 @@
/* 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 "star/StarShapeFactory.h"
#include "star/StarShape.h"
#include "star/StarShapeConfigWidget.h"
#include <KoShapeFactoryBase.h>
#include <KoShapeStroke.h>
#include <KoProperties.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include <KoColorBackground.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
+#include "kis_pointer_utils.h"
+
StarShapeFactory::StarShapeFactory()
: KoShapeFactoryBase(StarShapeId, i18n("A star shape"))
{
setToolTip(i18n("A star"));
setIconName(koIconNameCStr("star-shape"));
QStringList elementNames;
elementNames << "regular-polygon" << "custom-shape";
setXmlElementNames(KoXmlNS::draw, elementNames);
setLoadingPriority(5);
KoShapeTemplate t;
t.id = KoPathShapeId;
t.templateId = "star";
t.name = i18n("Star");
t.family = "geometric";
t.toolTip = i18n("A star");
t.iconName = koIconName("star-shape");
KoProperties *props = new KoProperties();
props->setProperty("corners", 5);
QVariant v;
v.setValue(QColor(Qt::yellow));
props->setProperty("background", v);
t.properties = props;
addTemplate(t);
t.id = KoPathShapeId;
t.templateId = "flower";
t.name = i18n("Flower");
t.family = "funny";
t.toolTip = i18n("A flower");
t.iconName = koIconName("flower-shape");
props = new KoProperties();
props->setProperty("corners", 5);
props->setProperty("baseRadius", 10.0);
props->setProperty("tipRadius", 50.0);
props->setProperty("baseRoundness", 0.0);
props->setProperty("tipRoundness", 40.0);
v.setValue(QColor(Qt::magenta));
props->setProperty("background", v);
t.properties = props;
addTemplate(t);
t.id = KoPathShapeId;
t.templateId = "pentagon";
t.name = i18n("Pentagon");
t.family = "geometric";
t.toolTip = i18n("A pentagon");
t.iconName = koIconName("pentagon-shape");
props = new KoProperties();
props->setProperty("corners", 5);
props->setProperty("convex", true);
props->setProperty("tipRadius", 50.0);
props->setProperty("tipRoundness", 0.0);
v.setValue(QColor(Qt::blue));
props->setProperty("background", v);
t.properties = props;
addTemplate(t);
t.id = KoPathShapeId;
t.templateId = "hexagon";
t.name = i18n("Hexagon");
t.family = "geometric";
t.toolTip = i18n("A hexagon");
t.iconName = koIconName("hexagon-shape");
props = new KoProperties();
props->setProperty("corners", 6);
props->setProperty("convex", true);
props->setProperty("tipRadius", 50.0);
props->setProperty("tipRoundness", 0.0);
v.setValue(QColor(Qt::blue));
props->setProperty("background", v);
t.properties = props;
addTemplate(t);
}
KoShape *StarShapeFactory::createDefaultShape(KoDocumentResourceManager *) const
{
StarShape *star = new StarShape();
- star->setStroke(new KoShapeStroke(1.0));
+ star->setStroke(toQShared(new KoShapeStroke(1.0)));
star->setShapeId(KoPathShapeId);
return star;
}
KoShape *StarShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *) const
{
StarShape *star = new StarShape();
if (!star) {
return 0;
}
star->setCornerCount(params->intProperty("corners", 5));
star->setConvex(params->boolProperty("convex", false));
star->setBaseRadius(params->doubleProperty("baseRadius", 25.0));
star->setTipRadius(params->doubleProperty("tipRadius", 50.0));
star->setBaseRoundness(params->doubleProperty("baseRoundness", 0.0));
star->setTipRoundness(params->doubleProperty("tipRoundness", 0.0));
- star->setStroke(new KoShapeStroke(1.0));
+ star->setStroke(toQShared(new KoShapeStroke(1.0)));
star->setShapeId(KoPathShapeId);
QVariant v;
if (params->property("background", v)) {
star->setBackground(QSharedPointer<KoColorBackground>(new KoColorBackground(v.value<QColor>())));
}
return star;
}
bool StarShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
if (e.localName() == "regular-polygon" && e.namespaceURI() == KoXmlNS::draw) {
return true;
}
return (e.localName() == "custom-shape" && e.namespaceURI() == KoXmlNS::draw
&& e.attributeNS(KoXmlNS::draw, "engine", "") == "calligra:star");
}
QList<KoShapeConfigWidgetBase *> StarShapeFactory::createShapeOptionPanels()
{
QList<KoShapeConfigWidgetBase *> panels;
panels.append(new StarShapeConfigWidget());
return panels;
}
diff --git a/plugins/flake/textshape/AnnotationTextShapeFactory.cpp b/plugins/flake/textshape/AnnotationTextShapeFactory.cpp
index 5e3b4db53f..1fa42fdc79 100644
--- a/plugins/flake/textshape/AnnotationTextShapeFactory.cpp
+++ b/plugins/flake/textshape/AnnotationTextShapeFactory.cpp
@@ -1,135 +1,135 @@
/* This file is part of the KDE project
* Copyright (C) 2013 Mojtaba Shahi Senobari <mojtaba.shahi3000@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 "AnnotationTextShapeFactory.h"
#include "AnnotationTextShape.h"
#include <KoProperties.h>
#include <KoShape.h>
#include <KoTextDocument.h>
#include <KoTextShapeData.h>
#include <KoXmlNS.h>
#include <KoStyleManager.h>
#include <KoDocumentResourceManager.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextRangeManager.h>
#include <changetracker/KoChangeTracker.h>
#include <KoImageCollection.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <QDebug>
#include <kundo2stack.h>
#include <QTextCursor>
AnnotationTextShapeFactory::AnnotationTextShapeFactory()
: KoShapeFactoryBase(AnnotationShape_SHAPEID, i18n("Annotation"))
{
setToolTip(i18n("Annotation shape to show annotation content"));
QList<QPair<QString, QStringList> > odfElements;
odfElements.append(QPair<QString, QStringList>(KoXmlNS::office, QStringList("annotation")));
setXmlElements(odfElements);
KoShapeTemplate t;
t.name = i18n("Annotation");
t.iconName = koIconName("x-shape-text"); // Any icon for now :)
t.toolTip = i18n("Annotation Shape");
KoProperties *props = new KoProperties();
t.properties = props;
props->setProperty("demo", true);
addTemplate(t);
}
KoShape *AnnotationTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
{
KoInlineTextObjectManager *manager = 0;
KoTextRangeManager *locationManager = 0;
if (documentResources && documentResources->hasResource(KoText::InlineTextObjectManager)) {
QVariant variant = documentResources->resource(KoText::InlineTextObjectManager);
if (variant.isValid()) {
manager = variant.value<KoInlineTextObjectManager *>();
}
}
if (documentResources && documentResources->hasResource(KoText::TextRangeManager)) {
QVariant variant = documentResources->resource(KoText::TextRangeManager);
if (variant.isValid()) {
locationManager = variant.value<KoTextRangeManager *>();
}
}
if (!manager) {
manager = new KoInlineTextObjectManager();
}
if (!locationManager) {
locationManager = new KoTextRangeManager();
}
AnnotationTextShape *annotation = new AnnotationTextShape(manager, locationManager);
if (documentResources) {
KoTextDocument document(annotation->textShapeData()->document());
if (documentResources->hasResource(KoText::StyleManager)) {
KoStyleManager *styleManager = documentResources->resource(KoText::StyleManager).value<KoStyleManager *>();
document.setStyleManager(styleManager);
}
// this is needed so the shape can reinitialize itself with the stylemanager
- annotation->textShapeData()->setDocument(annotation->textShapeData()->document(), true);
+ annotation->textShapeData()->setDocument(annotation->textShapeData()->document());
document.setUndoStack(documentResources->undoStack());
if (documentResources->hasResource(KoText::PageProvider)) {
KoPageProvider *pp = static_cast<KoPageProvider *>(documentResources->resource(KoText::PageProvider).value<void *>());
annotation->setPageProvider(pp);
}
if (documentResources->hasResource(KoText::ChangeTracker)) {
KoChangeTracker *changeTracker = documentResources->resource(KoText::ChangeTracker).value<KoChangeTracker *>();
document.setChangeTracker(changeTracker);
}
//update the resources of the document
annotation->updateDocumentData();
annotation->setImageCollection(documentResources->imageCollection());
}
// Should set if we don't set id it will set to TextShapeID.
annotation->setShapeId(AnnotationShape_SHAPEID);
annotation->setAnnotaionTextData(annotation->textShapeData());
return annotation;
}
KoShape *AnnotationTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const
{
Q_UNUSED(params);
AnnotationTextShape *shape = static_cast<AnnotationTextShape *>(createDefaultShape(documentResources));
shape->textShapeData()->document()->setUndoRedoEnabled(false);
if (documentResources) {
shape->setImageCollection(documentResources->imageCollection());
}
shape->textShapeData()->document()->setUndoRedoEnabled(true);
return shape;
}
bool AnnotationTextShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
return (e.localName() == "annotation" && e.namespaceURI() == KoXmlNS::office);
}
diff --git a/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp b/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp
index c49f2513ea..cd2ac550c9 100644
--- a/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp
+++ b/plugins/flake/textshape/ShrinkToFitShapeContainer.cpp
@@ -1,199 +1,199 @@
/* 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(*(new ShrinkToFitShapeContainerPrivate(this, childShape)))
+ : KoShapeContainer(new ShrinkToFitShapeContainerPrivate(this, childShape))
{
Q_UNUSED(documentResources);
Q_D(ShrinkToFitShapeContainer);
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
d->model = new ShrinkToFitShapeContainerModel(this, d);
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 *>(d->model), SLOT(finishedLayout()));
}
ShrinkToFitShapeContainer::~ShrinkToFitShapeContainer()
{
}
void ShrinkToFitShapeContainer::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
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
{
Q_D(const ShrinkToFitShapeContainer);
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, ShrinkToFitShapeContainerPrivate *d)
: q(q)
, d(d)
, 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 *>(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);
d->childShape->setSize(newSize);
QTransform m;
m.scale(m_scale, m_scale);
d->childShape->setTransformation(m);
}
}
bool ShrinkToFitShapeContainerModel::inheritsTransform(const KoShape *child) const
{
Q_ASSERT(child == d->childShape); Q_UNUSED(child);
return true;
}
bool ShrinkToFitShapeContainerModel::isChildLocked(const KoShape *child) const
{
Q_ASSERT(child == d->childShape); Q_UNUSED(child);
return true;
}
bool ShrinkToFitShapeContainerModel::isClipped(const KoShape *child) const
{
Q_ASSERT(child == d->childShape); Q_UNUSED(child);
return false;
}
diff --git a/plugins/flake/textshape/SimpleRootAreaProvider.cpp b/plugins/flake/textshape/SimpleRootAreaProvider.cpp
index e3f4459fcf..ec975e2c90 100644
--- a/plugins/flake/textshape/SimpleRootAreaProvider.cpp
+++ b/plugins/flake/textshape/SimpleRootAreaProvider.cpp
@@ -1,190 +1,190 @@
/* This file is part of the KDE project
* Copyright (C) 2011 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 "SimpleRootAreaProvider.h"
#include "TextShape.h"
#include <KoBorder.h>
#include <KoTextLayoutRootArea.h>
#include <KoTextLayoutObstruction.h>
SimpleRootAreaProvider::SimpleRootAreaProvider(KoTextShapeData *data, TextShape *textshape)
: m_textShape(textshape)
, m_area(0)
, m_textShapeData(data)
, m_fixAutogrow(false)
{
}
KoTextLayoutRootArea *SimpleRootAreaProvider::provide(KoTextDocumentLayout *documentLayout, const RootAreaConstraint &, int requestedPosition, bool *isNewRootArea)
{
if (m_area == 0) {
*isNewRootArea = true;
m_area = new KoTextLayoutRootArea(documentLayout);
m_area->setAssociatedShape(m_textShape);
m_textShapeData->setRootArea(m_area);
return m_area;
}
if (requestedPosition == 0) {
*isNewRootArea = false;
return m_area;
}
return 0;
}
void SimpleRootAreaProvider::releaseAllAfter(KoTextLayoutRootArea *afterThis)
{
Q_UNUSED(afterThis);
}
void SimpleRootAreaProvider::doPostLayout(KoTextLayoutRootArea *rootArea, bool isNewRootArea)
{
Q_UNUSED(isNewRootArea);
m_textShape->update(m_textShape->outlineRect());
QSizeF newSize = m_textShape->size()
- QSizeF(m_textShapeData->leftPadding() + m_textShapeData->rightPadding(),
m_textShapeData->topPadding() + m_textShapeData->bottomPadding());
KoBorder *border = m_textShape->border();
if (border) {
newSize -= QSizeF(border->borderWidth(KoBorder::LeftBorder) + border->borderWidth(KoBorder::RightBorder), border->borderWidth(KoBorder::TopBorder) + border->borderWidth(KoBorder::BottomBorder));
}
if (m_textShapeData->verticalAlignment() & Qt::AlignBottom) {
// Do nothing
}
if (m_textShapeData->verticalAlignment() & Qt::AlignVCenter) {
// Do nothing
}
if (m_textShapeData->resizeMethod() == KoTextShapeData::AutoGrowWidthAndHeight
|| m_textShapeData->resizeMethod() == KoTextShapeData::AutoGrowHeight) {
qreal height = rootArea->bottom() - rootArea->top();
if (height > newSize.height()) {
newSize.setHeight(height);
}
if (m_textShape->shapeId() == "AnnotationTextShapeID") {
if (height < newSize.height()) {
newSize.setHeight(rootArea->bottom() - rootArea->top());
}
}
}
if (m_textShapeData->resizeMethod() == KoTextShapeData::AutoGrowWidthAndHeight
|| m_textShapeData->resizeMethod() == KoTextShapeData::AutoGrowWidth) {
qreal width = rootArea->right() - rootArea->left();
if (width > newSize.width()) {
newSize.setWidth(rootArea->right() - rootArea->left());
}
}
qreal newBottom = rootArea->top() + newSize.height();
- KoFlake::Position sizeAnchor = KoFlake::TopLeftCorner;
+ KoFlake::AnchorPosition sizeAnchor = KoFlake::TopLeft;
if (m_textShapeData->verticalAlignment() & Qt::AlignBottom) {
if (true /*FIXME test no page based shapes interfering*/) {
rootArea->setVerticalAlignOffset(newBottom - rootArea->bottom());
- sizeAnchor = KoFlake::BottomLeftCorner;
+ sizeAnchor = KoFlake::BottomLeft;
}
}
if (m_textShapeData->verticalAlignment() & Qt::AlignVCenter) {
if (true /*FIXME test no page based shapes interfering*/) {
rootArea->setVerticalAlignOffset((newBottom - rootArea->bottom()) / 2);
- sizeAnchor = KoFlake::CenteredPosition;
+ sizeAnchor = KoFlake::Center;
}
}
newSize += QSizeF(m_textShapeData->leftPadding() + m_textShapeData->rightPadding(),
m_textShapeData->topPadding() + m_textShapeData->bottomPadding());
if (border) {
newSize += QSizeF(border->borderWidth(KoBorder::LeftBorder) + border->borderWidth(KoBorder::RightBorder), border->borderWidth(KoBorder::TopBorder) + border->borderWidth(KoBorder::BottomBorder));
}
if (newSize != m_textShape->size()) {
// OO grows to both sides so when to small the initial layouting needs
// to keep that into account.
if (m_fixAutogrow) {
m_fixAutogrow = false;
QSizeF tmpSize = m_textShape->size();
tmpSize.setWidth(newSize.width());
- QPointF centerpos = rootArea->associatedShape()->absolutePosition(KoFlake::CenteredPosition);
+ QPointF centerpos = rootArea->associatedShape()->absolutePosition(KoFlake::Center);
m_textShape->setSize(tmpSize);
- m_textShape->setAbsolutePosition(centerpos, KoFlake::CenteredPosition);
+ m_textShape->setAbsolutePosition(centerpos, KoFlake::Center);
centerpos = rootArea->associatedShape()->absolutePosition(sizeAnchor);
m_textShape->setSize(newSize);
m_textShape->setAbsolutePosition(centerpos, sizeAnchor);
}
m_textShape->setSize(newSize);
}
m_textShape->update(m_textShape->outlineRect());
}
void SimpleRootAreaProvider::updateAll()
{
if (m_area && m_area->associatedShape()) {
m_area->associatedShape()->update();
}
}
QRectF SimpleRootAreaProvider::suggestRect(KoTextLayoutRootArea *rootArea)
{
//Come up with a rect, but actually we don't need the height, as we set it to infinite below
// Still better keep it for completeness sake
QRectF rect(QPointF(), m_textShape->size());
rect.adjust(m_textShapeData->leftPadding(), m_textShapeData->topPadding(), -m_textShapeData->rightPadding(), - m_textShapeData->bottomPadding());
KoBorder *border = m_textShape->border();
if (border) {
rect.adjust(border->borderWidth(KoBorder::LeftBorder), border->borderWidth(KoBorder::TopBorder),
-border->borderWidth(KoBorder::RightBorder), - border->borderWidth(KoBorder::BottomBorder));
}
// In simple cases we always set height way too high so that we have no breaking
// If the shape grows afterwards or not is handled in doPostLayout()
rect.setHeight(1E6);
if (m_textShapeData->resizeMethod() == KoTextShapeData::AutoGrowWidthAndHeight
|| m_textShapeData->resizeMethod() == KoTextShapeData::AutoGrowWidth) {
rootArea->setNoWrap(1E6);
}
// Make sure the size is not negative due to padding and border with
// This can happen on vertical lines containing text on shape.
if (rect.width() < 0) {
rect.setWidth(0);
}
return rect;
}
QList<KoTextLayoutObstruction *> SimpleRootAreaProvider::relevantObstructions(KoTextLayoutRootArea *rootArea)
{
Q_UNUSED(rootArea);
QList<KoTextLayoutObstruction *> obstructions;
/*
m_textShape->boundingRect();
QList<KoShape *> shapes;
shapes = manager->shapesAt(canvasRect):
*/
return obstructions;
}
diff --git a/plugins/flake/textshape/TextShapeFactory.cpp b/plugins/flake/textshape/TextShapeFactory.cpp
index 37b36d46ee..915ee5e58b 100644
--- a/plugins/flake/textshape/TextShapeFactory.cpp
+++ b/plugins/flake/textshape/TextShapeFactory.cpp
@@ -1,164 +1,164 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007,2009,2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
* Copyright (C) 2008 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 "TextShapeFactory.h"
#include "TextShape.h"
#include <KoProperties.h>
#include <KoShape.h>
#include <KoTextDocument.h>
#include <KoTextShapeData.h>
#include <KoXmlNS.h>
#include <KoStyleManager.h>
#include <KoDocumentResourceManager.h>
#include <KoInlineTextObjectManager.h>
#include <KoTextRangeManager.h>
#include <changetracker/KoChangeTracker.h>
#include <KoImageCollection.h>
#include <KoShapeLoadingContext.h>
#include <KoIcon.h>
#include <klocalizedstring.h>
#include <kundo2stack.h>
#include <QTextCursor>
TextShapeFactory::TextShapeFactory()
: KoShapeFactoryBase(TextShape_SHAPEID, i18n("Text"))
{
setToolTip(i18n("A shape that shows text"));
QList<QPair<QString, QStringList> > odfElements;
odfElements.append(QPair<QString, QStringList>(KoXmlNS::draw, QStringList("text-box")));
odfElements.append(QPair<QString, QStringList>(KoXmlNS::table, QStringList("table")));
setXmlElements(odfElements);
setLoadingPriority(1);
KoShapeTemplate t;
t.name = i18n("Text");
t.iconName = koIconName("x-shape-text");
t.toolTip = i18n("Text Shape");
KoProperties *props = new KoProperties();
t.properties = props;
props->setProperty("demo", true);
addTemplate(t);
}
KoShape *TextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const
{
KoInlineTextObjectManager *manager = 0;
KoTextRangeManager *locationManager = 0;
if (documentResources && documentResources->hasResource(KoText::InlineTextObjectManager)) {
QVariant variant = documentResources->resource(KoText::InlineTextObjectManager);
if (variant.isValid()) {
manager = variant.value<KoInlineTextObjectManager *>();
}
}
if (documentResources && documentResources->hasResource(KoText::TextRangeManager)) {
QVariant variant = documentResources->resource(KoText::TextRangeManager);
if (variant.isValid()) {
locationManager = variant.value<KoTextRangeManager *>();
}
}
if (!manager) {
manager = new KoInlineTextObjectManager();
}
if (!locationManager) {
locationManager = new KoTextRangeManager();
}
TextShape *text = new TextShape(manager, locationManager);
if (documentResources) {
KoTextDocument document(text->textShapeData()->document());
if (documentResources->hasResource(KoText::StyleManager)) {
KoStyleManager *styleManager = documentResources->resource(KoText::StyleManager).value<KoStyleManager *>();
document.setStyleManager(styleManager);
}
// this is needed so the shape can reinitialize itself with the stylemanager
- text->textShapeData()->setDocument(text->textShapeData()->document(), true);
+ text->textShapeData()->setDocument(text->textShapeData()->document());
document.setUndoStack(documentResources->undoStack());
if (documentResources->hasResource(KoText::PageProvider)) {
KoPageProvider *pp = static_cast<KoPageProvider *>(documentResources->resource(KoText::PageProvider).value<void *>());
text->setPageProvider(pp);
}
if (documentResources->hasResource(KoText::ChangeTracker)) {
KoChangeTracker *changeTracker = documentResources->resource(KoText::ChangeTracker).value<KoChangeTracker *>();
document.setChangeTracker(changeTracker);
}
document.setShapeController(documentResources->shapeController());
//update the resources of the document
text->updateDocumentData();
text->setImageCollection(documentResources->imageCollection());
}
return text;
}
KoShape *TextShapeFactory::createShape(const KoProperties */*params*/, KoDocumentResourceManager *documentResources) const
{
TextShape *shape = static_cast<TextShape *>(createDefaultShape(documentResources));
shape->textShapeData()->document()->setUndoRedoEnabled(false);
shape->setSize(QSizeF(300, 200));
/*
QString text("text");
if (params->contains(text)) {
KoTextShapeData *shapeData = qobject_cast<KoTextShapeData*>(shape->userData());
}
*/
if (documentResources) {
shape->setImageCollection(documentResources->imageCollection());
}
shape->textShapeData()->document()->setUndoRedoEnabled(true);
return shape;
}
bool TextShapeFactory::supports(const KoXmlElement &e, KoShapeLoadingContext &context) const
{
Q_UNUSED(context);
return (e.localName() == "text-box" && e.namespaceURI() == KoXmlNS::draw) ||
(e.localName() == "table" && e.namespaceURI() == KoXmlNS::table);
}
void TextShapeFactory::newDocumentResourceManager(KoDocumentResourceManager *manager) const
{
QVariant variant;
variant.setValue<KoInlineTextObjectManager *>(new KoInlineTextObjectManager(manager));
manager->setResource(KoText::InlineTextObjectManager, variant);
variant.setValue<KoTextRangeManager *>(new KoTextRangeManager());
manager->setResource(KoText::TextRangeManager, variant);
if (!manager->hasResource(KoDocumentResourceManager::UndoStack)) {
// qWarning() << "No KUndo2Stack found in the document resource manager, creating a new one";
manager->setUndoStack(new KUndo2Stack(manager));
}
if (!manager->hasResource(KoText::StyleManager)) {
variant.setValue(new KoStyleManager(manager));
manager->setResource(KoText::StyleManager, variant);
}
if (!manager->imageCollection()) {
manager->setImageCollection(new KoImageCollection(manager));
}
}
diff --git a/plugins/flake/textshape/TextTool.cpp b/plugins/flake/textshape/TextTool.cpp
index 6a013741d9..fc7218feb0 100644
--- a/plugins/flake/textshape/TextTool.cpp
+++ b/plugins/flake/textshape/TextTool.cpp
@@ -1,3162 +1,3164 @@
/* 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 "dialogs/SimpleTableWidget.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 <KoCanvasResourceManager.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 <QSignalMapper>
#include <QLinearGradient>
#include <QBitmap>
#include <QDrag>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMimeData>
#include "AnnotationTextShape.h"
#define AnnotationShape_SHAPEID "AnnotationTextShapeID"
#include "KoShapeBasedDocumentBase.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(KoCanvasResourceManager::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.
QSignalMapper *signalMapper = new QSignalMapper(this);
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString)));
- QList<QAction *> list;
- list.append(this->action("format_font"));
+ 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());
- list.append(a);
+ m_contextMenu->addAction(a);
addAction(QString("apply_%1").arg(factory->id()), a);
}
}
- setPopupActionList(list);
- connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas()));
+ 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(KoCanvasResourceManager::ApplicationSpeciality)
& KoCanvasResourceManager::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;
}
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);
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);
qSort(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);
v.setValue(rect);
canvas()->resourceManager()->setResource(KoCanvasResourceManager::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()->shapeManager()->selection();
+ 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);
}
}
}
KoCanvasResourceManager *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();
}
}
-QStringList TextTool::supportedPasteMimeTypes() const
-{
- QStringList list;
- list << "text/plain" << "text/html" << "application/vnd.oasis.opendocument.text";
- return list;
-}
-
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_OSX
// 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);
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));
}
void TextTool::keyReleaseEvent(QKeyEvent *event)
{
event->accept();
}
void TextTool::updateActions()
{
bool notInAnnotation = !dynamic_cast<AnnotationTextShape *>(m_textShape);
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(KoCanvasResourceManager::ApplicationSpeciality)
& KoCanvasResourceManager::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 toolActivation, const QSet<KoShape *> &shapes)
+void TextTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
- Q_UNUSED(toolActivation);
+ 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(KoCanvasResourceManager::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);
v.setValue(rect);
canvas()->resourceManager()->setResource(KoCanvasResourceManager::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 whereever 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(KoCanvasResourceManager::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);
// 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);
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(KoCanvasResourceManager::ApplicationSpeciality)
& KoCanvasResourceManager::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 genearted 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 == KoCanvasResourceManager::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()->shapeManager()->selection();
+ 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)
{
KoTextDocument document(m_textShapeData->document());
KoStyleManager *styleManager = document.styleManager();
KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId));
KoCharacterStyle *autoStyle;
if (!originalCharStyle) {
KoCharacterStyle blankStyle;
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) {
qSwap(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); // elipses
}
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()
{
AnnotationTextShape *shape = (AnnotationTextShape *)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager());
textEditor()->addAnnotation(shape);
// Set annotation creator.
KConfig cfg("kritarc");
cfg.reparseConfiguration();
KConfigGroup authorGroup(&cfg, "Author");
QStringList profiles = authorGroup.readEntry("profile-names", QStringList());
KSharedConfig::openConfig()->reparseConfiguration();
KConfigGroup appAuthorGroup(KSharedConfig::openConfig(), "Author");
QString profile = appAuthorGroup.readEntry("active-profile", "");
KConfigGroup cgs(&authorGroup, "Author-" + profile);
if (profiles.contains(profile)) {
KConfigGroup cgs(&authorGroup, "Author-" + profile);
shape->setCreator(cgs.readEntry("creator"));
} else {
if (profile == "anonymous") {
shape->setCreator("Anonymous");
} else {
KUser user(KUser::UseRealUserID);
shape->setCreator(user.property(KUser::FullName).toString());
}
}
// Set Annotation creation date.
shape->setDate(QDate::currentDate().toString(Qt::ISODate));
}
diff --git a/plugins/flake/textshape/TextTool.h b/plugins/flake/textshape/TextTool.h
index 6391c4385a..2205361ad3 100644
--- a/plugins/flake/textshape/TextTool.h
+++ b/plugins/flake/textshape/TextTool.h
@@ -1,425 +1,428 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2009 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2009 KO GmbH <cbo@kogmbh.com>
* Copyright (C) 2011 Mojtaba Shahi Senobari <mojtaba.shahi3000@gmail.com>
* Copyright (C) 2008, 2012 Pierre Stirnweiss <pstirnweiss@googlemail.org>
* 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.
*/
#ifndef KOTEXTTOOL_H
#define KOTEXTTOOL_H
#include "TextShape.h"
#include "KoPointedAt.h"
#include <KoToolBase.h>
#include <KoTextCommandBase.h>
#include <KoUnit.h>
#include <KoBorder.h>
#include <QClipboard>
#include <QHash>
#include <QTextBlock>
#include <QTextCursor>
#include <QTimer>
#include <QWeakPointer>
#include <QRectF>
#include <QPointer>
#include <TextEditingPluginContainer.h>
class InsertCharacter;
class KoChangeTracker;
class KoCharacterStyle;
class KoColor;
class KoColorPopupAction;
class KoParagraphStyle;
class KoStyleManager;
class KoTextEditor;
class UndoTextCommand;
class QAction;
class KActionMenu;
class KoFontFamilyAction;
class FontSizeAction;
class KUndo2Command;
class QDrag;
class QMimeData;
+class QMenu;
class MockCanvas;
class TextToolSelection;
/**
* This is the tool for the text-shape (which is a flake-based plugin).
*/
class TextTool : public KoToolBase, public KoUndoableTool
{
Q_OBJECT
public:
explicit TextTool(KoCanvasBase *canvas);
#ifndef NDEBUG
explicit TextTool(MockCanvas *canvas);
#endif
virtual ~TextTool();
/// reimplemented from superclass
virtual void paint(QPainter &painter, const KoViewConverter &converter);
/// reimplemented from superclass
virtual void mousePressEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseTripleClickEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseMoveEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseReleaseEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void keyPressEvent(QKeyEvent *event);
/// reimplemented from superclass
virtual void keyReleaseEvent(QKeyEvent *event);
/// reimplemented from superclass
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape *> &shapes);
/// reimplemented from superclass
virtual void deactivate();
/// reimplemented from superclass
virtual void copy() const;
/// reimplemented from KoUndoableTool
virtual void setAddUndoCommandAllowed(bool allowed)
{
m_allowAddUndoCommand = allowed;
}
///reimplemented
virtual void deleteSelection();
/// reimplemented from superclass
virtual void cut();
/// reimplemented from superclass
virtual bool paste();
/// reimplemented from superclass
- virtual QStringList supportedPasteMimeTypes() const;
- /// reimplemented from superclass
virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point);
/// reimplemented from superclass
void dragLeaveEvent(QDragLeaveEvent *event);
/// reimplemented from superclass
virtual void dropEvent(QDropEvent *event, const QPointF &point);
/// reimplemented from superclass
virtual void repaintDecorations();
/// reimplemented from superclass
virtual KoToolSelection *selection();
/// reimplemented from superclass
virtual QList<QPointer<QWidget> > createOptionWidgets();
/// reimplemented from superclass
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const;
/// reimplemented from superclass
virtual void inputMethodEvent(QInputMethodEvent *event);
/// The following two methods allow an undo/redo command to tell the tool, it will modify the QTextDocument and wants to be parent of the undo/redo commands resulting from these changes.
void startEditing(KUndo2Command *command);
void stopEditing();
void setShapeData(KoTextShapeData *data);
QRectF caretRect(QTextCursor *cursor, bool *upToDate = 0) const;
QRectF textRect(QTextCursor &cursor) const;
protected:
virtual void createActions();
TextShape *textShape()
{
return m_textShape;
}
friend class SimpleParagraphWidget;
friend class ParagraphSettingsDialog;
KoTextEditor *textEditor()
{
return m_textEditor.data();
}
public Q_SLOTS:
/// Insert comment to document.
void insertAnnotation();
/// start the textedit-plugin.
void startTextEditingPlugin(const QString &pluginId);
/// reimplemented from KoToolBase
virtual void canvasResourceChanged(int key, const QVariant &res);
Q_SIGNALS:
/// emitted every time a different styleManager is set.
void styleManagerChanged(KoStyleManager *manager);
/// emitted every time a caret move leads to a different character format being under the caret
void charFormatChanged(const QTextCharFormat &format, const QTextCharFormat &refBlockCharFormat);
/// emitted every time a caret move leads to a different paragraph format being under the caret
void blockFormatChanged(const QTextBlockFormat &format);
/// emitted every time a caret move leads to a different paragraph format being under the caret
void blockChanged(const QTextBlock &block);
private Q_SLOTS:
/// inserts new paragraph and includes it into the new section
void insertNewSection();
/// configures params of the current section
void configureSection();
/// inserts paragraph between sections bounds
void splitSections();
/// paste text from the clipboard without formatting
void pasteAsText();
/// make the selected text bold or not
void bold(bool);
/// make the selected text italic or not
void italic(bool);
/// underline of the selected text
void underline(bool underline);
/// strikethrough of the selected text
void strikeOut(bool strikeOut);
/// insert a non breaking space at the caret position
void nonbreakingSpace();
/// insert a non breaking hyphen at the caret position
void nonbreakingHyphen();
/// insert a soft hyphen at the caret position
void softHyphen();
/// insert a linebreak at the caret position
void lineBreak();
/// force the remainder of the text into the next page
void insertFrameBreak();
/// align all of the selected text left
void alignLeft();
/// align all of the selected text right
void alignRight();
/// align all of the selected text centered
void alignCenter();
/// align all of the selected text block-justified
void alignBlock();
/// make the selected text switch to be super-script
void superScript(bool);
/// make the selected text switch to be sub-script
void subScript(bool);
/// move the paragraph indent of the selected text to be less (left on LtR text)
void decreaseIndent();
/// move the paragraph indent of the selected text to be more (right on LtR text)
void increaseIndent();
/// Increase the font size. This will preserve eventual difference in font size within the selection.
void increaseFontSize();
/// Decrease font size. See above.
void decreaseFontSize();
/// Set font family
void setFontFamily(const QString &);
/// Set Font size
void setFontSize(qreal size);
/// see KoTextEditor::insertIndexMarker
void insertIndexMarker();
/// shows a dialog to insert a table
void insertTable();
/// insert a table of given dimensions
void insertTableQuick(int rows, int columns);
/// insert a row above
void insertTableRowAbove();
/// insert a row below
void insertTableRowBelow();
/// insert a column left
void insertTableColumnLeft();
/// insert a column right
void insertTableColumnRight();
/// delete a column
void deleteTableColumn();
/// delete a row
void deleteTableRow();
/// merge table cells
void mergeTableCells();
/// split previous merged table cells
void splitTableCells();
/// format the table border (enter table pen mode)
void setTableBorderData(const KoBorder::BorderData &data);
/// shows a dialog to alter the paragraph properties
void formatParagraph();
/// select all text in the current document.
void selectAll();
/// show the style manager
void showStyleManager(int styleId = -1);
/// change color of a selected text
void setTextColor(const KoColor &color);
/// change background color of a selected text
void setBackgroundColor(const KoColor &color);
/// Enable or disable grow-width-to-fit-text.
void setGrowWidthToFit(bool enabled);
/// Enable or disable grow-height-to-fit-text.
void setGrowHeightToFit(bool enabled);
/// Enable or disable shrink-to-fit-text.
void setShrinkToFit(bool enabled);
/// set Paragraph style of current selection. Existing style will be completely overridden.
void setStyle(KoParagraphStyle *syle);
/// set the characterStyle of the current selection. see above.
void setStyle(KoCharacterStyle *style);
/// set the level of current selected list
void setListLevel(int level);
/// slot to call when a series of commands is started that together need to become 1 undo action.
void startMacro(const QString &title);
/// slot to call when a series of commands has ended that together should be 1 undo action.
void stopMacro();
/// show the insert special character docker.
void insertSpecialCharacter();
/// insert string
void insertString(const QString &string);
/// returns the focus to canvas when styles are selected in the optionDocker
void returnFocusToCanvas();
void selectFont();
void shapeAddedToCanvas();
void blinkCaret();
void relayoutContent();
// called when the m_textShapeData has been deleted.
void shapeDataRemoved();
//Show tooltip with editing info
void showEditTip();
/// print debug about the details of the text document
void debugTextDocument();
/// print debug about the details of the styles on the current text document
void debugTextStyles();
void ensureCursorVisible(bool moveView = true);
void createStyleFromCurrentBlockFormat(const QString &name);
void createStyleFromCurrentCharFormat(const QString &name);
void testSlot(bool);
/// change block text direction
void textDirectionChanged();
void updateActions();
+ QMenu* popupActionsMenu();
+
private:
void repaintCaret();
void repaintSelection();
KoPointedAt hitTest(const QPointF &point) const;
void updateStyleManager();
void updateSelectedShape(const QPointF &point, bool noDocumentChange);
void updateSelectionHandler();
void editingPluginEvents();
void finishedWord();
void finishedParagraph();
void startingSimpleEdit();
void runUrl(KoPointerEvent *event, QString &url);
void useTableBorderCursor();
QMimeData *generateMimeData() const;
TextEditingPluginContainer *textEditingPluginContainer();
private:
friend class UndoTextCommand;
friend class ChangeTracker;
friend class TextCutCommand;
friend class ShowChangesCommand;
TextShape *m_textShape; // where caret of m_textEditor currently is
KoTextShapeData *m_textShapeData; // where caret of m_textEditor currently is
QWeakPointer<KoTextEditor> m_textEditor;
QWeakPointer<KoTextEditor> m_oldTextEditor;
KoChangeTracker *m_changeTracker;
KoUnit m_unit;
bool m_allowActions;
bool m_allowAddUndoCommand;
bool m_allowResourceManagerUpdates;
int m_prevCursorPosition; /// used by editingPluginEvents
int m_prevMouseSelectionStart, m_prevMouseSelectionEnd;
QTimer m_caretTimer;
bool m_caretTimerState;
QAction *m_actionPasteAsText;
QAction *m_actionFormatBold;
QAction *m_actionFormatItalic;
QAction *m_actionFormatUnderline;
QAction *m_actionFormatStrikeOut;
QAction *m_actionAlignLeft;
QAction *m_actionAlignRight;
QAction *m_actionAlignCenter;
QAction *m_actionAlignBlock;
QAction *m_actionFormatSuper;
QAction *m_actionFormatSub;
QAction *m_actionFormatIncreaseIndent;
QAction *m_actionFormatDecreaseIndent;
QAction *m_growWidthAction;
QAction *m_growHeightAction;
QAction *m_shrinkToFitAction;
QAction *m_actionChangeDirection;
QAction *m_actionInsertSection;
QAction *m_actionConfigureSection;
QAction *m_actionSplitSections;
KActionMenu *m_variableMenu;
FontSizeAction *m_actionFormatFontSize;
KoFontFamilyAction *m_actionFormatFontFamily;
KoColorPopupAction *m_actionFormatTextColor;
KoColorPopupAction *m_actionFormatBackgroundColor;
KUndo2Command *m_currentCommand; //this command will be the direct parent of undoCommands generated as the result of QTextDocument changes
bool m_currentCommandHasChildren;
InsertCharacter *m_specialCharacterDocker;
QPointer<TextEditingPluginContainer> m_textEditingPlugins;
bool m_textTyping;
bool m_textDeleting;
QTimer m_editTipTimer;
KoPointedAt m_editTipPointedAt;
QPoint m_editTipPos;
bool m_delayedEnsureVisible;
TextToolSelection *m_toolSelection;
KoPointedAt m_tableDragInfo;
bool m_tableDraggedOnce;
bool m_tableDragWithShift;
QPointF m_draggingOrigin;
qreal m_dx;
qreal m_dy;
bool m_tablePenMode;
KoBorder::BorderData m_tablePenBorderData;
mutable QRectF m_lastImMicroFocus;
bool m_clickWithinSelection;
QDrag *m_drag;
QAbstractTextDocumentLayout::Selection m_preDragSelection;
+
+ QScopedPointer<QMenu> m_contextMenu;
};
#endif
diff --git a/plugins/flake/textshape/TextToolFactory.cpp b/plugins/flake/textshape/TextToolFactory.cpp
index 0be037e0d7..d032de3f64 100644
--- a/plugins/flake/textshape/TextToolFactory.cpp
+++ b/plugins/flake/textshape/TextToolFactory.cpp
@@ -1,45 +1,45 @@
/* 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.
*/
#include "TextToolFactory.h"
#include "TextTool.h"
#include "TextShape.h"
#include "AnnotationTextShape.h"
#include <KoIcon.h>
#include <klocalizedstring.h>
TextToolFactory::TextToolFactory()
: KoToolFactoryBase("TextTool")
{
setToolTip(i18n("Text editing"));
setSection(dynamicToolType() + ",calligrawords,calligraauthor");
setIconName(koIconNameCStr("tool-text"));
- setPriority(0);
+ setPriority(2);
setActivationShapeId(TextShape_SHAPEID "," AnnotationShape_SHAPEID);
}
TextToolFactory::~TextToolFactory()
{
}
KoToolBase *TextToolFactory::createTool(KoCanvasBase *canvas)
{
return new TextTool(canvas);
}
diff --git a/plugins/flake/textshape/kotext/CMakeLists.txt b/plugins/flake/textshape/kotext/CMakeLists.txt
index 067d54be28..c8743a5852 100644
--- a/plugins/flake/textshape/kotext/CMakeLists.txt
+++ b/plugins/flake/textshape/kotext/CMakeLists.txt
@@ -1,136 +1,135 @@
include_directories(${FONTCONFIG_INCLUDE_DIR}
${FREETYPE_INCLUDE_DIRS})
set(kritatext_LIB_SRCS
KoDocumentRdfBase.cpp
KoText.cpp
KoTextBlockData.cpp
KoTextBlockBorderData.cpp
KoTextBlockPaintStrategyBase.cpp
KoTextOdfSaveHelper.cpp
- KoTextPaste.cpp
KoTextDocument.cpp
KoTextEditor.cpp
KoTextEditor_undo.cpp
KoTextEditor_format.cpp
KoList.cpp
KoTextEditingRegistry.cpp
KoTextEditingFactory.cpp
KoTextEditingPlugin.cpp
KoTextRangeManager.cpp
KoInlineTextObjectManager.cpp
KoInlineObjectFactoryBase.cpp
KoInlineObjectRegistry.cpp
InsertInlineObjectActionBase_p.cpp
InsertVariableAction.cpp
InsertNamedVariableAction.cpp
InsertTextReferenceAction.cpp
InsertTextLocator.cpp
KoInlineObject.cpp
KoTextRange.cpp
KoVariable.cpp
KoVariableManager.cpp
KoNamedVariable.cpp
KoSection.cpp
KoSectionEnd.cpp
KoSectionUtils.cpp
KoSectionModel.cpp
KoTextLocator.cpp
KoTextReference.cpp
KoAnchorInlineObject.cpp
KoAnchorTextRange.cpp
KoTextShapeSavingContext.cpp
KoAnnotation.cpp
KoAnnotationManager.cpp
KoBookmark.cpp
KoBookmarkManager.cpp
KoInlineNote.cpp
KoInlineCite.cpp
KoTextSoftPageBreak.cpp
KoTextDebug.cpp
KoTextPage.cpp
KoPageProvider.cpp
KoTableColumnAndRowStyleManager.cpp
KoTextInlineRdf.cpp
KoTextMeta.cpp
KoTextTableTemplate.cpp
OdfTextTrackStyles.cpp
ToCBibGeneratorInfo.cpp
KoTableOfContentsGeneratorInfo.cpp
KoBibliographyInfo.cpp
BibliographyGenerator.cpp
styles/Styles_p.cpp
styles/KoCharacterStyle.cpp
styles/KoParagraphStyle.cpp
styles/KoStyleManager.cpp
styles/KoListStyle.cpp
styles/KoListLevelProperties.cpp
styles/KoTableStyle.cpp
styles/KoTableColumnStyle.cpp
styles/KoTableRowStyle.cpp
styles/KoTableCellStyle.cpp
styles/KoSectionStyle.cpp
opendocument/KoTextSharedLoadingData.cpp
opendocument/KoTextSharedSavingData.cpp
opendocument/KoTextLoader.cpp
opendocument/KoTextWriter_p.cpp
opendocument/KoTextWriter.cpp
changetracker/KoChangeTracker.cpp
changetracker/KoChangeTrackerElement.cpp
changetracker/KoFormatChangeInformation.cpp
changetracker/KoDeletedRowColumnDataStore.cpp
changetracker/KoDeletedRowData.cpp
changetracker/KoDeletedColumnData.cpp
changetracker/KoDeletedCellData.cpp
commands/ChangeAnchorPropertiesCommand.cpp
commands/ChangeListCommand.cpp
commands/ChangeStylesCommand.cpp
commands/ChangeStylesMacroCommand.cpp
commands/DeleteAnchorsCommand.cpp
commands/DeleteAnnotationsCommand.cpp
commands/DeleteCommand.cpp
commands/DeleteTableColumnCommand.cpp
commands/DeleteTableRowCommand.cpp
commands/InsertNoteCommand.cpp
commands/InsertTableColumnCommand.cpp
commands/InsertTableRowCommand.cpp
commands/ResizeTableCommand.cpp
commands/InsertInlineObjectCommand.cpp
commands/ListItemNumberingCommand.cpp
commands/TextPasteCommand.cpp
commands/AddTextRangeCommand.cpp
commands/AddAnnotationCommand.cpp
commands/ParagraphFormattingCommand.cpp
commands/RenameSectionCommand.cpp
commands/NewSectionCommand.cpp
commands/SplitSectionsCommand.cpp
KoTextDrag.cpp
KoTextCommandBase.cpp
TextDebug.cpp
)
add_library(kritatext SHARED ${kritatext_LIB_SRCS})
generate_export_header(kritatext BASE_NAME kritatext)
target_link_libraries(kritatext kritaflake Qt5::Gui kritawidgetutils )
target_link_libraries(kritatext LINK_INTERFACE_LIBRARIES kritaflake Qt5::Gui )
target_include_directories(kritatext
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/opendocument>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/changetracker>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/styles>
)
set_target_properties(kritatext PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritatext ${INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/plugins/flake/textshape/kotext/KoTextPaste.cpp b/plugins/flake/textshape/kotext/KoTextPaste.cpp
deleted file mode 100644
index e5f18c6f3b..0000000000
--- a/plugins/flake/textshape/kotext/KoTextPaste.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
- * 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 "KoTextPaste.h"
-
-#include <KoTextDocument.h>
-#include <KoOdfReadStore.h>
-#include <KoOdfLoadingContext.h>
-#include <KoShapeLoadingContext.h>
-#include <KoShapeController.h>
-#include <KoShape.h>
-#include <KoCanvasBase.h>
-#include <KoTextEditor.h>
-#include <opendocument/KoTextLoader.h>
-#include <KoTextSharedLoadingData.h>
-#include <KoSectionModel.h>
-
-#include "TextDebug.h"
-#ifdef SHOULD_BUILD_RDF
-#include "KoTextRdfCore.h"
-#include "KoSectionModel.h"
-#include <Soprano/Soprano>
-#endif
-
-class Q_DECL_HIDDEN KoTextPaste::Private
-{
-public:
- Private(KoTextEditor *editor, KoShapeController *shapeCont, QSharedPointer<Soprano::Model> _rdfModel,
- KoCanvasBase *c, KUndo2Command *cmd
- )
- : editor(editor)
- , resourceManager(shapeCont->resourceManager())
- , rdfModel(_rdfModel)
- , shapeController(shapeCont)
- , command(cmd)
- , canvas(c)
- {
- }
-
- KoTextEditor *editor;
- KoDocumentResourceManager *resourceManager;
- QSharedPointer<Soprano::Model> rdfModel;
- KoShapeController *shapeController;
- KUndo2Command *command;
- KoCanvasBase *canvas;
-};
-
-KoTextPaste::KoTextPaste(KoTextEditor *editor, KoShapeController *shapeController, QSharedPointer<Soprano::Model> rdfModel, KoCanvasBase *c, KUndo2Command *cmd)
- : d(new Private(editor, shapeController, rdfModel, c, cmd))
-{
-}
-
-KoTextPaste::~KoTextPaste()
-{
- delete d;
-}
-
-bool KoTextPaste::process(const KoXmlElement &body, KoOdfReadStore &odfStore)
-{
- bool ok = true;
- KoOdfLoadingContext loadingContext(odfStore.styles(), odfStore.store());
- KoShapeLoadingContext context(loadingContext, d->resourceManager);
- context.setSectionModel(KoTextDocument(d->editor->document()).sectionModel());
-
- KoTextLoader loader(context);
-
- debugText << "text paste";
- // load the paste directly into the editor's cursor -- which breaks encapsulation
- loader.loadBody(body, *d->editor->cursor(), KoTextLoader::PasteMode); // now let's load the body from the ODF KoXmlElement.
-
-// context.sectionModel()->invalidate(); FIXME!!
-
-#ifdef SHOULD_BUILD_RDF
- debugText << "text paste, rdf handling" << d->rdfModel;
- // RDF: Grab RDF metadata from ODF file if present & load it into rdfModel
- if (d->rdfModel)
- {
- QSharedPointer<Soprano::Model> tmpmodel(Soprano::createModel());
- ok = KoTextRdfCore::loadManifest(odfStore.store(), tmpmodel);
- debugText << "ok:" << ok << " tmpmodel.sz:" << tmpmodel->statementCount();
- debugText << "existing rdf model.sz:" << d->rdfModel->statementCount();
-#ifndef NDEBUG
- KoTextRdfCore::dumpModel("RDF from C+P", tmpmodel);
-#endif
- d->rdfModel->addStatements(tmpmodel->listStatements().allElements());
- debugText << "done... existing rdf model.sz:" << d->rdfModel->statementCount();
-#ifndef NDEBUG
- KoTextRdfCore::dumpModel("Imported RDF after C+P", d->rdfModel);
-#endif
- }
-#endif
-
- KoTextSharedLoadingData *sharedData = static_cast<KoTextSharedLoadingData *>(context.sharedData(KOTEXT_SHARED_LOADING_ID));
-
- // add shapes to the document
- foreach (KoShape *shape, sharedData->insertedShapes()) {
- QPointF move;
- d->canvas->clipToDocument(shape, move);
- if (move.x() != 0 || move.y() != 0) {
- shape->setPosition(shape->position() + move);
- }
-
- // During load we make page anchored shapes invisible, because otherwise
- // they leave empty rects in the text if there is run-around
- // now is the time to make them visible again
- shape->setVisible(true);
-
- d->editor->addCommand(d->shapeController->addShapeDirect(shape, d->command));
- }
-
- return ok;
-}
diff --git a/plugins/flake/textshape/kotext/KoTextPaste.h b/plugins/flake/textshape/kotext/KoTextPaste.h
deleted file mode 100644
index 54ac463484..0000000000
--- a/plugins/flake/textshape/kotext/KoTextPaste.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2008 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 KOTEXTPASTE_H
-#define KOTEXTPASTE_H
-
-#include <KoOdfPaste.h>
-#include "kritatext_export.h"
-
-class KoTextEditor;
-class KoShapeController;
-class KUndo2Command;
-class KoCanvasBase;
-
-#include <QSharedPointer>
-namespace Soprano
-{
- class Model;
-}
-
-class KRITATEXT_EXPORT KoTextPaste : public KoOdfPaste
-{
-public:
- /**
- * Note: RdfModel ownership is not taken. You must ensure that it remains
- * valid for the lifetime of the object.
- *
- * @param editor the KoTextEditor the text will be read into
- * @param shapeController the shapecontroller that gives access to the document's shapes and resourcemanager
- * @param rdfModel the rdfModel we'll insert the tuples into
- */
- KoTextPaste(KoTextEditor *editor, KoShapeController *shapeController, QSharedPointer<Soprano::Model> rdfModel, KoCanvasBase *canvas, KUndo2Command *cmd);
- virtual ~KoTextPaste();
-
-protected:
- /// reimplemented
- virtual bool process(const KoXmlElement &body, KoOdfReadStore &odfStore);
-
- class Private;
- Private * const d;
-};
-
-#endif /* KOTEXTPASTE_H */
diff --git a/plugins/flake/textshape/kotext/commands/TextPasteCommand.cpp b/plugins/flake/textshape/kotext/commands/TextPasteCommand.cpp
index b0ca0f7b5d..fbbbce2e11 100644
--- a/plugins/flake/textshape/kotext/commands/TextPasteCommand.cpp
+++ b/plugins/flake/textshape/kotext/commands/TextPasteCommand.cpp
@@ -1,133 +1,137 @@
/*
This file is part of the KDE project
* Copyright (C) 2009 Pierre Stirnweiss <pstirnweiss@googlemail.com>
* Copyright (C) 2011 Boudewijn Rempt <boud@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 "TextPasteCommand.h"
#include <KoText.h>
#include <KoTextEditor.h>
#include <KoTextDocument.h>
-#include <KoTextPaste.h>
#include <KoShapeController.h>
#include <KoParagraphStyle.h>
#include <klocalizedstring.h>
#include "TextDebug.h"
#include <QTextDocument>
#include <QMimeData>
#include "DeleteCommand.h"
#include "KoDocumentRdfBase.h"
+#include <KoOdf.h>
+#include <kis_assert.h>
+
#ifdef SHOULD_BUILD_RDF
#include <Soprano/Soprano>
#else
namespace Soprano
{
class Model
{
};
}
#endif
TextPasteCommand::TextPasteCommand(const QMimeData *mimeData,
QTextDocument *document,
KoShapeController *shapeController,
KoCanvasBase *canvas, KUndo2Command *parent, bool pasteAsText)
: KUndo2Command (parent),
m_mimeData(mimeData),
m_document(document),
m_rdf(0),
m_shapeController(shapeController),
m_canvas(canvas),
m_pasteAsText(pasteAsText),
m_first(true)
{
m_rdf = qobject_cast<KoDocumentRdfBase*>(shapeController->resourceManager()->resource(KoText::DocumentRdf).value<QObject*>());
if (m_pasteAsText)
setText(kundo2_i18n("Paste As Text"));
else
setText(kundo2_i18n("Paste"));
}
void TextPasteCommand::undo()
{
KUndo2Command::undo();
}
void TextPasteCommand::redo()
{
if (m_document.isNull()) return;
KoTextDocument textDocument(m_document);
KoTextEditor *editor = textDocument.textEditor();
if (!m_first) {
KUndo2Command::redo();
} else {
editor->beginEditBlock(); //this is needed so Qt does not merge successive paste actions together
m_first = false;
if (editor->hasSelection()) { //TODO
editor->addCommand(new DeleteCommand(DeleteCommand::NextChar, m_document.data(), m_shapeController, this));
}
// check for mime type
if (m_mimeData->hasFormat(KoOdf::mimeType(KoOdf::Text))
|| m_mimeData->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) ) {
KoOdf::DocumentType odfType = KoOdf::Text;
if (!m_mimeData->hasFormat(KoOdf::mimeType(odfType))) {
odfType = KoOdf::OpenOfficeClipboard;
}
if (editor->blockFormat().hasProperty(KoParagraphStyle::HiddenByTable)) {
editor->insertText(QString());
}
if (m_pasteAsText) {
editor->insertText(m_mimeData->text());
} else {
QSharedPointer<Soprano::Model> rdfModel;
#ifdef SHOULD_BUILD_RDF
if(!m_rdf) {
rdfModel = QSharedPointer<Soprano::Model>(Soprano::createModel());
} else {
rdfModel = m_rdf->model();
}
#endif
- KoTextPaste paste(editor, m_shapeController, rdfModel, m_canvas, this);
- paste.paste(odfType, m_mimeData);
+ KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "Pasting of text is not implemented yet!");
+
+ //KoTextPaste paste(editor, m_shapeController, rdfModel, m_canvas, this);
+ //paste.paste(odfType, m_mimeData);
#ifdef SHOULD_BUILD_RDF
if (m_rdf) {
m_rdf->updateInlineRdfStatements(editor->document());
}
#endif
}
} else if (!m_pasteAsText && m_mimeData->hasHtml()) {
editor->insertHtml(m_mimeData->html());
} else if (m_pasteAsText || m_mimeData->hasText()) {
editor->insertText(m_mimeData->text());
}
editor->endEditBlock(); //see above beginEditBlock
}
}
diff --git a/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp b/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp
index 7877b3cad6..e5a44acb6d 100644
--- a/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp
+++ b/plugins/flake/textshape/kotext/opendocument/KoTextLoader.cpp
@@ -1,1657 +1,1657 @@
/* This file is part of the KDE project
* Copyright (C) 2001-2006 David Faure <faure@kde.org>
* Copyright (C) 2007,2009,2011 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Sebastian Sauer <mail@dipe.org>
* Copyright (C) 2007,2011 Pierre Ducroquet <pinaraf@pinaraf.info>
* Copyright (C) 2007-2011 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
* Copyright (C) 2009-2012 KO GmbH <cbo@kogmbh.com>
* Copyright (C) 2009 Pierre Stirnweiss <pstirnweiss@googlemail.com>
* Copyright (C) 2010 KO GmbH <ben.martin@kogmbh.com>
* Copyright (C) 2011 Pavol Korinek <pavol.korinek@ixonos.com>
* Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
* Copyright (C) 2011 Boudewijn Rempt <boud@kogmbh.com>
* Copyright (C) 2011-2012 Gopalakrishna Bhat A <gopalakbhat@gmail.com>
* Copyright (C) 2012 Inge Wallin <inge@lysator.liu.se>
* Copyright (C) 2009-2012 C. Boemann <cbo@boemann.dk>
* Copyright (C) 2014-2015 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 "KoTextLoader.h"
#include <KoTextMeta.h>
#include <KoBookmark.h>
#include <KoBookmarkManager.h>
#include <KoAnnotation.h>
#include <KoAnnotationManager.h>
#include <KoInlineNote.h>
#include <KoInlineCite.h>
#include <KoTextRangeManager.h>
#include <KoInlineTextObjectManager.h>
#include "KoList.h"
#include <KoOdfLoadingContext.h>
#include <KoOdfStylesReader.h>
#include <KoOdfLineNumberingConfiguration.h>
#include <KoShapeContainer.h>
#include <KoShapeFactoryBase.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeRegistry.h>
#include <KoTableColumnAndRowStyleManager.h>
#include <KoAnchorInlineObject.h>
#include <KoAnchorTextRange.h>
#include <KoTextBlockData.h>
#include "KoTextDebug.h"
#include "KoTextDocument.h"
#include "KoTextSharedLoadingData.h"
#include <KoOdfBibliographyConfiguration.h>
#include <KoVariable.h>
#include <KoVariableManager.h>
#include <KoInlineObjectRegistry.h>
#include <KoXmlNS.h>
#include <KoXmlReader.h>
#include "KoTextInlineRdf.h"
#include "KoTableOfContentsGeneratorInfo.h"
#include "KoBibliographyInfo.h"
#include "KoSection.h"
#include "KoSectionEnd.h"
#include "KoTextSoftPageBreak.h"
#include "KoDocumentRdfBase.h"
#include "KoElementReference.h"
#include "KoTextTableTemplate.h"
#include "styles/KoStyleManager.h"
#include "styles/KoParagraphStyle.h"
#include "styles/KoCharacterStyle.h"
#include "styles/KoListStyle.h"
#include "styles/KoListLevelProperties.h"
#include "styles/KoTableStyle.h"
#include "styles/KoTableColumnStyle.h"
#include "styles/KoTableCellStyle.h"
#include "styles/KoSectionStyle.h"
#include <KoSectionUtils.h>
#include <KoSectionModel.h>
#include <klocalizedstring.h>
#include "TextDebug.h"
#include <QList>
#include <QVector>
#include <QMap>
#include <QRect>
#include <QStack>
#include <QTextBlock>
#include <QTextCursor>
#include <QTextList>
#include <QTextTable>
#include <QTime>
#include <QString>
#include <QTextInlineObject>
#include <QTextStream>
#include <QXmlStreamReader>
// if defined then debugging is enabled
// #define KOOPENDOCUMENTLOADER_DEBUG
/// \internal d-pointer class.
class Q_DECL_HIDDEN KoTextLoader::Private
{
public:
KoShapeLoadingContext &context;
KoTextSharedLoadingData *textSharedData;
// store it here so that you don't need to get it all the time from
// the KoOdfLoadingContext.
bool stylesDotXml;
QTextBlockFormat defaultBlockFormat;
QTextCharFormat defaultCharFormat;
int bodyProgressTotal;
int bodyProgressValue;
int nextProgressReportMs;
QTime progressTime;
QVector<KoList *> currentLists;
KoListStyle *currentListStyle;
int currentListLevel;
// Two lists that follow the same style are considered as one for numbering purposes
// This hash keeps all the lists that have the same style in one KoList.
QHash<KoListStyle *, KoList *> lists;
KoCharacterStyle *endCharStyle; // charstyle from empty span used at end of paragraph
KoStyleManager *styleManager;
KoShape *shape;
int loadSpanLevel;
int loadSpanInitialPos;
QVector<QString> nameSpacesList;
QList<KoSection *> openingSections;
QStack<KoSection *> sectionStack; // Used to track the parent of current section
QMap<QString, KoList *> xmlIdToListMap;
QVector<KoList *> m_previousList;
QMap<QString, KoList *> numberedParagraphListId;
QStringList rdfIdList;
/// level is between 1 and 10
void setCurrentList(KoList *currentList, int level);
/// level is between 1 and 10
KoList *previousList(int level);
explicit Private(KoShapeLoadingContext &context, KoShape *s)
: context(context),
textSharedData(0),
// stylesDotXml says from where the office:automatic-styles are to be picked from:
// the content.xml or the styles.xml (in a multidocument scenario). It does not
// decide from where the office:styles are to be picked (always picked from styles.xml).
// For our use here, stylesDotXml is always false (see ODF1.1 spec §2.1).
stylesDotXml(context.odfLoadingContext().useStylesAutoStyles()),
bodyProgressTotal(0),
bodyProgressValue(0),
nextProgressReportMs(0),
currentLists(10),
currentListStyle(0),
currentListLevel(1),
endCharStyle(0),
styleManager(0),
shape(s),
loadSpanLevel(0),
loadSpanInitialPos(0)
, m_previousList(10)
{
progressTime.start();
}
~Private() {
debugText << "Loading took" << (float)(progressTime.elapsed()) / 1000 << " seconds";
}
KoList *list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList);
};
KoList *KoTextLoader::Private::list(const QTextDocument *document, KoListStyle *listStyle, bool mergeSimilarStyledList)
{
//TODO: Remove mergeSimilarStyledList parameter by finding a way to put the numbered-paragraphs of same level
// to a single QTextList while loading rather than maintaining a hash list
if (mergeSimilarStyledList) {
if (lists.contains(listStyle)) {
return lists[listStyle];
}
}
KoList *newList = new KoList(document, listStyle);
lists[listStyle] = newList;
return newList;
}
void KoTextLoader::Private::setCurrentList(KoList *currentList, int level)
{
Q_ASSERT(level > 0 && level <= 10);
currentLists[level - 1] = currentList;
m_previousList[level - 1] = currentList;
}
KoList *KoTextLoader::Private::previousList(int level)
{
Q_ASSERT(level > 0 && level <= 10);
if (m_previousList.size() < level) {
return 0;
}
return m_previousList.at(level - 1);
}
inline static bool isspace(ushort ch)
{
// options are ordered by likelyhood
return ch == ' ' || ch== '\n' || ch == '\r' || ch == '\t';
}
QString KoTextLoader::normalizeWhitespace(const QString &in, bool leadingSpace)
{
QString textstring = in;
ushort *text = (ushort*)textstring.data(); // this detaches from the string 'in'
int r, w = 0;
int len = textstring.length();
for (r = 0; r < len; ++r) {
const ushort ch = text[r];
// check for space, tab, line feed, carriage return
if (isspace(ch)) {
// if we were lead by whitespace in some parent or previous sibling element,
// we completely collapse this space
if (r != 0 || !leadingSpace)
text[w++] = ' ';
// find the end of the whitespace run
while (r < len && isspace(text[r]))
++r;
// and then record the next non-whitespace character
if (r < len)
text[w++] = text[r];
} else {
text[w++] = ch;
}
}
// and now trim off the unused part of the string
textstring.truncate(w);
return textstring;
}
/////////////KoTextLoader
KoTextLoader::KoTextLoader(KoShapeLoadingContext &context, KoShape *shape)
: QObject()
, d(new Private(context, shape))
{
KoSharedLoadingData *sharedData = context.sharedData(KOTEXT_SHARED_LOADING_ID);
if (sharedData) {
d->textSharedData = dynamic_cast<KoTextSharedLoadingData *>(sharedData);
}
//debugText << "sharedData" << sharedData << "textSharedData" << d->textSharedData;
if (!d->textSharedData) {
d->textSharedData = new KoTextSharedLoadingData();
KoDocumentResourceManager *rm = context.documentResourceManager();
KoStyleManager *styleManager = rm->resource(KoText::StyleManager).value<KoStyleManager*>();
d->textSharedData->loadOdfStyles(context, styleManager);
if (!sharedData) {
context.addSharedData(KOTEXT_SHARED_LOADING_ID, d->textSharedData);
} else {
warnText << "A different type of sharedData was found under the" << KOTEXT_SHARED_LOADING_ID;
Q_ASSERT(false);
}
}
if (context.documentRdf()) {
d->rdfIdList = qobject_cast<KoDocumentRdfBase*>(context.documentRdf())->idrefList();
}
}
KoTextLoader::~KoTextLoader()
{
delete d;
}
void KoTextLoader::loadBody(const KoXmlElement &bodyElem, QTextCursor &cursor, LoadBodyMode mode)
{
const QTextDocument *document = cursor.block().document();
static int rootCallChecker = 0;
if (rootCallChecker == 0) {
if (document->resource(KoTextDocument::FrameCharFormat, KoTextDocument::FrameCharFormatUrl).isValid()) {
d->defaultBlockFormat = KoTextDocument(document).frameBlockFormat();
d->defaultCharFormat = KoTextDocument(document).frameCharFormat();
} else {
// This is the first call of loadBody on the document.
// Store the default block and char formats
// Will be used whenever a new block is inserted
d->defaultCharFormat = cursor.charFormat();
KoTextDocument(document).setFrameCharFormat(cursor.blockCharFormat());
d->defaultBlockFormat = cursor.blockFormat();
KoTextDocument(document).setFrameBlockFormat(cursor.blockFormat());
}
}
rootCallChecker++;
cursor.beginEditBlock();
// If we are pasting text, we should handle sections correctly
// we are saving which sections end in current block
// and put their ends after the inserted text.
QList<KoSectionEnd *> oldSectionEndings;
if (mode == PasteMode) {
QTextBlockFormat fmt = cursor.blockFormat();
oldSectionEndings = KoSectionUtils::sectionEndings(fmt);
fmt.clearProperty(KoParagraphStyle::SectionEndings);
cursor.setBlockFormat(fmt);
}
if (!d->openingSections.isEmpty()) {
QTextBlockFormat format = cursor.block().blockFormat();
d->openingSections << KoSectionUtils::sectionStartings(format); // if we had some already we need to append the new ones
KoSectionUtils::setSectionStartings(format, d->openingSections);
cursor.setBlockFormat(format);
d->openingSections.clear();
}
KoOdfLineNumberingConfiguration *lineNumberingConfiguration =
new KoOdfLineNumberingConfiguration(d->context.odfLoadingContext()
.stylesReader()
.lineNumberingConfiguration());
KoTextDocument(document).setLineNumberingConfiguration(lineNumberingConfiguration);
KoOdfBibliographyConfiguration *bibConfiguration =
new KoOdfBibliographyConfiguration(d->context.odfLoadingContext()
.stylesReader()
.globalBibliographyConfiguration());
KoTextDocument(document).styleManager()->setBibliographyConfiguration(bibConfiguration);
d->styleManager = KoTextDocument(document).styleManager();
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat());
#endif
bool usedParagraph = false; // set to true if we found a tag that used the paragraph, indicating that the next round needs to start a new one.
if (bodyElem.namespaceURI() == KoXmlNS::table && bodyElem.localName() == "table") {
loadTable(bodyElem, cursor);
}
else {
startBody(KoXml::childNodesCount(bodyElem));
KoXmlElement tag;
for (KoXmlNode _node = bodyElem.firstChild(); !_node.isNull(); _node = _node.nextSibling() ) {
if (!(tag = _node.toElement()).isNull()) {
const QString localName = tag.localName();
if (tag.namespaceURI() == KoXmlNS::text) {
if ((usedParagraph) && (tag.localName() != "table"))
cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat);
usedParagraph = true;
if (localName == "p") { // text paragraph
loadParagraph(tag, cursor);
} else if (localName == "h") { // heading
loadHeading(tag, cursor);
} else if (localName == "unordered-list" || localName == "ordered-list" // OOo-1.1
|| localName == "list" || localName == "numbered-paragraph") { // OASIS
loadList(tag, cursor);
} else if (localName == "section") {
loadSection(tag, cursor);
} else if (localName == "table-of-content") {
loadTableOfContents(tag, cursor);
} else if (localName == "bibliography") {
loadBibliography(tag, cursor);
} else {
KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(tag, d->context);
if (obj) {
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (textObjectManager) {
KoVariableManager *varManager = textObjectManager->variableManager();
if (varManager) {
textObjectManager->insertInlineObject(cursor, obj);
}
}
} else {
usedParagraph = false;
warnText << "unhandled text:" << localName;
}
}
} else if (tag.namespaceURI() == KoXmlNS::draw
|| tag.namespaceURI() == KoXmlNS::dr3d) {
loadShape(tag, cursor);
} else if (tag.namespaceURI() == KoXmlNS::table) {
if (localName == "table") {
loadTable(tag, cursor);
usedParagraph = false;
} else {
warnText << "KoTextLoader::loadBody unhandled table::" << localName;
}
}
processBody();
}
}
endBody();
}
rootCallChecker--;
// Here we put old endings after text insertion.
if (mode == PasteMode) {
QTextBlockFormat fmt = cursor.blockFormat();
oldSectionEndings = KoSectionUtils::sectionEndings(fmt);
KoSectionUtils::setSectionEndings(fmt, oldSectionEndings);
cursor.setBlockFormat(fmt);
}
cursor.endEditBlock();
KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager();
Q_UNUSED(textRangeManager);
//debugText << "text ranges::";
//Q_FOREACH (KoTextRange *range, textRangeManager->textRanges()) {
//debugText << range->id();
//}
if (!rootCallChecker) {
// Allow to move end bounds of sections with inserting text
KoTextDocument(cursor.block().document()).sectionModel()->allowMovingEndBound();
}
}
void KoTextLoader::loadParagraph(const KoXmlElement &element, QTextCursor &cursor)
{
// TODO use the default style name a default value?
const QString styleName = element.attributeNS(KoXmlNS::text, "style-name",
QString());
KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml);
Q_ASSERT(d->styleManager);
if (!paragraphStyle) {
// Either the paragraph has no style or the style-name could not be found.
// Fix up the paragraphStyle to be our default paragraph style in either case.
if (!styleName.isEmpty())
warnText << "paragraph style " << styleName << "not found - using default style";
paragraphStyle = d->styleManager->defaultParagraphStyle();
}
QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format
if (paragraphStyle && (cursor.position() == cursor.block().position())) {
QTextBlock block = cursor.block();
// Apply list style when loading a list but we don't have a list style
paragraphStyle->applyStyle(block, d->currentLists[d->currentListLevel - 1] && !d->currentListStyle);
// Clear the outline level property. If a default-outline-level was set, it should not
// be applied when loading a document, only on user action.
block.blockFormat().clearProperty(KoParagraphStyle::OutlineLevel);
}
// Some paragraph have id's defined which we need to store so that we can eg
// attach text animations to this specific paragraph later on
KoElementReference id;
id.loadOdf(element);
if (id.isValid() && d->shape) {
QTextBlock block = cursor.block();
KoTextBlockData data(block); // this sets the user data, so don't remove
d->context.addShapeSubItemId(d->shape, QVariant::fromValue(block.userData()), id.toString());
}
// attach Rdf to cursor.block()
// remember inline Rdf metadata -- if the xml-id is actually
// about rdf.
if (element.hasAttributeNS(KoXmlNS::xhtml, "property")
|| d->rdfIdList.contains(id.toString()))
{
QTextBlock block = cursor.block();
KoTextInlineRdf* inlineRdf =
new KoTextInlineRdf((QTextDocument*)block.document(), block);
if (inlineRdf->loadOdf(element)) {
KoTextInlineRdf::attach(inlineRdf, cursor);
}
else {
delete inlineRdf;
inlineRdf = 0;
}
}
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat()) << d->currentLists[d->currentListLevel - 1] << d->currentListStyle;
#endif
bool stripLeadingSpace = true;
loadSpan(element, cursor, &stripLeadingSpace);
QTextBlock block = cursor.block();
QString text = block.text();
if (text.length() == 0 || text.at(text.length()-1) == QChar(0x2028)) {
if (d->endCharStyle) {
QTextBlockFormat blockFormat = block.blockFormat();
blockFormat.setProperty(KoParagraphStyle::EndCharStyle, QVariant::fromValue< QSharedPointer<KoCharacterStyle> >(QSharedPointer<KoCharacterStyle>(d->endCharStyle->clone())));
cursor.setBlockFormat(blockFormat);
}
}
d->endCharStyle = 0;
cursor.setCharFormat(cf); // restore the cursor char format
}
void KoTextLoader::loadHeading(const KoXmlElement &element, QTextCursor &cursor)
{
Q_ASSERT(d->styleManager);
int level = qMax(-1, element.attributeNS(KoXmlNS::text, "outline-level", "-1").toInt());
// This will fallback to the default-outline-level applied by KoParagraphStyle
QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString());
QTextBlock block = cursor.block();
// Set the paragraph-style on the block
KoParagraphStyle *paragraphStyle = d->textSharedData->paragraphStyle(styleName, d->stylesDotXml);
if (!paragraphStyle) {
paragraphStyle = d->styleManager->defaultParagraphStyle();
}
if (paragraphStyle) {
// Apply list style when loading a list but we don't have a list style
paragraphStyle->applyStyle(block, (d->currentListLevel > 1) &&
d->currentLists[d->currentListLevel - 2] && !d->currentListStyle);
}
QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format
bool stripLeadingSpace = true;
loadSpan(element, cursor, &stripLeadingSpace);
cursor.setCharFormat(cf); // restore the cursor char format
if ((block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) && (level == -1)) {
level = block.blockFormat().property(KoParagraphStyle::OutlineLevel).toInt();
} else {
if (level == -1)
level = 1;
QTextBlockFormat blockFormat;
blockFormat.setProperty(KoParagraphStyle::OutlineLevel, level);
cursor.mergeBlockFormat(blockFormat);
}
if (element.hasAttributeNS(KoXmlNS::text, "is-list-header")) {
QTextBlockFormat blockFormat;
blockFormat.setProperty(KoParagraphStyle::IsListHeader, element.attributeNS(KoXmlNS::text, "is-list-header") == "true");
cursor.mergeBlockFormat(blockFormat);
}
//we are defining our default behaviour here
//Case 1: If text:outline-style is specified then we use the outline style to determine the numbering style
//Case 2: If text:outline-style is not specified then if the <text:h> element is inside a <text:list> then it is numbered
// otherwise it is not
KoListStyle *outlineStyle = d->styleManager->outlineStyle();
if (!outlineStyle) {
outlineStyle = d->styleManager->defaultOutlineStyle()->clone();
d->styleManager->setOutlineStyle(outlineStyle);
}
//if outline style is not specified and this is not inside a list then we do not number it
if (outlineStyle->styleId() == d->styleManager->defaultOutlineStyle()->styleId()) {
if (d->currentListLevel <= 1) {
QTextBlockFormat blockFormat;
blockFormat.setProperty(KoParagraphStyle::UnnumberedListItem, true);
cursor.mergeBlockFormat(blockFormat);
} else { //inside a list then take the numbering from the list style
int level = d->currentListLevel - 1;
KoListLevelProperties llp;
if (!d->currentListStyle->hasLevelProperties(level)) {
// Look if one of the lower levels are defined to we can copy over that level.
for(int i = level - 1; i >= 0; --i) {
if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) {
llp = d->currentLists[level - 1]->style()->levelProperties(i);
break;
}
}
} else {
llp = d->currentListStyle->levelProperties(level);
}
llp.setLevel(level);
outlineStyle->setLevelProperties(llp);
}
}
KoList *list = KoTextDocument(block.document()).headingList();
if (!list) {
list = d->list(block.document(), outlineStyle, false);
KoTextDocument(block.document()).setHeadingList(list);
}
list->setStyle(outlineStyle);
list->add(block, level);
// attach Rdf to cursor.block()
// remember inline Rdf metadata
KoElementReference id;
id.loadOdf(element);
if (element.hasAttributeNS(KoXmlNS::xhtml, "property")
|| d->rdfIdList.contains(id.toString())) {
QTextBlock block = cursor.block();
KoTextInlineRdf* inlineRdf =
new KoTextInlineRdf((QTextDocument*)block.document(), block);
if (inlineRdf->loadOdf(element)) {
KoTextInlineRdf::attach(inlineRdf, cursor);
}
else {
delete inlineRdf;
inlineRdf = 0;
}
}
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat());
#endif
}
void KoTextLoader::loadList(const KoXmlElement &element, QTextCursor &cursor)
{
const bool numberedParagraph = element.localName() == "numbered-paragraph";
QString styleName = element.attributeNS(KoXmlNS::text, "style-name", QString());
KoListStyle *listStyle = d->textSharedData->listStyle(styleName, d->stylesDotXml);
KoList *continuedList = 0;
int level;
if (d->currentLists[d->currentListLevel - 1] || d->currentListLevel == 1) {
d->currentLists[d->currentListLevel - 1] = 0;
} else {
d->currentLists[d->currentListLevel - 1] = d->currentLists[d->currentListLevel - 2];
}
if (element.hasAttributeNS(KoXmlNS::text, "continue-list")) {
if (d->xmlIdToListMap.contains(element.attributeNS(KoXmlNS::text, "continue-list", QString()))) {
continuedList = d->xmlIdToListMap.value(element.attributeNS(KoXmlNS::text, "continue-list", QString()));
}
} else {
//the ODF spec says that continue-numbering is considered only if continue-list is not specified
if (element.hasAttributeNS(KoXmlNS::text, "continue-numbering")) {
const QString continueNumbering = element.attributeNS(KoXmlNS::text, "continue-numbering", QString());
if (continueNumbering == "true") {
//since ODF spec says "and the numbering style of the preceding list is the same as the current list"
KoList *prevList = d->previousList(d->currentListLevel);
if (prevList && listStyle && prevList->style()->hasLevelProperties(d->currentListLevel)
&& listStyle->hasLevelProperties(d->currentListLevel)
&& (prevList->style()->levelProperties(d->currentListLevel).style() ==
listStyle->levelProperties(d->currentListLevel).style())) {
continuedList = prevList;
}
}
}
}
// TODO: get level from the style, if it has a style:list-level attribute (new in ODF-1.2)
if (numberedParagraph) {
if (element.hasAttributeNS(KoXmlNS::text, "list-id")) {
QString listId = element.attributeNS(KoXmlNS::text, "list-id");
if (d->numberedParagraphListId.contains(listId)) {
d->currentLists.fill(d->numberedParagraphListId.value(listId));
} else {
KoList *currentList = d->list(cursor.block().document(), listStyle, false);
d->currentLists.fill(currentList);
d->numberedParagraphListId.insert(listId, currentList);
}
} else {
d->currentLists.fill(d->list(cursor.block().document(), listStyle, true));
}
level = element.attributeNS(KoXmlNS::text, "level", "1").toInt();
d->currentListStyle = listStyle;
} else {
if (!listStyle)
listStyle = d->currentListStyle;
level = d->currentListLevel++;
KoList *currentList = d->currentLists[d->currentListLevel - 2];
if (!currentList) {
currentList = d->list(cursor.block().document(), listStyle, false);
currentList->setListContinuedFrom(continuedList);
d->currentLists[d->currentListLevel - 2] = currentList;
}
d->currentListStyle = listStyle;
}
if (element.hasAttributeNS(KoXmlNS::xml, "id")) {
d->xmlIdToListMap.insert(element.attributeNS(KoXmlNS::xml, "id", QString()), d->currentLists[d->currentListLevel - 2]);
}
if (level < 0 || level > 10) { // should not happen but if it does then we should not crash/assert
warnText << "Out of bounds list-level=" << level;
level = qBound(0, level, 10);
}
if (! numberedParagraph) {
d->setCurrentList(d->currentLists[d->currentListLevel - 2], level);
}
#ifdef KOOPENDOCUMENTLOADER_DEBUG
if (d->currentListStyle)
debugText << "styleName =" << styleName << "listStyle =" << d->currentListStyle->name()
<< "level =" << level << "hasLevelProperties =" << d->currentListStyle->hasLevelProperties(level)
//<<" style="<<props.style()<<" prefix="<<props.listItemPrefix()<<" suffix="<<props.listItemSuffix()
;
else
debugText << "styleName =" << styleName << " currentListStyle = 0";
#endif
KoXmlElement e;
QList<KoXmlElement> childElementsList;
QString generatedXmlString;
KoXmlDocument doc;
QXmlStreamReader reader;
for (KoXmlNode _node = element.firstChild(); !_node.isNull(); _node = _node.nextSibling()) {
if (!(e = _node.toElement()).isNull()) {
childElementsList.append(e);
}
}
// Iterate over list items and add them to the textlist
bool firstTime = true;
foreach (e, childElementsList) {
if (e.localName() != "removed-content") {
if (!firstTime && !numberedParagraph)
cursor.insertBlock(d->defaultBlockFormat, d->defaultCharFormat);
firstTime = false;
loadListItem(e, cursor, level);
}
}
if (numberedParagraph || --d->currentListLevel == 1) {
d->currentListStyle = 0;
d->currentLists.fill(0);
}
}
void KoTextLoader::loadListItem(const KoXmlElement &e, QTextCursor &cursor, int level)
{
bool numberedParagraph = e.parentNode().toElement().localName() == "numbered-paragraph";
if (e.isNull() || e.namespaceURI() != KoXmlNS::text)
return;
const bool listHeader = e.tagName() == "list-header";
if (!numberedParagraph && e.tagName() != "list-item" && !listHeader)
return;
QTextBlock current = cursor.block();
QTextBlockFormat blockFormat;
if (numberedParagraph) {
if (e.localName() == "p") {
loadParagraph(e, cursor);
} else if (e.localName() == "h") {
loadHeading(e, cursor);
}
blockFormat.setProperty(KoParagraphStyle::ListLevel, level);
} else {
loadBody(e, cursor);
}
if (!cursor.blockFormat().boolProperty(KoParagraphStyle::ForceDisablingList)) {
if (!current.textList()) {
if (!d->currentLists[level - 1]->style()->hasLevelProperties(level)) {
KoListLevelProperties llp;
// Look if one of the lower levels are defined to we can copy over that level.
for(int i = level - 1; i >= 0; --i) {
if(d->currentLists[level - 1]->style()->hasLevelProperties(i)) {
llp = d->currentLists[level - 1]->style()->levelProperties(i);
break;
}
}
llp.setLevel(level);
// TODO make the 10 configurable
llp.setIndent(level * 10.0);
d->currentLists[level - 1]->style()->setLevelProperties(llp);
}
d->currentLists[level - 1]->add(current, level);
}
if (listHeader)
blockFormat.setProperty(KoParagraphStyle::IsListHeader, true);
if (e.hasAttributeNS(KoXmlNS::text, "start-value")) {
int startValue = e.attributeNS(KoXmlNS::text, "start-value", QString()).toInt();
blockFormat.setProperty(KoParagraphStyle::ListStartValue, startValue);
}
// mark intermediate paragraphs as unnumbered items
QTextCursor c(current);
c.mergeBlockFormat(blockFormat);
while (c.block() != cursor.block()) {
c.movePosition(QTextCursor::NextBlock);
if (c.block().textList()) // a sublist
break;
blockFormat = c.blockFormat();
blockFormat.setProperty(listHeader ? KoParagraphStyle::IsListHeader : KoParagraphStyle::UnnumberedListItem, true);
c.setBlockFormat(blockFormat);
d->currentLists[level - 1]->add(c.block(), level);
}
}
debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat());
}
void KoTextLoader::loadSection(const KoXmlElement &sectionElem, QTextCursor &cursor)
{
KoSection *parent = d->sectionStack.empty() ? 0 : d->sectionStack.top();
KoSection *section = d->context.sectionModel()->createSection(cursor, parent);
if (!section->loadOdf(sectionElem, d->textSharedData, d->stylesDotXml)) {
delete section;
warnText << "Could not load section";
return;
}
d->sectionStack << section;
d->openingSections << section;
loadBody(sectionElem, cursor);
// Close the section on the last block of text we have loaded just now.
QTextBlockFormat format = cursor.block().blockFormat();
KoSectionUtils::setSectionEndings(format,
KoSectionUtils::sectionEndings(format) << d->context.sectionModel()->createSectionEnd(section));
d->sectionStack.pop();
cursor.setBlockFormat(format);
section->setKeepEndBound(true); // This bound should stop moving with new text
}
void KoTextLoader::loadNote(const KoXmlElement &noteElem, QTextCursor &cursor)
{
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (textObjectManager) {
QString className = noteElem.attributeNS(KoXmlNS::text, "note-class");
KoInlineNote *note = 0;
int position = cursor.position(); // need to store this as the following might move is
if (className == "footnote") {
note = new KoInlineNote(KoInlineNote::Footnote);
note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame());
} else {
note = new KoInlineNote(KoInlineNote::Endnote);
note->setMotherFrame(KoTextDocument(cursor.block().document()).auxillaryFrame());
}
if (note->loadOdf(noteElem, d->context)) {
cursor.setPosition(position); // restore the position before inserting the note
textObjectManager->insertInlineObject(cursor, note);
} else {
cursor.setPosition(position); // restore the position
delete note;
}
}
}
void KoTextLoader::loadCite(const KoXmlElement &noteElem, QTextCursor &cursor)
{
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (textObjectManager) {
//Now creating citation with default type KoInlineCite::Citation.
KoInlineCite *cite = new KoInlineCite(KoInlineCite::Citation);
// the manager is needed during loading so set it now
cite->setManager(textObjectManager);
if (cite->loadOdf(noteElem, d->context)) {
textObjectManager->insertInlineObject(cursor, cite);
} else {
delete cite;
}
}
}
void KoTextLoader::loadText(const QString &fulltext, QTextCursor &cursor,
bool *stripLeadingSpace, bool isLastNode)
{
QString text = normalizeWhitespace(fulltext, *stripLeadingSpace);
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << " <text> text=" << text << text.length() << *stripLeadingSpace;
#endif
if (!text.isEmpty()) {
// if present text ends with a space,
// we can remove the leading space in the next text
*stripLeadingSpace = text[text.length() - 1].isSpace();
cursor.insertText(text);
if (d->loadSpanLevel == 1 && isLastNode
&& cursor.position() > d->loadSpanInitialPos) {
QTextCursor tempCursor(cursor);
tempCursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor, 1); // select last char loaded
if (tempCursor.selectedText() == " " && *stripLeadingSpace) { // if it's a collapsed blankspace
tempCursor.removeSelectedText(); // remove it
}
}
}
}
void KoTextLoader::loadSpan(const KoXmlElement &element, QTextCursor &cursor, bool *stripLeadingSpace)
{
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "text-style:" << KoTextDebug::textAttributes(cursor.blockCharFormat());
#endif
Q_ASSERT(stripLeadingSpace);
if (d->loadSpanLevel++ == 0)
d->loadSpanInitialPos = cursor.position();
for (KoXmlNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) {
KoXmlElement ts = node.toElement();
const QString localName(ts.localName());
const bool isTextNS = ts.namespaceURI() == KoXmlNS::text;
const bool isDrawNS = ts.namespaceURI() == KoXmlNS::draw;
const bool isDr3dNS = ts.namespaceURI() == KoXmlNS::dr3d;
const bool isOfficeNS = ts.namespaceURI() == KoXmlNS::office;
//if (isOfficeNS)
debugText << "office:"<<isOfficeNS << localName;
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "load" << localName << *stripLeadingSpace << node.toText().data();
#endif
if (!(isTextNS && localName == "span")) {
d->endCharStyle = 0;
}
if (node.isText()) {
bool isLastNode = node.nextSibling().isNull();
loadText(node.toText().data(), cursor, stripLeadingSpace,
isLastNode);
} else if (isTextNS && localName == "span") { // text:span
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << " <span> localName=" << localName;
#endif
QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString());
QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format
KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml);
if (characterStyle) {
characterStyle->applyStyle(&cursor);
if (ts.firstChild().isNull()) {
// empty span so let's save the characterStyle for possible use at end of par
d->endCharStyle = characterStyle;
}
} else if (!styleName.isEmpty()) {
warnText << "character style " << styleName << " not found";
}
loadSpan(ts, cursor, stripLeadingSpace); // recurse
cursor.setCharFormat(cf); // restore the cursor char format
} else if (isTextNS && localName == "s") { // text:s
int howmany = 1;
if (ts.hasAttributeNS(KoXmlNS::text, "c")) {
howmany = ts.attributeNS(KoXmlNS::text, "c", QString()).toInt();
}
cursor.insertText(QString().fill(32, howmany));
*stripLeadingSpace = false;
} else if ( (isTextNS && localName == "note")) { // text:note
loadNote(ts, cursor);
} else if (isTextNS && localName == "bibliography-mark") { // text:bibliography-mark
loadCite(ts,cursor);
} else if (isTextNS && localName == "tab") { // text:tab
cursor.insertText("\t");
*stripLeadingSpace = false;
} else if (isTextNS && localName == "a") { // text:a
QString target = ts.attributeNS(KoXmlNS::xlink, "href");
QString styleName = ts.attributeNS(KoXmlNS::text, "style-name", QString());
QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format
if (!styleName.isEmpty()) {
KoCharacterStyle *characterStyle = d->textSharedData->characterStyle(styleName, d->stylesDotXml);
if (characterStyle) {
characterStyle->applyStyle(&cursor);
} else {
warnText << "character style " << styleName << " not found";
}
}
QTextCharFormat newCharFormat = cursor.charFormat();
newCharFormat.setAnchor(true);
newCharFormat.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Anchor);
newCharFormat.setAnchorHref(target);
cursor.setCharFormat(newCharFormat);
loadSpan(ts, cursor, stripLeadingSpace); // recurse
cursor.setCharFormat(cf); // restore the cursor char format
} else if (isTextNS && localName == "line-break") { // text:line-break
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << " <line-break> Node localName=" << localName;
#endif
cursor.insertText(QChar(0x2028));
*stripLeadingSpace = false;
} else if (isTextNS && localName == "soft-page-break") { // text:soft-page-break
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (textObjectManager) {
textObjectManager->insertInlineObject(cursor, new KoTextSoftPageBreak());
}
} else if (isTextNS && localName == "meta") {
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "loading a text:meta";
#endif
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (textObjectManager) {
const QTextDocument *document = cursor.block().document();
KoTextMeta* startmark = new KoTextMeta(document);
textObjectManager->insertInlineObject(cursor, startmark);
// Add inline Rdf here.
KoElementReference id;
id.loadOdf(ts);
if (ts.hasAttributeNS(KoXmlNS::xhtml, "property")
|| (id.isValid() && d->rdfIdList.contains(id.toString()))) {
KoTextInlineRdf* inlineRdf =
new KoTextInlineRdf((QTextDocument*)document, startmark);
if (inlineRdf->loadOdf(ts)) {
startmark->setInlineRdf(inlineRdf);
}
else {
delete inlineRdf;
inlineRdf = 0;
}
}
loadSpan(ts, cursor, stripLeadingSpace); // recurse
KoTextMeta* endmark = new KoTextMeta(document);
textObjectManager->insertInlineObject(cursor, endmark);
startmark->setEndBookmark(endmark);
}
}
// text:bookmark, text:bookmark-start and text:bookmark-end
else if (isTextNS && (localName == "bookmark" || localName == "bookmark-start" || localName == "bookmark-end")) {
KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager();
if (localName == "bookmark-end") {
KoBookmark *bookmark = textRangeManager->bookmarkManager()->bookmark(KoBookmark::createUniqueBookmarkName(textRangeManager->bookmarkManager(), ts.attribute("name"), true));
if (bookmark) {
bookmark->setRangeEnd(cursor.position());
}
} else {
KoBookmark *bookmark = new KoBookmark(cursor);
bookmark->setManager(textRangeManager);
if (textRangeManager && bookmark->loadOdf(ts, d->context)) {
textRangeManager->insert(bookmark);
}
else {
warnText << "Could not load bookmark";
delete bookmark;
}
}
} else if (isTextNS && localName == "bookmark-ref") {
QString bookmarkName = ts.attribute("ref-name");
QTextCharFormat cf = cursor.charFormat(); // store the current cursor char format
if (!bookmarkName.isEmpty()) {
QTextCharFormat linkCf(cf); // and copy it to alter it
linkCf.setAnchor(true);
linkCf.setProperty(KoCharacterStyle::AnchorType, KoCharacterStyle::Bookmark);
QStringList anchorName;
anchorName << bookmarkName;
linkCf.setAnchorHref('#'+ bookmarkName);
cursor.setCharFormat(linkCf);
}
// TODO add support for loading text:reference-format
loadSpan(ts, cursor, stripLeadingSpace); // recurse
cursor.setCharFormat(cf); // restore the cursor char format
}
// text:annotation and text:annotation-end
else if (isOfficeNS && (localName == "annotation" || localName == "annotation-end")) {
debugText << "------> annotation found" << localName;
KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager();
if (localName == "annotation-end") {
// If the tag is annotation-end, there should already be a KoAnnotation
// with the same name as this one. If so, just find it and set the end
// of the range to the current position.
KoAnnotation *annotation = textRangeManager->annotationManager()->annotation(KoAnnotation::createUniqueAnnotationName(textRangeManager->annotationManager(), ts.attribute("name"), true));
if (annotation) {
annotation->setRangeEnd(cursor.position());
}
} else {
// if the tag is "annotation" then create a KoAnnotation
// and an annotation shape and call loadOdf() in them.
KoAnnotation *annotation = new KoAnnotation(cursor);
annotation->setManager(textRangeManager);
if (textRangeManager && annotation->loadOdf(ts, d->context)) {
textRangeManager->insert(annotation);
KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(ts, d->context);
// Don't show it before it's being laid out.
//
// Also this hides it in all applications but
// those that have an annotation layout manager
// (currently: words, author).
shape->setVisible(false);
d->textSharedData->shapeInserted(shape, element, d->context);
annotation->setAnnotationShape(shape);
}
else {
warnText << "Could not load annotation";
delete annotation;
}
}
}
else if (isTextNS && localName == "number") { // text:number
/* ODF Spec, §4.1.1, Formatted Heading Numbering
If a heading has a numbering applied, the text of the formatted number can be included in a
<text:number> element. This text can be used by applications that do not support numbering of
headings, but it will be ignored by applications that support numbering. */
} else if (isTextNS && localName == "dde-connection") {
// TODO: load actual connection (or at least preserve it)
// For now: just load the text
for (KoXmlNode n = ts.firstChild(); !n.isNull(); n = n.nextSibling()) {
if (n.isText()) {
loadText(n.toText().data(), cursor, stripLeadingSpace, false);
}
}
} else if ((isDrawNS) && localName == "a") { // draw:a
loadShapeWithHyperLink(ts, cursor);
} else if (isDrawNS || isDr3dNS) {
loadShape(ts, cursor);
} else {
KoInlineObject *obj = KoInlineObjectRegistry::instance()->createFromOdf(ts, d->context);
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (obj && textObjectManager) {
KoVariableManager *varManager = textObjectManager->variableManager();
if (varManager) {
textObjectManager->insertInlineObject(cursor, obj);
// we need to update whitespace stripping here so we don't remove to many whitespaces.
// this is simplified as it assumes the first child it the text item but that should be the case
// most of the time with variables so it should be fine.
KoXmlNode child = ts.firstChild();
if (child.isText()) {
QString text = normalizeWhitespace(child.toText().data(), *stripLeadingSpace);
if (!text.isEmpty()) {
// if present text ends with a space,
// we can remove the leading space in the next text
*stripLeadingSpace = text[text.length() - 1].isSpace();
}
}
}
} else {
#if 0 //1.6:
bool handled = false;
// Check if it's a variable
KoVariable *var = context.variableCollection().loadOasisField(textDocument(), ts, context);
if (var) {
textData = "#"; // field placeholder
customItem = var;
handled = true;
}
if (!handled) {
handled = textDocument()->loadSpanTag(ts, context, this, pos, textData, customItem);
if (!handled) {
warnText << "Ignoring tag " << ts.tagName();
context.styleStack().restore();
continue;
}
}
#else
#ifdef KOOPENDOCUMENTLOADER_DEBUG
debugText << "Node '" << localName << "' unhandled";
#endif
}
#endif
}
}
--d->loadSpanLevel;
}
void KoTextLoader::loadTable(const KoXmlElement &tableElem, QTextCursor &cursor)
{
QTextTableFormat tableFormat;
QString tableStyleName = tableElem.attributeNS(KoXmlNS::table, "style-name", "");
if (!tableStyleName.isEmpty()) {
KoTableStyle *tblStyle = d->textSharedData->tableStyle(tableStyleName, d->stylesDotXml);
if (tblStyle)
tblStyle->applyStyle(tableFormat);
}
QString tableTemplateName = tableElem.attributeNS(KoXmlNS::table, "template-name", "");
if (! tableTemplateName.isEmpty()) {
if(KoTextTableTemplate *tableTemplate = d->styleManager->tableTemplate(tableTemplateName)) {
tableFormat.setProperty(KoTableStyle::TableTemplate, tableTemplate->styleId());
}
}
if (tableElem.attributeNS(KoXmlNS::table, "use-banding-columns-styles", "false") == "true") {
tableFormat.setProperty(KoTableStyle::UseBandingColumnStyles, true);
}
if (tableElem.attributeNS(KoXmlNS::table, "use-banding-rows-styles", "false") == "true") {
tableFormat.setProperty(KoTableStyle::UseBandingRowStyles, true);
}
if (tableElem.attributeNS(KoXmlNS::table, "use-first-column-styles", "false") == "true") {
tableFormat.setProperty(KoTableStyle::UseFirstColumnStyles, true);
}
if (tableElem.attributeNS(KoXmlNS::table, "use-first-row-styles", "false") == "true") {
tableFormat.setProperty(KoTableStyle::UseFirstRowStyles, true);
}
if (tableElem.attributeNS(KoXmlNS::table, "use-last-column-styles", "false") == "true") {
tableFormat.setProperty(KoTableStyle::UseLastColumnStyles, true);
}
if (tableElem.attributeNS(KoXmlNS::table, "use-last-row-styles", "false") == "true") {
tableFormat.setProperty(KoTableStyle::UseLastRowStyles, true);
}
// Let's try to figure out when to hide the current block
QTextBlock currentBlock = cursor.block();
QTextTable *outerTable = cursor.currentTable();
bool hide = cursor.position() == 0;
if (outerTable) {
QTextTableCell cell = outerTable->cellAt(cursor.position());
if (cursor.position() == cell.firstCursorPosition().position()) {
hide = true;
}
}
if (!hide) {
// Let's insert an extra block so that will be the one we hide instead
cursor.insertBlock(cursor.blockFormat(), cursor.blockCharFormat());
currentBlock = cursor.block();
}
if (tableElem.attributeNS(KoXmlNS::table, "protected", "false") == "true") {
tableFormat.setProperty(KoTableStyle::TableIsProtected, true);
}
QTextTable *tbl = cursor.insertTable(1, 1, tableFormat);
// 'Hide' the block before the table (possibly the extra block we just inserted)
QTextBlockFormat blockFormat;
//blockFormat.setFont(currentBlock.blockFormat().font());
QTextCursor tmpCursor(currentBlock);
blockFormat.setProperty(KoParagraphStyle::HiddenByTable, true);
QVariant masterStyle = tableFormat.property(KoTableStyle::MasterPageName);
if (!masterStyle.isNull()) {
// if table has a master page style property, copy it to block before table, because
// this block is a hidden block so let's make it belong to the same masterpage
// so we don't end up with a page break at the wrong place
blockFormat.setProperty(KoParagraphStyle::MasterPageName, masterStyle);
}
tmpCursor.setBlockFormat(blockFormat);
KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl);
int rows = 0;
int columns = 0;
QList<QRect> spanStore; //temporary list to store spans until the entire table have been created
KoXmlElement tblTag;
int headingRowCounter = 0;
QList<KoXmlElement> rowTags;
forEachElement(tblTag, tableElem) {
if (! tblTag.isNull()) {
const QString tblLocalName = tblTag.localName();
if (tblTag.namespaceURI() == KoXmlNS::table) {
if (tblLocalName == "table-column") {
loadTableColumn(tblTag, tbl, columns);
} else if (tblLocalName == "table-columns") {
KoXmlElement e;
forEachElement(e, tblTag) {
if (e.localName() == "table-column") {
loadTableColumn(e, tbl, columns);
}
}
} else if (tblLocalName == "table-row") {
loadTableRow(tblTag, tbl, spanStore, cursor, rows);
} else if (tblLocalName == "table-rows") {
KoXmlElement subTag;
forEachElement(subTag, tblTag) {
if (!subTag.isNull()) {
if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) {
loadTableRow(subTag, tbl, spanStore, cursor, rows);
}
}
}
} else if (tblLocalName == "table-header-rows") {
KoXmlElement subTag;
forEachElement(subTag, tblTag) {
if (!subTag.isNull()) {
if ((subTag.namespaceURI() == KoXmlNS::table) && (subTag.localName() == "table-row")) {
headingRowCounter++;
loadTableRow(subTag, tbl, spanStore, cursor, rows);
}
}
}
}
}
}
}
if (headingRowCounter > 0) {
QTextTableFormat fmt = tbl->format();
fmt.setProperty(KoTableStyle::NumberHeadingRows, headingRowCounter);
tbl->setFormat(fmt);
}
// Finally create spans
Q_FOREACH (const QRect &span, spanStore) {
tbl->mergeCells(span.y(), span.x(), span.height(), span.width()); // for some reason Qt takes row, column
}
cursor = tbl->lastCursorPosition();
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
}
void KoTextLoader::loadTableColumn(const KoXmlElement &tblTag, QTextTable *tbl, int &columns)
{
KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl);
int rows = tbl->rows();
int repeatColumn = tblTag.attributeNS(KoXmlNS::table, "number-columns-repeated", "1").toInt();
QString columnStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", "");
if (!columnStyleName.isEmpty()) {
KoTableColumnStyle *columnStyle = d->textSharedData->tableColumnStyle(columnStyleName, d->stylesDotXml);
if (columnStyle) {
for (int c = columns; c < columns + repeatColumn; c++) {
tcarManager.setColumnStyle(c, *columnStyle);
}
}
}
QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", "");
if (!defaultCellStyleName.isEmpty()) {
KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml);
for (int c = columns; c < columns + repeatColumn; c++) {
tcarManager.setDefaultColumnCellStyle(c, cellStyle);
}
}
columns = columns + repeatColumn;
if (rows > 0)
tbl->resize(rows, columns);
else
tbl->resize(1, columns);
}
void KoTextLoader::loadTableRow(const KoXmlElement &tblTag, QTextTable *tbl, QList<QRect> &spanStore, QTextCursor &cursor, int &rows)
{
KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl);
int columns = tbl->columns();
QString rowStyleName = tblTag.attributeNS(KoXmlNS::table, "style-name", "");
if (!rowStyleName.isEmpty()) {
KoTableRowStyle *rowStyle = d->textSharedData->tableRowStyle(rowStyleName, d->stylesDotXml);
if (rowStyle) {
tcarManager.setRowStyle(rows, *rowStyle);
}
}
QString defaultCellStyleName = tblTag.attributeNS(KoXmlNS::table, "default-cell-style-name", "");
if (!defaultCellStyleName.isEmpty()) {
KoTableCellStyle *cellStyle = d->textSharedData->tableCellStyle(defaultCellStyleName, d->stylesDotXml);
tcarManager.setDefaultRowCellStyle(rows, cellStyle);
}
rows++;
if (columns > 0)
tbl->resize(rows, columns);
else
tbl->resize(rows, 1);
// Added a row
int currentCell = 0;
KoXmlElement rowTag;
forEachElement(rowTag, tblTag) {
if (!rowTag.isNull()) {
const QString rowLocalName = rowTag.localName();
if (rowTag.namespaceURI() == KoXmlNS::table) {
if (rowLocalName == "table-cell") {
loadTableCell(rowTag, tbl, spanStore, cursor, currentCell);
currentCell++;
} else if (rowLocalName == "covered-table-cell") {
currentCell++;
}
}
}
}
}
void KoTextLoader::loadTableCell(const KoXmlElement &rowTag, QTextTable *tbl, QList<QRect> &spanStore, QTextCursor &cursor, int &currentCell)
{
KoTableColumnAndRowStyleManager tcarManager = KoTableColumnAndRowStyleManager::getManager(tbl);
const int currentRow = tbl->rows() - 1;
QTextTableCell cell = tbl->cellAt(currentRow, currentCell);
// store spans until entire table have been loaded
int rowsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-rows-spanned", "1").toInt();
int columnsSpanned = rowTag.attributeNS(KoXmlNS::table, "number-columns-spanned", "1").toInt();
spanStore.append(QRect(currentCell, currentRow, columnsSpanned, rowsSpanned));
if (cell.isValid()) {
QString cellStyleName = rowTag.attributeNS(KoXmlNS::table, "style-name", "");
KoTableCellStyle *cellStyle = 0;
if (!cellStyleName.isEmpty()) {
cellStyle = d->textSharedData->tableCellStyle(cellStyleName, d->stylesDotXml);
} else if (tcarManager.defaultRowCellStyle(currentRow)) {
cellStyle = tcarManager.defaultRowCellStyle(currentRow);
} else if (tcarManager.defaultColumnCellStyle(currentCell)) {
cellStyle = tcarManager.defaultColumnCellStyle(currentCell);
}
if (cellStyle)
cellStyle->applyStyle(cell);
QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
if (rowTag.attributeNS(KoXmlNS::table, "protected", "false") == "true") {
cellFormat.setProperty(KoTableCellStyle::CellIsProtected, true);
}
cell.setFormat(cellFormat);
// handle inline Rdf
// rowTag is the current table cell.
KoElementReference id;
id.loadOdf(rowTag);
if (rowTag.hasAttributeNS(KoXmlNS::xhtml, "property") || d->rdfIdList.contains(id.toString())) {
KoTextInlineRdf* inlineRdf = new KoTextInlineRdf((QTextDocument*)cursor.block().document(),cell);
if (inlineRdf->loadOdf(rowTag)) {
QTextTableCellFormat cellFormat = cell.format().toTableCellFormat();
cellFormat.setProperty(KoTableCellStyle::InlineRdf,QVariant::fromValue(inlineRdf));
cell.setFormat(cellFormat);
}
else {
delete inlineRdf;
inlineRdf = 0;
}
}
cursor = cell.firstCursorPosition();
loadBody(rowTag, cursor);
}
}
void KoTextLoader::loadShapeWithHyperLink(const KoXmlElement &element, QTextCursor& cursor)
{
// get the hyperlink
QString hyperLink = element.attributeNS(KoXmlNS::xlink, "href");
KoShape *shape = 0;
//load the shape for hyperlink
KoXmlNode node = element.firstChild();
if (!node.isNull()) {
KoXmlElement ts = node.toElement();
shape = loadShape(ts, cursor);
if (shape) {
shape->setHyperLink(hyperLink);
}
}
}
KoShape *KoTextLoader::loadShape(const KoXmlElement &element, QTextCursor &cursor)
{
KoShape *shape = KoShapeRegistry::instance()->createShapeFromOdf(element, d->context);
if (!shape) {
debugText << "shape '" << element.localName() << "' unhandled";
return 0;
}
KoShapeAnchor *anchor = new KoShapeAnchor(shape);
anchor->loadOdf(element, d->context);
- shape->setAnchor(anchor);
+ // shape->setAnchor(anchor); // undefined in Krita!
d->textSharedData->shapeInserted(shape, element, d->context);
// page anchored shapes are handled differently
if (anchor->anchorType() == KoShapeAnchor::AnchorPage) {
// nothing else to do
} else if (anchor->anchorType() == KoShapeAnchor::AnchorAsCharacter) {
KoAnchorInlineObject *anchorObject = new KoAnchorInlineObject(anchor);
KoInlineTextObjectManager *textObjectManager = KoTextDocument(cursor.block().document()).inlineTextObjectManager();
if (textObjectManager) {
textObjectManager->insertInlineObject(cursor, anchorObject);
}
} else { // KoShapeAnchor::AnchorToCharacter or KoShapeAnchor::AnchorParagraph
KoAnchorTextRange *anchorRange = new KoAnchorTextRange(anchor, cursor);
KoTextRangeManager *textRangeManager = KoTextDocument(cursor.block().document()).textRangeManager();
anchorRange->setManager(textRangeManager);
textRangeManager->insert(anchorRange);
}
return shape;
}
void KoTextLoader::loadTableOfContents(const KoXmlElement &element, QTextCursor &cursor)
{
// make sure that the tag is table-of-content
Q_ASSERT(element.tagName() == "table-of-content");
QTextBlockFormat tocFormat;
// for "meta-information" about the TOC we use this class
KoTableOfContentsGeneratorInfo *info = new KoTableOfContentsGeneratorInfo();
// to store the contents we use an extrafor "meta-information" about the TOC we use this class
QTextDocument *tocDocument = new QTextDocument();
KoTextDocument(tocDocument).setStyleManager(d->styleManager);
KoTextDocument(tocDocument).setTextRangeManager(new KoTextRangeManager);
info->m_name = element.attribute("name");
info->m_styleName = element.attribute("style-name");
KoXmlElement e;
forEachElement(e, element) {
if (e.isNull() || e.namespaceURI() != KoXmlNS::text) {
continue;
}
if (e.localName() == "table-of-content-source" && e.namespaceURI() == KoXmlNS::text) {
info->loadOdf(d->textSharedData, e);
// uncomment to see what has been loaded
//info.tableOfContentData()->dump();
tocFormat.setProperty(KoParagraphStyle::TableOfContentsData, QVariant::fromValue<KoTableOfContentsGeneratorInfo*>(info) );
tocFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue<QTextDocument*>(tocDocument) );
cursor.insertBlock(tocFormat);
// We'll just try to find displayable elements and add them as paragraphs
} else if (e.localName() == "index-body") {
QTextCursor cursorFrame = tocDocument->rootFrame()->lastCursorPosition();
bool firstTime = true;
KoXmlElement p;
forEachElement(p, e) {
// All elem will be "p" instead of the title, which is particular
if (p.isNull() || p.namespaceURI() != KoXmlNS::text)
continue;
if (!firstTime) {
// use empty formats to not inherit from the prev parag
QTextBlockFormat bf;
QTextCharFormat cf;
cursorFrame.insertBlock(bf, cf);
}
firstTime = false;
QTextBlock current = cursorFrame.block();
QTextBlockFormat blockFormat;
if (p.localName() == "p") {
loadParagraph(p, cursorFrame);
} else if (p.localName() == "index-title") {
loadBody(p, cursorFrame);
}
QTextCursor c(current);
c.mergeBlockFormat(blockFormat);
}
}// index-body
}
}
void KoTextLoader::loadBibliography(const KoXmlElement &element, QTextCursor &cursor)
{
// make sure that the tag is bibliography
Q_ASSERT(element.tagName() == "bibliography");
QTextBlockFormat bibFormat;
// for "meta-information" about the bibliography we use this class
KoBibliographyInfo *info = new KoBibliographyInfo();
QTextDocument *bibDocument = new QTextDocument();
KoTextDocument(bibDocument).setStyleManager(d->styleManager);
KoTextDocument(bibDocument).setTextRangeManager(new KoTextRangeManager);
info->m_name = element.attribute("name");
info->m_styleName = element.attribute("style-name");
KoXmlElement e;
forEachElement(e, element) {
if (e.isNull() || e.namespaceURI() != KoXmlNS::text) {
continue;
}
if (e.localName() == "bibliography-source" && e.namespaceURI() == KoXmlNS::text) {
info->loadOdf(d->textSharedData, e);
bibFormat.setProperty(KoParagraphStyle::BibliographyData, QVariant::fromValue<KoBibliographyInfo*>(info));
bibFormat.setProperty(KoParagraphStyle::GeneratedDocument, QVariant::fromValue<QTextDocument*>(bibDocument));
cursor.insertBlock(bibFormat);
// We'll just try to find displayable elements and add them as paragraphs
} else if (e.localName() == "index-body") {
QTextCursor cursorFrame = bibDocument->rootFrame()->lastCursorPosition();
bool firstTime = true;
KoXmlElement p;
forEachElement(p, e) {
// All elem will be "p" instead of the title, which is particular
if (p.isNull() || p.namespaceURI() != KoXmlNS::text)
continue;
if (!firstTime) {
// use empty formats to not inherit from the prev parag
QTextBlockFormat bf;
QTextCharFormat cf;
cursorFrame.insertBlock(bf, cf);
}
firstTime = false;
QTextBlock current = cursorFrame.block();
QTextBlockFormat blockFormat;
if (p.localName() == "p") {
loadParagraph(p, cursorFrame);
} else if (p.localName() == "index-title") {
loadBody(p, cursorFrame);
}
QTextCursor c(current);
c.mergeBlockFormat(blockFormat);
}
}// index-body
}
}
void KoTextLoader::startBody(int total)
{
d->bodyProgressTotal += total;
}
void KoTextLoader::processBody()
{
d->bodyProgressValue++;
if (d->progressTime.elapsed() >= d->nextProgressReportMs) { // update based on elapsed time, don't saturate the queue
d->nextProgressReportMs = d->progressTime.elapsed() + 333; // report 3 times per second
Q_ASSERT(d->bodyProgressTotal > 0);
const int percent = d->bodyProgressValue * 100 / d->bodyProgressTotal;
emit sigProgress(percent);
}
}
void KoTextLoader::endBody()
{
}
diff --git a/plugins/flake/textshape/textlayout/KoTextShapeData.cpp b/plugins/flake/textshape/textlayout/KoTextShapeData.cpp
index 6491406ea1..5dcd83c70e 100644
--- a/plugins/flake/textshape/textlayout/KoTextShapeData.cpp
+++ b/plugins/flake/textshape/textlayout/KoTextShapeData.cpp
@@ -1,392 +1,402 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2009-2010 Thomas Zander <zander@kde.org>
* Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
* Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
* Copyright (C) 2011 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 "KoTextShapeData.h"
#include <KoTextShapeDataBase.h>
#include <KoTextShapeDataBase_p.h>
#include "KoTextDocument.h"
#include <KoTextEditor.h>
#include "styles/KoStyleManager.h"
#include "styles/KoParagraphStyle.h"
#include <KoTextLayoutRootArea.h>
#include <QTextDocument>
#include <QTextBlock>
#include <QTextCursor>
#include <KoGenStyle.h>
#include <KoOdfLoadingContext.h>
#include <KoOdfStylesReader.h>
#include <KoShapeLoadingContext.h>
#include <KoShapeSavingContext.h>
#include <KoDocumentRdfBase.h>
#include <KoStyleStack.h>
#include <KoShape.h>
#include <KoUnit.h>
#include <KoXmlNS.h>
#include "KoTextPage.h"
#include "opendocument/KoTextLoader.h"
#include "opendocument/KoTextWriter.h"
#include <TextLayoutDebug.h>
class KoTextShapeDataPrivate : public KoTextShapeDataBasePrivate
{
public:
KoTextShapeDataPrivate()
- : ownsDocument(true)
- , topPadding(0)
+ : topPadding(0)
, leftPadding(0)
, rightPadding(0)
, bottomPadding(0)
, direction(KoText::AutoDirection)
- , textpage(0)
, rootArea(0)
- , paragraphStyle(0)
+ {
+ }
+
+ KoTextShapeDataPrivate(const KoTextShapeDataPrivate &rhs)
+ : KoTextShapeDataBasePrivate(rhs),
+ topPadding(rhs.topPadding),
+ leftPadding(rhs.leftPadding),
+ rightPadding(rhs.rightPadding),
+ bottomPadding(rhs.bottomPadding),
+ direction(rhs.direction),
+ rootArea(rhs.rootArea), // WARNING: root area becomes shared via raw pointer!
+ paragraphStyle(rhs.paragraphStyle->clone())
{
}
~KoTextShapeDataPrivate() override
{
- if (ownsDocument) {
- delete document;
- }
- delete textpage;
- delete paragraphStyle;
}
- bool ownsDocument;
qreal topPadding;
qreal leftPadding;
qreal rightPadding;
qreal bottomPadding;
KoText::Direction direction;
- KoTextPage *textpage;
KoTextLayoutRootArea *rootArea;
- KoParagraphStyle *paragraphStyle; // the paragraph style of the shape (part of the graphic style)
+ QScopedPointer<KoParagraphStyle> paragraphStyle; // the paragraph style of the shape (part of the graphic style)
};
KoTextShapeData::KoTextShapeData()
- : KoTextShapeDataBase(*(new KoTextShapeDataPrivate()))
+ : KoTextShapeDataBase(new KoTextShapeDataPrivate())
+{
+ setDocument(new QTextDocument);
+}
+
+KoTextShapeData::KoTextShapeData(KoTextShapeDataPrivate *dd)
+ : KoTextShapeDataBase(dd)
{
- setDocument(new QTextDocument, true);
}
KoTextShapeData::~KoTextShapeData()
{
}
-void KoTextShapeData::setDocument(QTextDocument *document, bool transferOwnership)
+KoShapeUserData *KoTextShapeData::clone() const
+{
+ Q_D(const KoTextShapeData);
+ return new KoTextShapeData(new KoTextShapeDataPrivate(*d));
+}
+
+void KoTextShapeData::setDocument(QTextDocument *document)
{
Q_D(KoTextShapeData);
Q_ASSERT(document);
- if (d->ownsDocument && document != d->document) {
- delete d->document;
- }
- d->ownsDocument = transferOwnership;
// The following avoids the normal case where the glyph metrices are rounded to integers and
// hinted to the screen by freetype, which you of course don't want for WYSIWYG
if (! document->useDesignMetrics())
document->setUseDesignMetrics(true);
KoTextDocument kodoc(document);
if (document->isEmpty() && !document->firstBlock().blockFormat().hasProperty(KoParagraphStyle::StyleId)) { // apply app default style for first parag
KoStyleManager *sm = kodoc.styleManager();
if (sm) {
KoParagraphStyle *defaultStyle = sm->defaultParagraphStyle();
if (defaultStyle) {
QTextBlock block = document->begin();
defaultStyle->applyStyle(block);
}
}
}
// After setting the document (even if not changing it) we need to explicitly set the root area
// to 0. Otherwise crashes may occur when inserting textshape in words (or resetting document)
d->rootArea = 0;
- if (d->document == document)
- return;
- d->document = document;
+ if (d->document.data() != document) {
+ d->document.reset(document);
- if (kodoc.textEditor() == 0)
- kodoc.setTextEditor(new KoTextEditor(d->document));
+ if (kodoc.textEditor() == 0) {
+ kodoc.setTextEditor(new KoTextEditor(d->document.data()));
+ }
+ }
}
qreal KoTextShapeData::documentOffset() const
{
Q_D(const KoTextShapeData);
if (d->rootArea) {
KoBorder *border = d->rootArea->associatedShape()->border();
if (border) {
return d->rootArea->top() - topPadding() - border->borderWidth(KoBorder::TopBorder);
} else {
return d->rootArea->top() - topPadding();
}
} else {
return 0.0;
}
}
void KoTextShapeData::setDirty()
{
Q_D(KoTextShapeData);
if (d->rootArea) {
d->rootArea->setDirty();
}
}
bool KoTextShapeData::isDirty() const
{
Q_D(const KoTextShapeData);
if (d->rootArea) {
return d->rootArea->isDirty();
}
return true;
}
void KoTextShapeData::setPageDirection(KoText::Direction direction)
{
Q_D(KoTextShapeData);
d->direction = direction;
}
KoText::Direction KoTextShapeData::pageDirection() const
{
Q_D(const KoTextShapeData);
return d->direction;
}
void KoTextShapeData::setRootArea(KoTextLayoutRootArea *rootArea)
{
Q_D(KoTextShapeData);
d->rootArea = rootArea;
}
KoTextLayoutRootArea *KoTextShapeData::rootArea()
{
Q_D(const KoTextShapeData);
return d->rootArea;
}
void KoTextShapeData::setLeftPadding(qreal padding)
{
Q_D(KoTextShapeData);
d->leftPadding = padding;
}
qreal KoTextShapeData::leftPadding() const
{
Q_D(const KoTextShapeData);
return d->leftPadding;
}
void KoTextShapeData::setTopPadding(qreal padding)
{
Q_D(KoTextShapeData);
d->topPadding = padding;
}
qreal KoTextShapeData::topPadding() const
{
Q_D(const KoTextShapeData);
return d->topPadding;
}
void KoTextShapeData::setRightPadding(qreal padding)
{
Q_D(KoTextShapeData);
d->rightPadding = padding;
}
qreal KoTextShapeData::rightPadding() const
{
Q_D(const KoTextShapeData);
return d->rightPadding;
}
void KoTextShapeData::setBottomPadding(qreal padding)
{
Q_D(KoTextShapeData);
d->bottomPadding = padding;
}
qreal KoTextShapeData::bottomPadding() const
{
Q_D(const KoTextShapeData);
return d->bottomPadding;
}
void KoTextShapeData::setPadding(qreal padding)
{
setLeftPadding(padding);
setTopPadding(padding);
setRightPadding(padding);
setBottomPadding(padding);
}
bool KoTextShapeData::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context, KoDocumentRdfBase *rdfData, KoShape *shape)
{
Q_UNUSED(rdfData);
KoTextLoader loader(context, shape);
QTextCursor cursor(document());
loader.loadBody(element, cursor); // now let's load the body from the ODF KoXmlElement.
KoTextEditor *editor = KoTextDocument(document()).textEditor();
if (editor) { // at one point we have to get the position from the odf doc instead.
editor->setPosition(0);
}
return true;
}
void KoTextShapeData::saveOdf(KoShapeSavingContext &context, KoDocumentRdfBase *rdfData, int from, int to) const
{
Q_D(const KoTextShapeData);
- KoTextWriter::saveOdf(context, rdfData, d->document, from, to);
+ KoTextWriter::saveOdf(context, rdfData, d->document.data(), from, to);
}
void KoTextShapeData::loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context)
{
Q_D(KoTextShapeData);
// load the (text) style of the frame
const KoXmlElement *style = 0;
if (element.hasAttributeNS(KoXmlNS::draw, "style-name")) {
style = context.odfLoadingContext().stylesReader().findStyle(
element.attributeNS(KoXmlNS::draw, "style-name"), "graphic",
context.odfLoadingContext().useStylesAutoStyles());
if (!style) {
warnTextLayout << "graphic style not found:" << element.attributeNS(KoXmlNS::draw, "style-name");
}
}
if (element.hasAttributeNS(KoXmlNS::presentation, "style-name")) {
style = context.odfLoadingContext().stylesReader().findStyle(
element.attributeNS(KoXmlNS::presentation, "style-name"), "presentation",
context.odfLoadingContext().useStylesAutoStyles());
if (!style) {
warnTextLayout << "presentation style not found:" << element.attributeNS(KoXmlNS::presentation, "style-name");
}
}
if (style) {
KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
styleStack.save();
context.odfLoadingContext().addStyles(style, style->attributeNS(KoXmlNS::style, "family", "graphic").toLocal8Bit().constData()); // Load all parents
styleStack.setTypeProperties("graphic");
// Spacing (padding)
const QString paddingLeft(styleStack.property(KoXmlNS::fo, "padding-left" ));
if (!paddingLeft.isEmpty()) {
setLeftPadding(KoUnit::parseValue(paddingLeft));
}
const QString paddingRight(styleStack.property(KoXmlNS::fo, "padding-right" ));
if (!paddingRight.isEmpty()) {
setRightPadding(KoUnit::parseValue(paddingRight));
}
const QString paddingTop(styleStack.property(KoXmlNS::fo, "padding-top" ));
if (!paddingTop.isEmpty()) {
setTopPadding(KoUnit::parseValue(paddingTop));
}
const QString paddingBottom(styleStack.property(KoXmlNS::fo, "padding-bottom" ));
if (!paddingBottom.isEmpty()) {
setBottomPadding(KoUnit::parseValue(paddingBottom));
}
const QString padding(styleStack.property(KoXmlNS::fo, "padding"));
if (!padding.isEmpty()) {
setPadding(KoUnit::parseValue(padding));
}
styleStack.restore();
QString family = style->attributeNS(KoXmlNS::style, "family", "graphic");
KoParagraphStyle *defaultStyle = 0;
const KoXmlElement *dstyle = context.odfLoadingContext().stylesReader().defaultStyle(family);
if (dstyle) {
defaultStyle = new KoParagraphStyle();
defaultStyle->loadOdf(dstyle, context);
}
// graphic styles don't support inheritance yet therefor some additional work is needed here.
QList<KoParagraphStyle *> paragraphStyles;
while (style) {
KoParagraphStyle *pStyle = new KoParagraphStyle();
pStyle->loadOdf(style, context);
if (!paragraphStyles.isEmpty()) {
paragraphStyles.last()->setParentStyle(pStyle);
}
paragraphStyles.append(pStyle);
QString family = style->attributeNS(KoXmlNS::style, "family", "graphic");
style = context.odfLoadingContext().stylesReader().findStyle(
style->attributeNS(KoXmlNS::style, "parent-style-name"), family.toLocal8Bit().constData(),
context.odfLoadingContext().useStylesAutoStyles());
}
// rather than setting default style and apply to block we just set a final parent
paragraphStyles.last()->setParentStyle(defaultStyle);
QTextDocument *document = this->document();
QTextCursor cursor(document);
QTextBlockFormat format;
paragraphStyles.first()->applyStyle(format);
cursor.setBlockFormat(format);
QTextCharFormat cformat;
paragraphStyles.first()->KoCharacterStyle::applyStyle(cformat);
cursor.setCharFormat(cformat);
cursor.setBlockCharFormat(cformat);
- d->paragraphStyle = new KoParagraphStyle(format, cformat);
+ d->paragraphStyle.reset(new KoParagraphStyle(format, cformat));
qDeleteAll(paragraphStyles);
delete defaultStyle;
}
}
void KoTextShapeData::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const
{
if ((leftPadding() == rightPadding()) && (topPadding() == bottomPadding()) && (rightPadding() == topPadding())) {
style.addPropertyPt("fo:padding", leftPadding(), KoGenStyle::GraphicType);
} else {
if (leftPadding()) {
style.addPropertyPt("fo:padding-left", leftPadding(), KoGenStyle::GraphicType);
}
if (rightPadding()) {
style.addPropertyPt("fo:padding-right", rightPadding(), KoGenStyle::GraphicType);
}
if (topPadding()) {
style.addPropertyPt("fo:padding-top", topPadding(), KoGenStyle::GraphicType);
}
if (bottomPadding()) {
style.addPropertyPt("fo:padding-bottom", bottomPadding(), KoGenStyle::GraphicType);
}
}
Q_D(const KoTextShapeData);
if (d->paragraphStyle) {
d->paragraphStyle->saveOdf(style, context);
}
}
diff --git a/plugins/flake/textshape/textlayout/KoTextShapeData.h b/plugins/flake/textshape/textlayout/KoTextShapeData.h
index 3817dd7050..379ac57ffd 100644
--- a/plugins/flake/textshape/textlayout/KoTextShapeData.h
+++ b/plugins/flake/textshape/textlayout/KoTextShapeData.h
@@ -1,142 +1,147 @@
/* This file is part of the KDE project
* Copyright (C) 2006, 2009-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 KOTEXTSHAPEDATA_H
#define KOTEXTSHAPEDATA_H
#include "KoText.h"
#include "kritatextlayout_export.h"
#include <KoTextShapeDataBase.h>
#include <KoXmlReaderForward.h>
class QTextDocument;
class KoShapeLoadingContext;
class KoShapeSavingContext;
class KoTextShapeDataPrivate;
class KoDocumentRdfBase;
class KoTextLayoutRootArea;
/**
* The data store that is held by each TextShape instance.
* This is a separate object to allow Words proper to use this class' API and
* access the internals of the text shape.
*
* This class holds a QTextDocument pointer and is built so multiple shapes (and thus
* multiple instances of this shape data) can share one QTextDocument by providing a
* different view on (a different part of) the QTextDocument.
*/
class KRITATEXTLAYOUT_EXPORT KoTextShapeData : public KoTextShapeDataBase
{
Q_OBJECT
public:
/// constructor
KoTextShapeData();
virtual ~KoTextShapeData();
+ KoShapeUserData* clone() const override;
+
/**
* Replace the QTextDocument this shape will render.
* @param document the new document. If there was an old document owned, it will be deleted.
* @param transferOwnership if true then the document will be considered the responsibility
* of this data and the doc will be deleted when this shapeData dies.
*/
- void setDocument(QTextDocument *document, bool transferOwnership = true);
+ void setDocument(QTextDocument *document);
/**
* return the amount of points into the document (y) this shape will display.
*/
qreal documentOffset() const;
/// mark shape as dirty triggering a re-layout of its text.
void setDirty();
/// return if the shape is marked dirty and its text content needs to be relayout
bool isDirty() const;
/// Set the rootArea that is associated to the textshape
void setRootArea(KoTextLayoutRootArea *rootArea);
/// the rootArea that is associated to the textshape
KoTextLayoutRootArea *rootArea();
void setLeftPadding(qreal padding);
qreal leftPadding() const;
void setTopPadding(qreal padding);
qreal topPadding() const;
void setRightPadding(qreal padding);
qreal rightPadding() const;
void setBottomPadding(qreal padding);
qreal bottomPadding() const;
void setPadding(qreal padding);
/**
* Load the TextShape from ODF.
*
* @see the @a TextShape::loadOdf() method which calls this method.
* @see the @a KoTextLoader::loadBody() method which got called by this method
* to load the ODF.
*/
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context, KoDocumentRdfBase *rdfData, KoShape *shape = 0);
/**
* Load the TextShape from ODF.
* Overloaded method provided for your convenience.
*/
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) {
return loadOdf(element, context, 0);
}
/**
* Store the TextShape data as ODF.
* @see TextShape::saveOdf()
*/
void saveOdf(KoShapeSavingContext &context, KoDocumentRdfBase *rdfData, int from = 0, int to = -1) const;
/**
* Store the TextShape data as ODF.
* Overloaded method provided for your convenience.
*/
virtual void saveOdf(KoShapeSavingContext &context, int from = 0, int to = -1) const {
saveOdf(context, 0, from, to);
}
// reimplemented
virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context);
// reimplemented
virtual void saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const;
/**
* Set the page direction.
* The page direction will determine behavior on the insertion of new text and those
* new paragraphs default direction.
*/
void setPageDirection(KoText::Direction direction);
/**
* Return the direction set on the page.
* The page direction will determine behavior on the insertion of new text and those
* new paragraphs default direction.
*/
KoText::Direction pageDirection() const;
+private:
+ KoTextShapeData(KoTextShapeDataPrivate *dd);
+
private:
Q_DECLARE_PRIVATE(KoTextShapeData)
};
#endif
diff --git a/plugins/flake/vectorshape/VectorTool.cpp b/plugins/flake/vectorshape/VectorTool.cpp
index 3d245c1cc5..026cfffc4a 100644
--- a/plugins/flake/vectorshape/VectorTool.cpp
+++ b/plugins/flake/vectorshape/VectorTool.cpp
@@ -1,116 +1,117 @@
/* This file is part of the KDE project
Copyright 2007 Montel Laurent <montel@kde.org>
Copyright 2011 Boudewijn Rempt <boud@valdyas.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "VectorTool.h"
#include "VectorShape.h"
#include "ChangeVectorDataCommand.h"
#include <QToolButton>
#include <QGridLayout>
#include <QDesktopServices>
#include <klocalizedstring.h>
#include <KoFileDialog.h>
#include <KoIcon.h>
#include <KoCanvasBase.h>
#include <KoImageCollection.h>
#include <KoSelection.h>
#include <KoShapeManager.h>
#include <KoPointerEvent.h>
VectorTool::VectorTool(KoCanvasBase *canvas)
: KoToolBase(canvas)
, m_shape(0)
{
}
-void VectorTool::activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes)
+void VectorTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
- Q_UNUSED(toolActivation);
+ KoToolBase::activate(activation, shapes);
foreach (KoShape *shape, shapes) {
m_shape = dynamic_cast<VectorShape *>(shape);
if (m_shape) {
break;
}
}
if (!m_shape) {
emit done();
return;
}
useCursor(Qt::ArrowCursor);
}
void VectorTool::deactivate()
{
m_shape = 0;
+ KoToolBase::deactivate();
}
QWidget *VectorTool::createOptionWidget()
{
QWidget *optionWidget = new QWidget();
QGridLayout *layout = new QGridLayout(optionWidget);
QToolButton *button = 0;
button = new QToolButton(optionWidget);
button->setIcon(koIcon("document-open"));
button->setToolTip(i18n("Open Vector Image (EMF/WMF/SVM)"));
layout->addWidget(button, 0, 0);
connect(button, SIGNAL(clicked(bool)), this, SLOT(changeUrlPressed()));
return optionWidget;
}
void VectorTool::changeUrlPressed()
{
if (m_shape == 0) {
return;
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setCaption(i18n("Select a Vector Image"));
dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation));
dialog.setMimeTypeFilters(QString("image/x-emf,image/x-wmf,image/x-svm,image/svg+xml").split(','));
QString fn = dialog.filename();
if (!fn.isEmpty()) {
QFile f(fn);
if (f.exists()) {
f.open(QFile::ReadOnly);
QByteArray ba = f.readAll();
f.close();
if (!ba.isEmpty()) {
const VectorShape::VectorType vectorType = VectorShape::vectorType(ba);
ChangeVectorDataCommand *cmd = new ChangeVectorDataCommand(m_shape, qCompress(ba), vectorType);
canvas()->addCommand(cmd);
}
}
}
}
void VectorTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
if (canvas()->shapeManager()->shapeAt(event->point) != m_shape) {
event->ignore(); // allow the event to be used by another
return;
}
changeUrlPressed();
}
diff --git a/plugins/flake/vectorshape/VectorTool.h b/plugins/flake/vectorshape/VectorTool.h
index 6f36319001..68f65ba5d3 100644
--- a/plugins/flake/vectorshape/VectorTool.h
+++ b/plugins/flake/vectorshape/VectorTool.h
@@ -1,60 +1,60 @@
/* This file is part of the KDE project
Copyright 2011 Boudewijn Rempt <boud@valdyas.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef VECTOR_TOOL
#define VECTOR_TOOL
#include <KoToolBase.h>
class VectorShape;
class VectorTool : public KoToolBase
{
Q_OBJECT
public:
explicit VectorTool(KoCanvasBase *canvas);
/// reimplemented from KoToolBase
virtual void paint(QPainter &, const KoViewConverter &) {}
/// reimplemented from KoToolBase
virtual void mousePressEvent(KoPointerEvent *) {}
/// reimplemented from superclass
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/// reimplemented from KoToolBase
virtual void mouseMoveEvent(KoPointerEvent *) {}
/// reimplemented from KoToolBase
virtual void mouseReleaseEvent(KoPointerEvent *) {}
/// reimplemented from KoToolBase
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape *> &shapes);
/// reimplemented from KoToolBase
virtual void deactivate();
protected:
/// reimplemented from KoToolBase
virtual QWidget *createOptionWidget();
private Q_SLOTS:
void changeUrlPressed();
private:
VectorShape *m_shape;
};
#endif
diff --git a/plugins/impex/CMakeLists.txt b/plugins/impex/CMakeLists.txt
index 34f7156aac..f91943f646 100644
--- a/plugins/impex/CMakeLists.txt
+++ b/plugins/impex/CMakeLists.txt
@@ -1,46 +1,48 @@
project(kritafilters)
add_subdirectory(libkra)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
add_definitions( -DCPU_32_BITS )
endif()
if(JPEG_FOUND AND HAVE_LCMS2)
add_subdirectory(jpeg)
endif()
if(TIFF_FOUND)
add_subdirectory(tiff)
endif()
if(PNG_FOUND)
add_subdirectory(png)
add_subdirectory(csv)
endif()
if(OPENEXR_FOUND)
add_subdirectory(exr)
endif()
if(POPPLER_FOUND)
add_subdirectory(pdf)
endif()
if(LIBRAW_FOUND)
add_subdirectory(raw)
endif()
+add_subdirectory(svg)
+
add_subdirectory(bmp)
add_subdirectory(ora)
add_subdirectory(ppm)
add_subdirectory(xcf)
add_subdirectory(psd)
add_subdirectory(odg)
add_subdirectory(qml)
add_subdirectory(tga)
add_subdirectory(heightmap)
add_subdirectory(brush)
add_subdirectory(spriter)
add_subdirectory(video)
add_subdirectory(kra)
diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h
index 2e3d262cbc..e901009b46 100644
--- a/plugins/impex/brush/kis_brush_export.h
+++ b/plugins/impex/brush/kis_brush_export.h
@@ -1,61 +1,61 @@
/*
* 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.
*/
#ifndef _KIS_Brush_EXPORT_H_
#define _KIS_Brush_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <ui_wdg_export_gih.h>
#include <kis_config_widget.h>
#include <kis_properties_configuration.h>
class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih
{
Q_OBJECT
public:
KisWdgOptionsBrush(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg);
KisPropertiesConfigurationSP configuration() const;
};
class KisBrushExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisBrushExport(QObject *parent, const QVariantList &);
virtual ~KisBrushExport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
void initializeCapabilities();
};
#endif
diff --git a/plugins/impex/exr/exr_export.h b/plugins/impex/exr/exr_export.h
index a1950b9a9a..23cd869859 100644
--- a/plugins/impex/exr/exr_export.h
+++ b/plugins/impex/exr/exr_export.h
@@ -1,59 +1,60 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _EXR_EXPORT_H_
#define _EXR_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
#include "ui_exr_export_widget.h"
class KisWdgOptionsExr : public KisConfigWidget, public Ui::ExrExportWidget
{
Q_OBJECT
public:
KisWdgOptionsExr(QWidget *parent)
: KisConfigWidget(parent)
{
setupUi(this);
}
void setConfiguration(const KisPropertiesConfigurationSP cfg);
KisPropertiesConfigurationSP configuration() const;
};
class EXRExport : public KisImportExportFilter
{
Q_OBJECT
public:
EXRExport(QObject *parent, const QVariantList &);
virtual ~EXRExport();
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
void initializeCapabilities();
};
#endif
diff --git a/plugins/impex/exr/exr_import.h b/plugins/impex/exr/exr_import.h
index eb6ed2cb5f..52adab72b7 100644
--- a/plugins/impex/exr/exr_import.h
+++ b/plugins/impex/exr/exr_import.h
@@ -1,37 +1,37 @@
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef EXR_IMPORT_H_
#define EXR_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class exrImport : public KisImportExportFilter
{
Q_OBJECT
public:
exrImport(QObject *parent, const QVariantList &);
virtual ~exrImport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
};
#endif
diff --git a/plugins/impex/libkra/CMakeLists.txt b/plugins/impex/libkra/CMakeLists.txt
index d127d20d4d..2b840c1e8a 100644
--- a/plugins/impex/libkra/CMakeLists.txt
+++ b/plugins/impex/libkra/CMakeLists.txt
@@ -1,29 +1,29 @@
-#add_subdirectory(tests)
+add_subdirectory(tests)
set(kritalibkra_LIB_SRCS
kis_colorize_dom_utils.cpp
kis_colorize_dom_utils.h
kis_kra_loader.cpp
kis_kra_loader.h
kis_kra_load_visitor.cpp
kis_kra_load_visitor.h
kis_kra_saver.cpp
kis_kra_saver.h
kis_kra_save_visitor.cpp
kis_kra_save_visitor.h
kis_kra_savexml_visitor.cpp
kis_kra_savexml_visitor.h
kis_kra_tags.h
kis_kra_utils.cpp
kis_kra_utils.h
)
add_library(kritalibkra SHARED ${kritalibkra_LIB_SRCS})
target_link_libraries(kritalibkra kritaui)
generate_export_header(kritalibkra BASE_NAME kritalibkra)
set_target_properties(kritalibkra PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritalibkra ${INSTALL_TARGETS_DEFAULT_ARGS} )
diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp
index 89acc985a5..e6faaba85f 100644
--- a/plugins/impex/libkra/kis_kra_load_visitor.cpp
+++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp
@@ -1,661 +1,663 @@
/*
* 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 <QRect>
#include <QBuffer>
#include <QByteArray>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoStore.h>
#include <KoColorSpace.h>
// kritaimage
#include <metadata/kis_meta_data_io_backend.h>
#include <metadata/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"
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,
QMap<KisNode *, QString> &layerFilenames,
QMap<KisNode *, QString> &keyframeFilenames,
const QString & name,
int syntaxVersion) :
KisNodeVisitor(),
m_layerFilenames(layerFilenames),
m_keyframeFilenames(keyframeFilenames)
{
m_external = false;
m_image = image;
m_store = store;
m_name = name;
m_store->pushDirectory();
if (m_name.startsWith("/")) {
m_name.remove(0, 1);
}
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 (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);
dbgFile << "Visit: " << layer->name() << " colorSpace: " << layer->colorSpace()->id();
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->filter().data(), getLocation(layer, DOT_FILTERCONFIG));
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());
result = loadFilterConfiguration(layer->filter().data(), getLocation(layer, DOT_FILTERCONFIG));
layer->update();
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);
layer->setCopyFrom(srcLayer);
// 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->filter().data(), getLocation(mask, DOT_FILTERCONFIG));
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);
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.").arg(id).arg(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.").arg(id).arg(location);
}
}
}
}
return true;
}
template<class DevicePolicy>
bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy)
{
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;
}
if (m_store->open(location + ".defaultpixel")) {
int pixelSize = device->colorSpace()->pixelSize();
if (m_store->size() == pixelSize) {
KoColor color(Qt::transparent, device->colorSpace());
m_store->read((char*)color.data(), pixelSize);
policy.setDefaultPixel(device, color);
}
m_store->close();
}
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();
// Create a colorspace with the embedded profile
const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data);
if (device->setProfile(profile)) {
return true;
}
}
m_warningMessages << i18n("Could not load profile: %1.", location);
return true;
}
bool KisKraLoadVisitor::loadFilterConfiguration(KisFilterConfigurationSP kfc, const QString& location)
{
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);
}
return true;
}
}
m_warningMessages << i18n("Could not filter configuration %1.", location);
return true;
}
bool KisKraLoadVisitor::loadMetaData(KisNode* node)
{
dbgFile << "Load metadata for " << node->name();
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 backenda t 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)
{
// 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.xml")) {
+ 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_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;
- KoXmlDocument doc = KoXmlDocument(true);
- bool ok = doc.setContent(m_store->device(), &errorMsg, &errorLine, &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;
}
- QDomDocument dom;
- KoXml::asQDomElement(dom, doc.documentElement());
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);
}
}
}
diff --git a/plugins/impex/libkra/tests/CMakeLists.txt b/plugins/impex/libkra/tests/CMakeLists.txt
index 04fcd783d9..1fcf6a5787 100644
--- a/plugins/impex/libkra/tests/CMakeLists.txt
+++ b/plugins/impex/libkra/tests/CMakeLists.txt
@@ -1,18 +1,15 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
macro_add_unittest_definitions()
-set(kis_kra_loader_test_SRCS kis_kra_loader_test.cpp )
-kde4_add_broken_unit_test(KisKraLoaderTest TESTNAME krita-ui-KisKraLoaderTest ${kis_kra_loader_test_SRCS})
-target_link_libraries(KisKraLoaderTest kritaimage kritaui Qt5::Test kritalibkra)
-
-set(kis_kra_saver_test_SRCS kis_kra_saver_test.cpp )
-kde4_add_broken_unit_test(KisKraSaverTest TESTNAME krita-ui-KisKraSaverTest ${kis_kra_saver_test_SRCS})
-target_link_libraries(KisKraSaverTest kritaimage kritaui Qt5::Test kritalibkra)
-
-set(kis_kra_savexml_visitor_test_SRCS kis_kra_savexml_visitor_test.cpp )
-kde4_add_unit_test(KisKraSaveXmlVisitorTest TESTNAME krita-ui-KisKraSaveXmlVisitorTest ${kis_kra_savexml_visitor_test_SRCS})
-target_link_libraries(KisKraSaveXmlVisitorTest kritaimage kritaui Qt5::Test kritalibkra)
-
+ecm_add_test(
+ kis_kra_loader_test.cpp
+ TEST_NAME KisKraLoaderTest
+ LINK_LIBRARIES kritaimage kritaui kritalibkra Qt5::Test)
+
+ecm_add_test(
+ kis_kra_saver_test.cpp
+ TEST_NAME KisKraSaverTest
+ LINK_LIBRARIES kritaimage kritaui kritalibkra Qt5::Test)
diff --git a/libs/ui/tests/data/single_layer_no_channel_flags_nontransp_def_pixel.kra b/plugins/impex/libkra/tests/data/single_layer_no_channel_flags_nontransp_def_pixel.kra
similarity index 100%
rename from libs/ui/tests/data/single_layer_no_channel_flags_nontransp_def_pixel.kra
rename to plugins/impex/libkra/tests/data/single_layer_no_channel_flags_nontransp_def_pixel.kra
diff --git a/libs/ui/tests/data/single_layer_no_channel_flags_transp_def_pixel.kra b/plugins/impex/libkra/tests/data/single_layer_no_channel_flags_transp_def_pixel.kra
similarity index 100%
rename from libs/ui/tests/data/single_layer_no_channel_flags_transp_def_pixel.kra
rename to plugins/impex/libkra/tests/data/single_layer_no_channel_flags_transp_def_pixel.kra
diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp
index c279c74a54..a540c80389 100644
--- a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp
+++ b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp
@@ -1,172 +1,172 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_kra_loader_test.h"
#include <QTest>
#include <KisDocument.h>
#include <KoDocumentInfo.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorSpace.h>
#include <KoColor.h>
#include "KisDocument.h"
#include "kis_image.h"
#include "testutil.h"
#include "KisPart.h"
#include <filter/kis_filter_registry.h>
#include <generator/kis_generator_registry.h>
#include "kis_image_animation_interface.h"
#include "kis_keyframe_channel.h"
#include "kis_time_range.h"
void KisKraLoaderTest::initTestCase()
{
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
}
void KisKraLoaderTest::testLoading()
{
- KisDocument *doc = KisPart::instance()->createDocument();
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra");
KisImageSP image = doc->image();
image->lock();
QCOMPARE(image->nlayers(), 12);
QCOMPARE(doc->documentInfo()->aboutInfo("title"), QString("test image for loading"));
QCOMPARE(image->height(), 753);
QCOMPARE(image->width(), 1000);
QCOMPARE(image->colorSpace()->id(), KoColorSpaceRegistry::instance()->rgb8()->id());
KisNodeSP node = image->root()->firstChild();
QVERIFY(node);
QCOMPARE(node->name(), QString("Background"));
QVERIFY(node->inherits("KisPaintLayer"));
node = node->nextSibling();
QVERIFY(node);
QCOMPARE(node->name(), QString("Group 1"));
QVERIFY(node->inherits("KisGroupLayer"));
QCOMPARE((int) node->childCount(), 2);
-
- delete doc;
}
void testObligeSingleChildImpl(bool transpDefaultPixel)
{
QString id = !transpDefaultPixel ?
"single_layer_no_channel_flags_nontransp_def_pixel.kra" :
"single_layer_no_channel_flags_transp_def_pixel.kra";
QString fileName = TestUtil::fetchDataFileLazy(id);
- KisDocument *doc = KisPart::instance()->createDocument();
- doc->loadNativeFormat(fileName);
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+ const bool result = doc->loadNativeFormat(fileName);
+ QVERIFY(result);
+
KisImageSP image = doc->image();
QVERIFY(image);
QCOMPARE(image->nlayers(), 2);
KisNodeSP root = image->root();
KisNodeSP child = root->firstChild();
QVERIFY(child);
QCOMPARE(root->original(), root->projection());
if (transpDefaultPixel) {
QCOMPARE(root->original(), child->projection());
} else {
QVERIFY(root->original() != child->projection());
}
-
- delete doc;
}
void KisKraLoaderTest::testObligeSingleChild()
{
testObligeSingleChildImpl(true);
}
void KisKraLoaderTest::testObligeSingleChildNonTranspPixel()
{
testObligeSingleChildImpl(false);
}
void KisKraLoaderTest::testLoadAnimated()
{
- KisDocument *doc = KisPart::instance()->createDocument();
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test_animation.kra");
KisImageSP image = doc->image();
KisNodeSP node1 = image->root()->firstChild();
KisNodeSP node2 = node1->nextSibling();
QVERIFY(node1->inherits("KisPaintLayer"));
QVERIFY(node2->inherits("KisPaintLayer"));
KisPaintLayerSP layer1 = qobject_cast<KisPaintLayer*>(node1.data());
KisPaintLayerSP layer2 = qobject_cast<KisPaintLayer*>(node2.data());
- KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id());
- KisKeyframeChannel *channel2 = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id());
+ QVERIFY(layer1->isAnimated());
+ QVERIFY(!layer2->isAnimated());
+
+ KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id());
+ QVERIFY(channel1);
QCOMPARE(channel1->keyframeCount(), 3);
- QCOMPARE(channel2->keyframeCount(), 1);
QCOMPARE(image->animationInterface()->framerate(), 17);
QCOMPARE(image->animationInterface()->fullClipRange(), KisTimeRange::fromTime(15, 45));
QCOMPARE(image->animationInterface()->currentTime(), 19);
KisPaintDeviceSP dev = layer1->paintDevice();
const KoColorSpace *cs = dev->colorSpace();
KoColor transparent(Qt::transparent, cs);
KoColor white(Qt::white, cs);
KoColor red(Qt::red, cs);
image->animationInterface()->switchCurrentTimeAsync(0);
image->waitForDone();
QCOMPARE(dev->exactBounds(), QRect(506, 378, 198, 198));
QCOMPARE(dev->x(), -26);
QCOMPARE(dev->y(), -128);
QCOMPARE(dev->defaultPixel(), transparent);
image->animationInterface()->switchCurrentTimeAsync(20);
image->waitForDone();
QCOMPARE(dev->nonDefaultPixelArea(), QRect(615, 416, 129, 129));
QCOMPARE(dev->x(), 502);
QCOMPARE(dev->y(), 224);
QCOMPARE(dev->defaultPixel(), white);
image->animationInterface()->switchCurrentTimeAsync(30);
image->waitForDone();
QCOMPARE(dev->nonDefaultPixelArea(), QRect(729, 452, 45, 44));
QCOMPARE(dev->x(), 645);
QCOMPARE(dev->y(), -10);
QCOMPARE(dev->defaultPixel(), red);
}
QTEST_MAIN(KisKraLoaderTest)
diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp
index 9b7d2162d6..99226ff310 100644
--- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp
+++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp
@@ -1,429 +1,549 @@
/*
* Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_kra_saver_test.h"
#include <QTest>
#include <QBitArray>
#include <KisDocument.h>
#include <KoDocumentInfo.h>
#include <KoShapeContainer.h>
#include <KoPathShape.h>
#include "filter/kis_filter_registry.h"
#include "filter/kis_filter_configuration.h"
#include "filter/kis_filter.h"
#include "KisDocument.h"
#include "kis_image.h"
#include "kis_pixel_selection.h"
#include "kis_group_layer.h"
#include "kis_paint_layer.h"
#include "kis_clone_layer.h"
#include "kis_adjustment_layer.h"
#include "kis_shape_layer.h"
#include "kis_filter_mask.h"
#include "kis_transparency_mask.h"
#include "kis_selection_mask.h"
#include "kis_selection.h"
#include "kis_fill_painter.h"
#include "kis_shape_selection.h"
#include "util.h"
#include "testutil.h"
#include "kis_keyframe_channel.h"
#include "kis_image_animation_interface.h"
#include "kis_layer_properties_icons.h"
#include "kis_transform_mask_params_interface.h"
#include <filter/kis_filter_registry.h>
#include <generator/kis_generator_registry.h>
#include <KoResourcePaths.h>
void KisKraSaverTest::initTestCase()
{
KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns");
KisFilterRegistry::instance();
KisGeneratorRegistry::instance();
}
void KisKraSaverTest::testCrashyShapeLayer()
{
/**
* KisShapeLayer used to call setImage from its destructor and
* therefore causing an infinite recursion (when at least one transparency
* mask was preset. This testcase just checks that.
*/
- QScopedPointer<KisDocument> doc(createCompleteDocument(true));
- Q_UNUSED(doc);
+ //QScopedPointer<KisDocument> doc(createCompleteDocument(true));
+ //Q_UNUSED(doc);
}
void KisKraSaverTest::testRoundTrip()
{
KisDocument* doc = createCompleteDocument();
KoColor bgColor(Qt::red, doc->image()->colorSpace());
doc->image()->setDefaultProjectionColor(bgColor);
doc->exportDocument(QUrl::fromLocalFile("roundtriptest.kra"));
+
QStringList list;
KisCountVisitor cv1(list, KoProperties());
doc->image()->rootLayer()->accept(cv1);
KisDocument *doc2 = KisPart::instance()->createDocument();
- doc2->loadNativeFormat("roundtriptest.kra");
+ bool result = doc2->loadNativeFormat("roundtriptest.kra");
+ QVERIFY(result);
KisCountVisitor cv2(list, KoProperties());
doc2->image()->rootLayer()->accept(cv2);
QCOMPARE(cv1.count(), cv2.count());
// check whether the BG color is saved correctly
QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor);
// test round trip of a transform mask
KisNode* tnode =
TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data();
QVERIFY(tnode);
KisTransformMask *tmask = dynamic_cast<KisTransformMask*>(tnode);
QVERIFY(tmask);
KisDumbTransformMaskParams *params = dynamic_cast<KisDumbTransformMaskParams*>(tmask->transformParams().data());
QVERIFY(params);
QTransform t = params->testingGetTransform();
QCOMPARE(t, createTestingTransform());
delete doc2;
delete doc;
-
-
-
}
void KisKraSaverTest::testSaveEmpty()
{
KisDocument* doc = createEmptyDocument();
doc->exportDocument(QUrl::fromLocalFile("emptytest.kra"));
QStringList list;
KisCountVisitor cv1(list, KoProperties());
doc->image()->rootLayer()->accept(cv1);
KisDocument *doc2 = KisPart::instance()->createDocument();
doc2->loadNativeFormat("emptytest.kra");
KisCountVisitor cv2(list, KoProperties());
doc2->image()->rootLayer()->accept(cv2);
QCOMPARE(cv1.count(), cv2.count());
delete doc2;
delete doc;
}
#include <filter/kis_filter_configuration.h>
#include "generator/kis_generator_registry.h"
#include <generator/kis_generator.h>
void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config)
{
TestUtil::ExternalImageChecker chk(testName, "fill_layer");
chk.setFuzzy(2);
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
// mask parent should be destructed before the document!
QRect refRect(0,0,512,512);
TestUtil::MaskParent p(refRect);
doc->setCurrentImage(p.image);
doc->documentInfo()->setAboutInfo("title", p.image->objectName());
KisSelectionSP selection;
KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection);
p.image->addNode(glayer, p.image->root(), KisNodeSP());
glayer->setDirty();
p.image->waitForDone();
chk.checkImage(p.image, "00_initial_layer_update");
doc->exportDocument(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"));
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_fill_layer_test.kra");
doc2->image()->waitForDone();
chk.checkImage(doc2->image(), "01_fill_layer_round_trip");
QVERIFY(chk.testPassed());
}
void KisKraSaverTest::testRoundTripFillLayerColor()
{
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color");
Q_ASSERT(generator);
// warning: we pass null paint device to the default constructed value
KisFilterConfigurationSP config = generator->factoryConfiguration();
Q_ASSERT(config);
QVariant v;
v.setValue(KoColor(Qt::red, cs));
config->setProperty("color", v);
testRoundTripFillLayerImpl("fill_layer_color", config);
}
void KisKraSaverTest::testRoundTripFillLayerPattern()
{
KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern");
QVERIFY(generator);
// warning: we pass null paint device to the default constructed value
KisFilterConfigurationSP config = generator->factoryConfiguration();
QVERIFY(config);
QVariant v;
v.setValue(QString("11_drawed_furry.png"));
config->setProperty("pattern", v);
testRoundTripFillLayerImpl("fill_layer_pattern", config);
}
#include "kis_psd_layer_style.h"
void KisKraSaverTest::testRoundTripLayerStyles()
{
TestUtil::ExternalImageChecker chk("kra_saver_test", "layer_styles");
QRect imageRect(0,0,512,512);
// the document should be created before the image!
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image");
KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8);
KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8);
KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8);
image->addNode(layer1);
image->addNode(layer2);
image->addNode(layer3);
doc->setCurrentImage(image);
doc->documentInfo()->setAboutInfo("title", image->objectName());
layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs));
layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs));
layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs));
KisPSDLayerStyleSP style(new KisPSDLayerStyle());
style->dropShadow()->setEffectEnabled(true);
style->dropShadow()->setAngle(-90);
style->dropShadow()->setUseGlobalLight(false);
layer1->setLayerStyle(style->clone());
style->dropShadow()->setAngle(180);
style->dropShadow()->setUseGlobalLight(true);
layer2->setLayerStyle(style->clone());
style->dropShadow()->setAngle(90);
style->dropShadow()->setUseGlobalLight(false);
layer3->setLayerStyle(style->clone());
image->initialRefreshGraph();
chk.checkImage(image, "00_initial_layers");
doc->exportDocument(QUrl::fromLocalFile("roundtrip_layer_styles.kra"));
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_layer_styles.kra");
doc2->image()->waitForDone();
chk.checkImage(doc2->image(), "00_initial_layers");
QVERIFY(chk.testPassed());
}
void KisKraSaverTest::testRoundTripAnimation()
{
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
QRect imageRect(0,0,512,512);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image");
KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8);
image->addNode(layer1);
layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs));
layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs));
KUndo2Command parentCommand;
layer1->enableAnimation();
KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true);
QVERIFY(rasterChannel);
rasterChannel->addKeyframe(10, &parentCommand);
image->animationInterface()->switchCurrentTimeAsync(10);
image->waitForDone();
layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs));
layer1->paintDevice()->moveTo(25, 15);
layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs));
rasterChannel->addKeyframe(20, &parentCommand);
image->animationInterface()->switchCurrentTimeAsync(20);
image->waitForDone();
layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs));
layer1->paintDevice()->moveTo(100, 50);
layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs));
QVERIFY(!layer1->useInTimeline());
layer1->setUseInTimeline(true);
doc->setCurrentImage(image);
doc->exportDocument(QUrl::fromLocalFile("roundtrip_animation.kra"));
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_animation.kra");
KisImageSP image2 = doc2->image();
KisNodeSP node = image2->root()->firstChild();
QVERIFY(node->inherits("KisPaintLayer"));
KisPaintLayerSP layer2 = qobject_cast<KisPaintLayer*>(node.data());
cs = layer2->paintDevice()->colorSpace();
QCOMPARE(image2->animationInterface()->currentTime(), 20);
KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id());
QVERIFY(channel);
QCOMPARE(channel->keyframeCount(), 3);
image2->animationInterface()->switchCurrentTimeAsync(0);
image2->waitForDone();
QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128));
QCOMPARE(layer2->paintDevice()->x(), 0);
QCOMPARE(layer2->paintDevice()->y(), 0);
QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs));
image2->animationInterface()->switchCurrentTimeAsync(10);
image2->waitForDone();
QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64));
QCOMPARE(layer2->paintDevice()->x(), 25);
QCOMPARE(layer2->paintDevice()->y(), 15);
QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs));
image2->animationInterface()->switchCurrentTimeAsync(20);
image2->waitForDone();
QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64));
QCOMPARE(layer2->paintDevice()->x(), 100);
QCOMPARE(layer2->paintDevice()->y(), 50);
QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs));
QVERIFY(layer2->useInTimeline());
}
#include "lazybrush/kis_lazy_fill_tools.h"
void KisKraSaverTest::testRoundTripColorizeMask()
{
QRect imageRect(0,0,512,512);
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16();
QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image");
doc->setCurrentImage(image);
KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS);
image->addNode(layer1);
KisColorizeMaskSP mask = new KisColorizeMask();
image->addNode(mask, layer1);
mask->initializeCompositeOp();
delete mask->setColorSpace(layer1->colorSpace());
{
KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace()));
mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace()));
// KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd");
}
{
KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace()));
mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace()));
// KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd");
}
{
KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8());
key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace()));
mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true);
// KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd");
}
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image);
KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image);
doc->exportDocument(QUrl::fromLocalFile("roundtrip_colorize.kra"));
QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
doc2->loadNativeFormat("roundtrip_colorize.kra");
KisImageSP image2 = doc2->image();
KisNodeSP node = image2->root()->firstChild()->firstChild();
KisColorizeMaskSP mask2 = dynamic_cast<KisColorizeMask*>(node.data());
QVERIFY(mask2);
QCOMPARE(mask2->compositeOpId(), mask->compositeOpId());
QCOMPARE(mask2->colorSpace(), mask->colorSpace());
QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false);
QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false);
QList<KisLazyFillTools::KeyStroke> strokes = mask->fetchKeyStrokesDirect();
qDebug() << ppVar(strokes.size());
QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20));
QCOMPARE(strokes[0].isTransparent, false);
QCOMPARE(strokes[0].color.colorSpace(), weirdCS);
QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20));
QCOMPARE(strokes[1].isTransparent, false);
QCOMPARE(strokes[1].color.colorSpace(), weirdCS);
QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10));
QCOMPARE(strokes[2].isTransparent, true);
QCOMPARE(strokes[2].color.colorSpace(), weirdCS);
}
+#include "kis_shape_layer.h"
+#include <KoPathShape.h>
+#include <KoColorBackground.h>
+
+void KisKraSaverTest::testRoundTripShapeLayer()
+{
+ TestUtil::ExternalImageChecker chk("kra_saver_test", "shape_layer");
+
+ QRect refRect(0,0,512,512);
+
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+ TestUtil::MaskParent p(refRect);
+
+ const qreal resolution = 144.0 / 72.0;
+ p.image->setResolution(resolution, resolution);
+
+ doc->setCurrentImage(p.image);
+ doc->documentInfo()->setAboutInfo("title", p.image->objectName());
+
+ KoPathShape* path = new KoPathShape();
+ path->setShapeId(KoPathShapeId);
+ path->moveTo(QPointF(10, 10));
+ path->lineTo(QPointF( 10, 110));
+ path->lineTo(QPointF(110, 110));
+ path->lineTo(QPointF(110, 10));
+ path->close();
+ path->normalize();
+ path->setBackground(toQShared(new KoColorBackground(Qt::red)));
+
+ path->setName("my_precious_shape");
+
+ KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75);
+ shapeLayer->addShape(path);
+ p.image->addNode(shapeLayer);
+ shapeLayer->setDirty();
+
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_layer_update");
+
+ doc->exportDocument(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"));
+
+ QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
+ doc2->loadNativeFormat("roundtrip_shapelayer_test.kra");
+
+ qApp->processEvents();
+ doc2->image()->waitForDone();
+ QCOMPARE(doc2->image()->xRes(), resolution);
+ QCOMPARE(doc2->image()->yRes(), resolution);
+ chk.checkImage(doc2->image(), "01_shape_layer_round_trip");
+
+ QVERIFY(chk.testPassed());
+}
+
+void KisKraSaverTest::testRoundTripShapeSelection()
+{
+ TestUtil::ExternalImageChecker chk("kra_saver_test", "shape_selection");
+
+ QRect refRect(0,0,512,512);
+
+ QScopedPointer<KisDocument> doc(KisPart::instance()->createDocument());
+ TestUtil::MaskParent p(refRect);
+
+ const qreal resolution = 144.0 / 72.0;
+ p.image->setResolution(resolution, resolution);
+
+ doc->setCurrentImage(p.image);
+ doc->documentInfo()->setAboutInfo("title", p.image->objectName());
+
+ p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace()));
+
+ KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds());
+
+ KisShapeSelection *shapeSelection = new KisShapeSelection(p.image, selection);
+ selection->setShapeSelection(shapeSelection);
+
+ KoPathShape* path = new KoPathShape();
+ path->setShapeId(KoPathShapeId);
+ path->moveTo(QPointF(10, 10));
+ path->lineTo(QPointF( 10, 110));
+ path->lineTo(QPointF(110, 110));
+ path->lineTo(QPointF(110, 10));
+ path->close();
+ path->normalize();
+ path->setBackground(toQShared(new KoColorBackground(Qt::red)));
+ path->setName("my_precious_shape");
+
+ shapeSelection->addShape(path);
+
+ KisTransparencyMaskSP tmask = new KisTransparencyMask();
+ tmask->setSelection(selection);
+ p.image->addNode(tmask, p.layer);
+
+ tmask->setDirty(p.image->bounds());
+
+ qApp->processEvents();
+ p.image->waitForDone();
+
+ chk.checkImage(p.image, "00_initial_shape_selection");
+
+ doc->exportDocument(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"));
+
+ QScopedPointer<KisDocument> doc2(KisPart::instance()->createDocument());
+ doc2->loadNativeFormat("roundtrip_shapeselection_test.kra");
+
+ qApp->processEvents();
+ doc2->image()->waitForDone();
+ QCOMPARE(doc2->image()->xRes(), resolution);
+ QCOMPARE(doc2->image()->yRes(), resolution);
+ chk.checkImage(doc2->image(), "00_initial_shape_selection");
+
+ KisNodeSP node = doc2->image()->root()->firstChild()->firstChild();
+ KisTransparencyMask *newMask = dynamic_cast<KisTransparencyMask*>(node.data());
+ QVERIFY(newMask);
+
+ QVERIFY(newMask->selection()->hasShapeSelection());
+
+ QVERIFY(chk.testPassed());
+}
+
QTEST_MAIN(KisKraSaverTest)
diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.h b/plugins/impex/libkra/tests/kis_kra_saver_test.h
index 94aaeb2684..04ff99c8d5 100644
--- a/plugins/impex/libkra/tests/kis_kra_saver_test.h
+++ b/plugins/impex/libkra/tests/kis_kra_saver_test.h
@@ -1,48 +1,51 @@
/*
* 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_KRA_SAVER_TEST_H
#define KIS_KRA_SAVER_TEST_H
#include <QtTest>
class KisKraSaverTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testCrashyShapeLayer();
// XXX: Also test roundtripping of metadata
void testRoundTrip();
void testSaveEmpty();
void testRoundTripFillLayerColor();
void testRoundTripFillLayerPattern();
void testRoundTripLayerStyles();
void testRoundTripAnimation();
void testRoundTripColorizeMask();
+ void testRoundTripShapeLayer();
+ void testRoundTripShapeSelection();
+
};
#endif
diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc
index 0d24026a64..1cc10e3c2f 100644
--- a/plugins/impex/png/kis_png_export.cc
+++ b/plugins/impex/png/kis_png_export.cc
@@ -1,227 +1,227 @@
/*
* Copyright (c) 2005 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_png_export.h"
#include <QCheckBox>
#include <QSlider>
#include <QApplication>
#include <kpluginfactory.h>
#include <KoColorSpace.h>
#include <KisImportExportManager.h>
#include <KoColorProfile.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpaceRegistry.h>
#include <KisExportCheckRegistry.h>
#include <kis_properties_configuration.h>
#include <kis_paint_device.h>
#include <KisDocument.h>
#include <kis_image.h>
#include <kis_paint_layer.h>
#include <kis_group_layer.h>
#include <kis_config.h>
#include <kis_properties_configuration.h>
#include <metadata/kis_meta_data_store.h>
#include <metadata/kis_meta_data_filter_registry_model.h>
#include <metadata/kis_exif_info_visitor.h>
#include "kis_png_converter.h"
#include <kis_iterator_ng.h>
K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin<KisPNGExport>();)
KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
{
}
KisPNGExport::~KisPNGExport()
{
}
bool hasVisibleWidgets()
{
QWidgetList wl = QApplication::allWidgets();
Q_FOREACH (QWidget* w, wl) {
if (w->isVisible() && strcmp(w->metaObject()->className(), "QDesktopWidget")) {
dbgFile << "Widget " << w << " " << w->objectName() << " " << w->metaObject()->className() << " is visible";
return true;
}
}
return false;
}
KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
{
KisImageSP image = document->savingImage();
KisPNGOptions options;
options.alpha = configuration->getBool("alpha", true);
options.interlace = configuration->getBool("interlaced", false);
- options.compression = configuration->getInt("compression", 0);
+ options.compression = configuration->getInt("compression", 3);
options.tryToSaveAsIndexed = configuration->getBool("indexed", false);
options.transparencyFillColor = configuration->getColor("transparencyFillColor").toQColor();
options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false);
options.forceSRGB = configuration->getBool("forceSRGB", true);
vKisAnnotationSP_it beginIt = image->beginAnnotations();
vKisAnnotationSP_it endIt = image->endAnnotations();
KisExifInfoVisitor eIV;
eIV.visit(image->rootLayer().data());
KisMetaData::Store *eI = 0;
if (eIV.countPaintLayer() == 1) {
eI = eIV.exifInfo();
}
if (eI) {
KisMetaData::Store* copy = new KisMetaData::Store(*eI);
eI = copy;
}
KisPNGConverter pngConverter(document);
KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI);
if (res == KisImageBuilder_RESULT_OK) {
delete eI;
return KisImportExportFilter::OK;
}
delete eI;
dbgFile << " Result =" << res;
return KisImportExportFilter::InternalError;
}
KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const
{
KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration();
cfg->setProperty("alpha", true);
cfg->setProperty("indexed", false);
- cfg->setProperty("compression", 9);
+ cfg->setProperty("compression", 3);
cfg->setProperty("interlaced", false);
cfg->setProperty("transparencyFillcolor", QString("255,255,255"));
cfg->setProperty("saveSRGBProfile", false);
cfg->setProperty("forceSRGB", true);
return cfg;
}
KisPropertiesConfigurationSP KisPNGExport::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const
{
QString filterConfig = KisConfig().exportConfiguration("PNG");
KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to);
cfg->fromXML(filterConfig, false);
return cfg;
}
KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const
{
return new KisWdgOptionsPNG(parent);
}
void KisPNGExport::initializeCapabilities()
{
addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED));
QList<QPair<KoID, KoID> > supportedColorModels;
supportedColorModels << QPair<KoID, KoID>()
<< QPair<KoID, KoID>(RGBAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(RGBAColorModelID, Integer16BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer8BitsColorDepthID)
<< QPair<KoID, KoID>(GrayAColorModelID, Integer16BitsColorDepthID);
addSupportedColorModels(supportedColorModels, "PNG");
}
void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg)
{
bool isThereAlpha = cfg->getBool("isThereAlpha");
alpha->setChecked(cfg->getBool("alpha", isThereAlpha));
if (cfg->getString("ColorModelID") == RGBAColorModelID.id()) {
tryToSaveAsIndexed->setVisible(true);
if (alpha->isChecked()) {
tryToSaveAsIndexed->setChecked(false);
}
else {
tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false));
}
}
else {
tryToSaveAsIndexed->setVisible(false);
}
interlacing->setChecked(cfg->getBool("interlaced", false));
- compressionLevel->setValue(cfg->getInt("compression", 9));
+ compressionLevel->setValue(cfg->getInt("compression", 3));
compressionLevel->setRange(1, 9 , 0);
alpha->setEnabled(isThereAlpha);
tryToSaveAsIndexed->setVisible(!isThereAlpha);
bnTransparencyFillColor->setEnabled(!alpha->isChecked());
bool sRGB = cfg->getBool("sRGB", false);
chkSRGB->setEnabled(sRGB);
chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true));
chkForceSRGB->setEnabled(!sRGB);
chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false));
QStringList rgb = cfg->getString("transparencyFillcolor", "0,0,0").split(',');
KoColor c(KoColorSpaceRegistry::instance()->rgb8());
c.fromQColor(Qt::white);
bnTransparencyFillColor->setDefaultColor(c);
c.fromQColor(QColor(rgb[0].toInt(), rgb[1].toInt(), rgb[2].toInt()));
bnTransparencyFillColor->setColor(c);
}
KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const
{
KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration());
bool alpha = this->alpha->isChecked();
bool interlace = interlacing->isChecked();
int compression = (int)compressionLevel->value();
bool tryToSaveAsIndexed = this->tryToSaveAsIndexed->isChecked();
QColor c = bnTransparencyFillColor->color().toQColor();
bool saveSRGB = chkSRGB->isChecked();
bool forceSRGB = chkForceSRGB->isChecked();
cfg->setProperty("alpha", alpha);
cfg->setProperty("indexed", tryToSaveAsIndexed);
cfg->setProperty("compression", compression);
cfg->setProperty("interlaced", interlace);
cfg->setProperty("transparencyFillcolor", QString("%1,%2,%3").arg(c.red()).arg(c.green()).arg(c.blue()));
cfg->setProperty("saveSRGBProfile", saveSRGB);
cfg->setProperty("forceSRGB", forceSRGB);
return cfg;
}
void KisWdgOptionsPNG::on_alpha_toggled(bool checked)
{
bnTransparencyFillColor->setEnabled(!checked);
}
#include "kis_png_export.moc"
diff --git a/plugins/impex/spriter/kis_spriter_export.h b/plugins/impex/spriter/kis_spriter_export.h
index d620f17493..04f9632a7d 100644
--- a/plugins/impex/spriter/kis_spriter_export.h
+++ b/plugins/impex/spriter/kis_spriter_export.h
@@ -1,135 +1,135 @@
/*
* 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.
*/
#ifndef _KIS_SPRITER_EXPORT_H_
#define _KIS_SPRITER_EXPORT_H_
#include <QVariant>
#include <QDomDocument>
#include <QList>
#include <KisImportExportFilter.h>
#include <kis_types.h>
struct SpriterFile {
qreal id;
QString name;
QString pathName;
QString baseName;
QString layerName;
qreal width;
qreal height;
qreal x;
qreal y;
};
struct Folder {
qreal id;
QString name;
QString pathName;
QString baseName;
QString groupName;
QList<SpriterFile> files;
};
struct Bone {
qreal id;
const Bone *parentBone;
QString name;
qreal x;
qreal y;
qreal width;
qreal height;
qreal localX;
qreal localY;
qreal localAngle;
qreal localScaleX;
qreal localScaleY;
qreal fixLocalX;
qreal fixLocalY;
qreal fixLocalAngle;
qreal fixLocalScaleX;
qreal fixLocalScaleY;
QList<Bone*> bones;
~Bone() {
qDeleteAll(bones);
bones.clear();;
}
};
struct SpriterSlot {
QString name;
bool defaultAttachmentFlag;
};
struct SpriterObject {
qreal id;
qreal folderId;
qreal fileId;
Bone *bone;
SpriterSlot *slot;
qreal x;
qreal y;
qreal localX;
qreal localY;
qreal localAngle;
qreal localScaleX;
qreal localScaleY;
qreal fixLocalX;
qreal fixLocalY;
qreal fixLocalAngle;
qreal fixLocalScaleX;
qreal fixLocalScaleY;
~SpriterObject() {
delete slot;
}
};
class KisSpriterExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisSpriterExport(QObject *parent, const QVariantList &);
virtual ~KisSpriterExport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
void initializeCapabilities();
private:
bool savePaintDevice(KisPaintDeviceSP dev, const QString &fileName);
void parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0);
Bone *parseBone(const Bone *parent, KisGroupLayerSP groupLayer);
void fixBone(Bone *bone);
void fillScml(QDomDocument &scml, const QString &entityName);
void writeBoneRef(const Bone *bone, QDomElement &mainline, QDomDocument &scml);
void writeBone(const Bone *bone, QDomElement &timeline, QDomDocument &scml);
KisImageSP m_image;
qreal m_timelineid;
QList<Folder> m_folders;
Bone *m_rootBone;
QList<SpriterObject> m_objects;
KisGroupLayerSP m_rootLayer; // Not the image's root later, but the one that is named "root"
KisLayerSP m_boneLayer;
};
#endif
diff --git a/plugins/impex/svg/CMakeLists.txt b/plugins/impex/svg/CMakeLists.txt
new file mode 100644
index 0000000000..63f81381f3
--- /dev/null
+++ b/plugins/impex/svg/CMakeLists.txt
@@ -0,0 +1,15 @@
+add_subdirectory(tests)
+
+set(kritasvgimport_SOURCES
+ kis_svg_import.cc
+ )
+
+add_library(kritasvgimport MODULE ${kritasvgimport_SOURCES})
+
+add_definitions(${SVG_DEFINITIONS} ${KDE4_ENABLE_EXCEPTIONS})
+
+target_link_libraries(kritasvgimport kritaui )
+
+install(TARGETS kritasvgimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
+
+install( PROGRAMS krita_svg.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})
diff --git a/plugins/impex/svg/kis_svg_import.cc b/plugins/impex/svg/kis_svg_import.cc
new file mode 100644
index 0000000000..0484fdb3b1
--- /dev/null
+++ b/plugins/impex/svg/kis_svg_import.cc
@@ -0,0 +1,82 @@
+/*
+ * 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 <KisDocument.h>
+#include <kis_image.h>
+
+#include <SvgParser.h>
+#include <KoColorSpaceRegistry.h>
+#include "kis_shape_layer.h"
+#include <KoShapeBasedDocumentBase.h>
+
+K_PLUGIN_FACTORY_WITH_JSON(SVGImportFactory, "krita_svg_import.json", registerPlugin<KisSVGImport>();)
+
+KisSVGImport::KisSVGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent)
+{
+}
+
+KisSVGImport::~KisSVGImport()
+{
+}
+
+KisImportExportFilter::ConversionStatus KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration)
+{
+ Q_UNUSED(configuration);
+
+ KisDocument * doc = document;
+
+ const QString baseXmlDir = QFileInfo(filename()).canonicalPath();
+
+ const qreal resolutionPPI = 100;
+ const qreal resolution = resolutionPPI / 72.0;
+
+ 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);
+ 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 KisImportExportFilter::OK;
+}
+
+#include <kis_svg_import.moc>
diff --git a/plugins/impex/svg/kis_svg_import.h b/plugins/impex/svg/kis_svg_import.h
new file mode 100644
index 0000000000..056ca619e7
--- /dev/null
+++ b/plugins/impex/svg/kis_svg_import.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifndef _KIS_SVG_IMPORT_H_
+#define _KIS_SVG_IMPORT_H_
+
+#include <QVariant>
+
+#include <KisImportExportFilter.h>
+
+class KisSVGImport : public KisImportExportFilter
+{
+ Q_OBJECT
+public:
+ KisSVGImport(QObject *parent, const QVariantList &);
+ virtual ~KisSVGImport();
+public:
+ virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration);
+};
+
+#endif
diff --git a/plugins/impex/svg/krita_svg.desktop b/plugins/impex/svg/krita_svg.desktop
new file mode 100644
index 0000000000..012506a559
--- /dev/null
+++ b/plugins/impex/svg/krita_svg.desktop
@@ -0,0 +1,69 @@
+[Desktop Entry]
+Name=Krita
+Name[af]=Krita
+Name[bg]=Krita
+Name[br]=Krita
+Name[bs]=Krita
+Name[ca]=Krita
+Name[ca@valencia]=Krita
+Name[cs]=Krita
+Name[cy]=Krita
+Name[da]=Krita
+Name[de]=Krita
+Name[el]=Krita
+Name[en_GB]=Krita
+Name[eo]=Krita
+Name[es]=Krita
+Name[et]=Krita
+Name[eu]=Krita
+Name[fi]=Krita
+Name[fr]=Krita
+Name[fy]=Krita
+Name[ga]=Krita
+Name[gl]=Krita
+Name[he]=Krita
+Name[hi]=केरिता
+Name[hne]=केरिता
+Name[hr]=Krita
+Name[hu]=Krita
+Name[ia]=Krita
+Name[is]=Krita
+Name[it]=Krita
+Name[kk]=Krita
+Name[ko]=Krita
+Name[lt]=Krita
+Name[lv]=Krita
+Name[mr]=क्रिटा
+Name[ms]=Krita
+Name[nb]=Krita
+Name[nds]=Krita
+Name[ne]=क्रिता
+Name[nl]=Krita
+Name[pl]=Krita
+Name[pt]=Krita
+Name[pt_BR]=Krita
+Name[ro]=Krita
+Name[ru]=Krita
+Name[se]=Krita
+Name[sk]=Krita
+Name[sl]=Krita
+Name[sv]=Krita
+Name[ta]=கிரிட்டா
+Name[tg]=Krita
+Name[tr]=Krita
+Name[ug]=Krita
+Name[uk]=Krita
+Name[uz]=Krita
+Name[uz@cyrillic]=Krita
+Name[wa]=Krita
+Name[xh]=Krita
+Name[x-test]=xxKritaxx
+Name[zh_CN]=Krita
+Name[zh_TW]=繪圖_Krita
+Exec=krita %u
+MimeType=image/svg;
+Type=Application
+Icon=calligrakrita
+Categories=Qt;KDE;Office;Graphics;
+StartupNotify=true
+NoDisplay=true
diff --git a/plugins/impex/svg/krita_svg_import.json b/plugins/impex/svg/krita_svg_import.json
new file mode 100644
index 0000000000..297c2c039c
--- /dev/null
+++ b/plugins/impex/svg/krita_svg_import.json
@@ -0,0 +1,12 @@
+{
+ "Id": "Krita SVG Import Filter",
+ "NoDisplay": "true",
+ "Type": "Service",
+ "X-KDE-Import": "image/svg+xml",
+ "X-KDE-Library": "kritasvgimport",
+ "X-KDE-ServiceTypes": [
+ "Krita/FileFilter"
+ ],
+ "X-KDE-Weight": "1",
+ "X-KDE-Extensions" : "svg"
+}
diff --git a/plugins/impex/svg/tests/CMakeLists.txt b/plugins/impex/svg/tests/CMakeLists.txt
new file mode 100644
index 0000000000..9fedf4f2cf
--- /dev/null
+++ b/plugins/impex/svg/tests/CMakeLists.txt
@@ -0,0 +1,10 @@
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests )
+
+macro_add_unittest_definitions()
+
+ecm_add_test(
+ kis_svg_test.cpp
+ TEST_NAME KisSvgTest
+ LINK_LIBRARIES kritaui Qt5::Test
+)
diff --git a/plugins/impex/svg/tests/data/results/official_gnu.svg.png b/plugins/impex/svg/tests/data/results/official_gnu.svg.png
new file mode 100644
index 0000000000..f67a5d71ab
Binary files /dev/null and b/plugins/impex/svg/tests/data/results/official_gnu.svg.png differ
diff --git a/plugins/impex/svg/tests/data/sources/official_gnu.svg b/plugins/impex/svg/tests/data/sources/official_gnu.svg
new file mode 100644
index 0000000000..4db2e6ad73
--- /dev/null
+++ b/plugins/impex/svg/tests/data/sources/official_gnu.svg
@@ -0,0 +1,2496 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="450pt"
+ height="450pt"
+ id="svg1522">
+ <defs
+ id="defs2146" />
+ <path
+ d="M 1,1196 L 2,1199 L 1198,1199 L 1199,1196 L 1200,1196 L 1200,1200 L 0,1200 L 0,1196 L 1,1196 z "
+ style="fill:black;stroke:black"
+ id="path2144" />
+
+ <rect width="100%" height="100%" fill="white" />
+
+ <g
+ transform="matrix(0.488869,0,0,0.472831,-18.41646,-12.6579)"
+ id="g2147">
+
+ <path
+ d="M 826,767 L 827,766 C 828.885,766.1398 826.8603,764.115 827,766 L 826,767 L 808.25,780.75 L 807.25,779.25 C 817.87,768.65 831.65,755.3 844.75,751.25 L 838.75,759.75 C 852.72,748.95 867.95,739.63 883.25,731.25 C 896.62,731.28827 906.68,715.94 907,703 C 903.753,686.41 902.075,669.57 894.75,654.25 C 880.27,637.28 860.5,666.05 843.25,653.75 L 828.25,641.75 C 810,598.33 786.54,556.06 757.25,518.75 L 749,502 C 745.799,475.32 750.74,444.68 770.25,424.25 C 775.265,421.37 780.67,418.27 786.75,419.25 L 776,425 L 782.25,437.25 L 757.25,476.75 C 765.254,464.06 790.07,454.65 799.75,468.25 C 800.4692,470.182 800.2856,474.643 797.25,473.75 L 794.75,467.25 C 773.97,459.642 749.86,482.36 754.25,502.75 C 767.94,499.039 786.57,489.11 802.75,496.25 L 810.75,499.75 C 813.765,496.885 815.502,482.1 809.25,486.75 L 808.25,485.25 C 820.13,473.24 820.31,454.16 813.75,439.25 L 776.25,394.25 L 798.75,411.25 C 807.58,420.74 815.79,430.77 820.25,442.75 C 824.206,454.93 824.965,471.28 817,482 C 817.8914,488.265 817.6852,495.01 815,501 C 826.79,499.52 833.26,522.94 818.25,521.75 L 816.75,516.25 L 812.25,512.75 L 807.25,512.25 C 815.532,527.96 798.228,545.88 782,539 L 781,542 C 788.476,551.035 810.1,553.09 815.25,538.25 C 820.954,538.8432 813.775,545.4 812.25,547.75 L 827.75,536.25 L 826.75,543.75 C 808.72,552.672 826.6281,573.52 828.75,586.75 L 829,573 C 828.3322,569.99 829.2716,559.29 832.75,565.25 C 850.81,606.92 902.51,626.1 943.75,615.25 L 930,623 L 928.25,623.75 C 940.6,629.649 955.21,623.5527 968,623 C 975.805,630.46 994.83,625.703 1001.75,614.75 L 990.25,611.75 L 983.25,604.75 L 892.75,439.25 C 877.12,421.13 857.73,407.99 836.75,397.25 L 828.25,397.75 L 819,391 L 805,394 L 802,387 C 798.448,390.55 795.857,396.548 790,397 L 793.75,386.25 L 785.75,392.25 C 783.723,389.738 782.433,387.195 783.75,384.25 C 765.08,378.789 741.08,385.1736 727.75,401.75 L 722.25,404.25 C 728.751,412.826 734.06,426.96 725.75,436.75 C 720.439,445.875 710.12,448.14 700,447 C 698.019,445.899 691.581,445.79 695.25,442.25 C 697.937,442.453 699.003,441.3616 700.25,439.25 C 717.41,437.119 709.401,411.71 704.75,401.25 C 687.49,393.889 674.49,375.84 674,357 L 668.75,348.25 C 668.4115,363.5 672.661,381.37 682.75,394.25 C 690.447,401.52 706.5,413.29 698.75,425.75 L 691.75,408.25 C 665.48,406.418 646.68,382.67 644.75,357.25 C 640.395,368.18 645.272,383.01 652.25,392.75 C 661.81,406.22 685.18,405.02 694.75,419.75 L 692,421 C 680.53,411.908 666.81,406.46 653.25,401.75 C 645.047,390.99 639.57,378.79 638.75,365.25 C 634.937,367.851 630.526,368.363 626.25,367.25 C 622.64,370.259 615.04,374.952 612.25,369.25 L 585.75,384.75 L 575.75,392.75 L 574.75,386.25 C 569.137,386.8326 565.688,382.701 562.75,379.25 L 558,389 L 548.75,382.25 C 542.979,384.81 540.897,382.18063 542.75,376.25 L 530.25,382.75 C 534.36,374.261 531.07,368.07 526.25,361.25 L 523.25,372.75 C 526.812,372.5498 530.745,373.1095 528.75,377.75 L 525.75,380.75 C 520.69,378.872 516.157,379.702 513.75,385.75 C 505.811,387.65 504.983,384.8298 507,379 C 504.443,380.729 501.169,385.466 499,381 C 495.746,383.324 490.322,384.763 487.25,382.25 L 473.75,394.75 C 430.41,402.366 378.65,416.04 349.75,456.75 L 340.25,465.25 C 331.086,484.62 325.77,505.86 312.25,523.25 L 297.75,590.75 C 291.173,591.2615 300.002,577 292,582 C 292.9202,599.79 296.214,623.4 276.25,632.25 L 270.25,639.25 L 269,649 C 275.847,676.12 244.24,686.78 232.25,706.25 C 229.213,711.651 230.322,717.23 232.25,722.75 C 235.3,727.064 239.606,724.606 244,725 C 259.49,721.74 266.96,702.8 284.75,704.75 C 290.415,695.856 299.83,689.31 310.75,690.75 C 322.63,674.81 343.92,679.09 360.75,677.75 L 374.75,663.25 L 369.75,676.75 L 386,671 C 387.885,671.1398 385.8603,669.115 386,671 L 385.25,666.25 L 398.25,655.25 C 403.779,653.614 407.747,647.595 411.25,643.25 C 420.146,636.26 427.36,626.72 433.25,617.25 L 435,619 C 433.856,622.357 426.437,627.651 431.75,628.75 L 427.25,636.75 L 435.75,632.75 L 452.75,612.75 L 462.25,590.25 L 464.75,587.75 L 477,550 L 480,526 C 482.793,520.852 482.951,514.94 483,509 L 488,492 L 489,472 L 495.25,455.25 L 523.75,429.75 L 523.75,428.25 L 520,427 C 506.63,431.345 495.25,442.75 489.75,455.75 L 463.75,486.75 L 451.25,494.75 L 451.25,493.25 L 469.25,477.25 L 471.75,474.75 L 478,463 L 479,462 C 480.885,462.1397 478.8603,460.115 479,462 L 478,463 L 475,465 L 474.25,464.75 L 481.25,454.25 L 483,452 L 484,451 L 485,450 C 486.885,450.1397 484.8603,448.115 485,450 L 484,451 L 483,452 L 465.25,466.75 L 503.25,421.25 C 524.18,420.081 543.35,422.632 554.75,403.25 C 558.524,407.066 552.541,410.757 551.25,414.25 L 556,419 C 549.63,439.56 531.95,455.86 517.75,472.75 C 515.846,474.695 513.349,480.519 510.25,477.75 C 526.31,466.06 514.546,452.66 517.75,440.25 L 498.25,459.25 C 482.44,530.56 478.16,614.55 411.25,661.25 C 408.209,668.192 405.858,675.19 406,683 L 416,706 C 415.91327,727.44 413.498,751.95 436.75,760.25 L 440,768 C 431.994,802.38 488.14,804.47 497.25,832.75 C 500.783,830.923 506.226,832.0992 508,836 C 506.786,850.95 481.4,859.83 492.25,877.75 L 501.25,883.75 C 532.03,886.541 552.09,923.41 520.25,941.25 L 516,950 C 515.8708,955.23 515.3845,960.83 519.25,964.75 L 530.75,967.75 C 530.75623,952.96 541.51,943.33 551.75,933.75 C 555.168,924.641 559.205,928.511 553.25,935.25 C 544.095,944.082 533.8,952.99 533,966 C 538.895,978.99 549.97,991.98 565.75,990.75 C 558.999,981.028 543.41,970.99 549.75,957.75 C 551.363,950.173 558.937,946.41 553,956 C 550.431,959.893 548.971,964.372 551.25,968.75 L 580,1008 C 582.837,1025.11 567.07,1036.11 554.25,1043.25 L 547.25,1053.25 C 545.27,1059.395 548.515,1064.16 553.25,1067.75 C 554.0026,1045.27 588.59,1039.35 590,1016 L 589.25,998.25 C 593.048,1004.499 592.694,1015.37 591,1023 C 588.189,1035.26 575.27,1039.93 567.25,1048.25 C 556.32,1055.844 554.4,1069.31 564.75,1076.75 C 553.58,1073.126 538.12,1062.07 547.25,1048.25 C 557.238,1035.02 580.5,1029.52 578,1009 C 570.937,986.47 540.34,997.02 532.75,971.25 C 525.093,969.01 514.35,968.077 513,958 C 512.453,950.992 515.134,945.11 518.25,939.25 C 554.11,914.85 514.294,888.64 490.25,878.75 C 487.427,875.469 487.903,871.236 488,867 C 484.75,852.47 512.28,846.49 502.75,834.25 C 493.622,844.27 469.07,858.3 473.75,875.25 C 489.22,887.1 506.71,899.13 519.75,912.75 L 472.25,877.75 C 464.208,865.79 476.131,856.12 483.75,848.25 L 466.25,853.75 C 492.15,842.21 471.363,824.04 452.25,822.75 C 449.168,820.036 450.12,815.729 450,812 C 470,791.29 428.35,809.398 422.25,791.75 C 414.554,780.1 427.596,771.6 431,762 C 410.97,753.424 390.62,732.99 413,713 C 409.484,696.35 394.23,680.78 406.75,663.25 C 387.94,683.94 357.26,680.14 337.25,698.25 L 332,707 C 319.01,784.4 337.462,865.1 381,929 C 394.34,956.94 439.21,969.63 435.75,1002.75 C 433.422,1005.424 430.913,1008.086 427,1007 L 434.25,1000.25 C 435.72,997.142 435.456,992.521 433.25,989.75 L 427,980 L 421.25,975.75 C 410.61,965.939 435.38,997.67 419,995 L 422,990 L 420,982 L 411.25,969.75 C 340.95,902.41 313.37,802.55 326.75,707.25 C 319.694,708.536 316.68,701.774 312.25,698.25 C 297.65,710.48 280.64,733.48 260.25,722.25 C 250.2,728.498 231.32,733.99 226,719 C 224.269,686.02 275.53,677.72 265,643 L 268.25,634.25 C 286.53,623.65 292.38,604.11 288.75,584.25 C 204.05,579.383 136.25,506.13 108.95,429.95 C 77.76,310.15 140.09,163.85 264.95,129.95 C 288.49,122.307 313.43,114.38 338.95,116.95 C 368.11,118.07 398.5,115.224 425.2,124.7 L 485.7,156.2 C 500.1,165.415 519.03,160.063 533.7,156.2 L 533.95,160.95 C 524.101,185.3 496.88,193.18 473.95,197.95 L 429.95,196.95 C 414.34,192.659 398.43,193.724 381.95,193.95 C 368.81,193.6579 356.1,194.8303 343.95,197.95 C 241.65,204.832 176.75,368.65 270.95,427.95 C 288.48,430.185 302,418.987 316.2,411.2 C 331.93,400.69 346.28,387.95 359.7,373.7 L 389.7,338.7 C 396.774,328.38 403.94,317.82 413.7,309.7 C 465.09,233.53 584.1,219.15 643.2,290.7 L 665.7,277.7 C 747.78,212.76 846.6,301.69 891.2,372.7 L 915.2,399.7 C 924.45,405.994 934.83,410.96 945.95,411.95 C 953.966,414.202 962.93,412.6924 970.7,410.7 C 1029.18,376.22 1022.42,291.2 993.7,239.2 C 967.99,207.3 925.62,195.47 886.9,199.95 L 841.9,196.95 C 827.56,193.012 813.44,184.85 805.15,171.7 C 804.12,165.339 800.066,158.6 803.15,152.2 C 827.45,164.59 857.31,166.3 881.65,151.7 C 954.23,98.97 1073.55,108.16 1112.65,198.2 C 1131.84,242.68 1138.66,293.23 1132.9,343 C 1139,441.79 1056.92,542 954.1,536.3 L 978.1,582.8 C 983.061,592.26 998.55,618.92 1005.1,597.3 C 1006.115,596.3223 1007.477,594.123 1008.6,596.3 C 1012.694,607.03 1005.84,618.07 998.6,625.8 C 941.39,638.35 867.2,627.2 833.1,575.3 L 834.1,641.8 C 851.58,669.83 880,630.37 897.6,650.3 C 907.6,671.73 915.53,694.69 907.6,718.8 L 860.1,771.3 C 856.485,787.9 887.38,791.7 886.85,809.05 C 875.01,824.41 857.62,835.73 841.1,844.3 C 856.24,859.72 854.64,888.12 838.6,902.8 L 828.85,906.05 C 801.92,895.64 754.11,925.54 779.6,955.3 L 782.85,965.05 C 782.75112,971.166 783.6781,977.49 780.6,982.8 C 775.762,997.61 763.19,1006.93 750.6,1014.8 C 713.89,1028.94 693.78,1072.46 714.1,1107.8 L 723.6,1108.3 C 699.75,1117.011 734.07,1121.93 743.6,1120.8 C 730.71,1127.006 708.39,1125.91 707.6,1107.3 C 678.66,1084.13 694.81,1036.17 725.6,1021.8 L 746.6,1001.8 C 753.064,993.669 756.63,984.16 757.85,974.05 L 758.85,959.05 C 773.83,1010.29 700.24,1021.99 697.1,1065.85 L 722.1,1030.35 C 753.59,1011.86 803.6,976.45 762.1,939.85 L 758.6,928.35 L 755.85,922.1 C 734.25,927.505 723.39,957.72 740.85,973.1 L 741.85,989.1 C 735.653,1003.84 721.68,1015.46 708.6,1024.85 C 688.86,1038.75 681.79,1063.06 685.85,1086.1 C 687.486,1094.357 693.239,1099.97 699.85,1105.1 L 701.6,1110.85 C 671.25,1100.898 647.8,1066.44 673.1,1038.35 C 684.02,1026.42 697.45,1018.58 712.6,1014.85 C 729.04,997.86 747.6,973.36 721.1,955.85 L 719.85,945.1 L 723.6,928.35 C 711.63,924.666 710.12,943.58 711.1,954.85 L 716.6,960.85 C 701.08,955.628 709.514,923.6 723.6,917.35 L 719.1,919.35 L 715.1,921.35 L 702.6,929.85 C 701.6455,931.223 698.796,933.576 697.85,931.1 L 707.1,923.35 L 715.1,918.35 L 717.6,916.35 L 688.1,932.85 L 692.85,927.1 L 700.6,920.85 C 712.24,912.062 696.284,919.001 691.1,921.35 L 680.1,927.85 L 687.85,916.1 C 680.685,922.077 662.24,927.44 678.6,917.85 C 692.11,911.021 707.98,901.47 722.85,906.1 C 757.96,884.81 799.5,899.95 838.65,895.85 C 860.91,868.57 836.896,834.3 804.65,837.35 L 767.9,838.1 L 744.9,845.1 C 673.25,877.49 625.2,788.32 596.6,735.3 C 591.479,728.398 583.85,724.21 576.1,724.3 C 592.9,718.478 603.3,741.09 610.6,753.3 L 623.6,773.8 C 627.03,755.86 632.468,734.63 647.6,723.3 L 645.85,727.05 C 635.34,741.2 628.51,757.99 628.85,776.05 C 641.82,816.19 690.27,856.38 731.85,831.05 C 735.7,831.5042 745.77,828.199 743.85,834.05 C 726.54,838.042 709.04,844.4 691.1,844.8 C 699.389,847.793 707.65,850.404 716.85,850.05 C 763.55,831.74 811.97,816.05 863.85,825.05 L 881.6,809.8 C 882.5638,793.24 853.59,790.5 856.6,773.3 C 848.351,779.831 850.776,770.296 854.6,765.3 L 837.1,780.8 L 843.6,771.8 C 858.91,752.3 825.98,786.87 829.1,777.3 L 834.1,771.3 C 835.436,769.761 841.467,765.213 836.1,766.3 L 818.1,782.8 L 825.85,767.05 L 826,767 z "
+ style="fill:#1c1c1c;stroke:#1c1c1c"
+ id="path1526" />
+ <path
+ d="M 186,312 L 143,303 L 179.25,314.75 L 212.75,327.75 L 215,325 L 218.75,327.25 C 214.834,359.72 221.579,396.61 247,419 L 230,441 L 237,427 C 236.052,418.359 231.782,424.451 228.25,427.75 C 221.558,425.095 231.182,408.18 220.75,417.75 L 218.25,416.75 L 224.75,401.25 L 209.25,411.75 C 213.953,404.409 220.25,397.62 214.75,389.25 L 201.25,395.75 L 208.75,381.25 L 207,380 C 198.852,386.372 202.608,370.471 203.75,366.25 L 189.25,368.75 L 198,361 L 195,361 L 149,373 C 140.565,374.11 128.01,371.533 123,378 C 126.315,379.992 132.056,377.7683 136,377 L 150.75,376.25 L 138.75,380.75 L 128,385 L 183.75,373.25 L 175,378 L 138.25,393.25 L 139.25,399.75 L 181.75,386.25 L 134,407 L 166.75,399.75 L 187.75,391.25 L 186.75,394.75 C 166.93,404.644 144.1,412.55 125,419 C 130.425,422.859 138.81,417.647 145,416 L 169.75,406.75 L 196.75,394.25 C 175.52,413.75 146.41,420.06 121.25,432.25 L 121.25,435.75 L 147.75,426.25 L 156.75,426.25 L 130,444 C 142.73,447.245 157.41,434.143 170,429 L 181.75,421.75 L 193.75,415.25 L 155.25,441.75 C 159.115,444.428 163.935,439.243 168,438 L 191.75,424.25 L 154.25,448.25 L 134.25,458.75 L 177.75,441.25 C 176.8054,442.539 176.494,445.505 178.75,445.75 L 187.75,443.25 C 185.467,449.809 177.761,452.188 172.25,456.25 L 143.25,473.75 C 160.19,470.309 175.34,458.65 190,449 L 193.75,447.25 C 181.66,462.07 167.13,473.96 149.25,482.25 L 149.25,484.75 C 151.942,485.6087 154.835,484.68246 156.75,482.75 L 171.75,473.75 L 183.75,466.25 C 184.775,472.816 191.805,465.6978 194.75,464.25 L 153.25,491.25 L 153.25,494.75 L 165,489 L 179,481 L 199,469 C 207.814,462.443 205.247,470.431 198.25,473.25 L 160.25,497.75 C 181.12,489.855 203.98,477.64 218.75,460.25 C 216.623,457.72 221.975,455.436 223.75,455.25 C 213.67,481.02 182.8,492.17 160.25,505.75 C 150.322,495.59 144.72,483.16 137.75,471.25 C 128.106,459.11 122.29,444.45 116.25,430.75 C 86.42,325.65 131.37,196.25 235.75,149.75 C 245.701,142.935 256.87,138.9 268,135 C 299.07,124.79 333.74,117.99 367,124 C 403.17,123.4948 440.8,130.103 469.3,154.75 L 491.05,166 C 502.12,168.356 512.87,166.8429 523.8,164.25 C 512.98,190.46 475.67,191.4 451.3,190.75 L 455.8,187.75 C 462.667,182.977 459.451,170.7 459.8,162.25 L 454.3,162.75 C 451.718,151.92 442.08,157.588 435.3,155.25 C 435.4579,166.16 434.75,179.18 429.8,189.75 L 426.05,190 C 423.203,190.8031 418.912,189.3611 422.8,186.75 C 422.59,179.576 426.057,173.94 429.05,168 L 428.8,152.25 L 424.05,145 L 418.8,188.75 L 400.05,188 L 399.05,173 L 399.05,166 C 400.152,161.881 400.312,157.592 404.8,157.25 L 405.05,169 C 403.24,172.379 402.714,184.75 407.05,177 L 408.05,173 C 410.415,159.37 414.82,176.012 409.05,181 L 410.8,183.75 C 422.06,170.94 425.23,150.97 413.8,136.25 L 410.3,137.25 L 410.8,150.75 L 404.3,136.25 C 398.061,136.5556 403.967,154.38 399.3,146.75 C 402.992,136.34 391.048,134.03 392.05,145 L 389.3,169.75 L 395.8,160.25 C 395.3287,169.661 395.635,180.35 388.8,187.75 L 377.3,187.75 L 391.05,141 C 391.6662,137.322 388.975,135.78 386.05,135 L 373.8,187.75 L 367.05,188 L 354.3,187.75 L 356.05,156 C 353.763,148.301 355.5936,136.61 348.3,132.25 C 351.005,147.78 354.4,168.15 350.8,185.75 L 303.3,201.25 L 258.05,238 L 252.3,229.75 L 255.05,225 L 218.3,192.75 C 216.361,189.755 206.37,184.868 210.3,191.75 L 214.3,194.75 L 207.3,196.25 L 206.3,199.75 C 216.85,212.99 230.76,225.93 243.8,235.75 C 245.063,233.245 242.097,231.872 240.8,230.25 L 228.3,216.75 C 222.952,211.52 214.5,207.362 212.3,200.25 L 232.8,209.25 L 245.3,228.25 L 246.3,230.75 C 256.5,236.251 256.57,244.65 247.8,251.75 C 236.79,269.35 227.51,287.78 222.8,307.75 L 212.3,301.75 C 203.122,287.31 179.62,287.94 164.3,282.25 L 164.3,284.75 C 184.02,292.033 205.13,297.22 221.05,312 L 218.8,322.75 C 197.11,308.51 175.09,293.62 151.05,284 L 147.05,286 L 170.05,297 C 179.864,300.74 182.81,305.825 186.05,312 L 186,312 z "
+ style="fill:#cfcfcf;stroke:#cfcfcf"
+ id="path1527" />
+ <path
+ d="M 1052,303 C 1071.31,295.747 1093.06,287.54 1115,291 L 1118.75,289.75 L 1104.75,283.25 L 1102.25,282.25 L 1118.75,280.75 L 1118.75,278.25 L 1100.25,275.75 L 1100.75,274.75 C 1098.39,270.819 1100.5422,270.587 1105,271 L 1120.75,269.75 L 1077.25,268.75 L 1080.75,267.75 C 1082.144,263.597 1086.129,263.62 1090,264 C 1098.331,263.0797 1107.82,261.184 1116,263 L 1117.75,261.75 L 1102.25,257.75 C 1103.474,256.8364 1110.695,256.8936 1106.75,253.25 L 1096.25,251.75 C 1092.347,247.43 1100.222,248.424 1102.25,247.25 L 1108.75,245.75 L 1105,243 L 1070,247 L 1066.25,246.75 L 1092,235 L 1090,233 L 1061.25,243.75 C 1071.087,235.969 1083.82,231.12 1095.75,227.75 L 1087,224 L 1093.75,219.75 L 1091.25,212.25 L 1095.75,209.25 L 1076.25,209.75 C 1079.084,207.331 1093.03,206.36 1085,201 C 1087.629,199.502 1089.026,197.199 1085.25,195.75 C 1086.61,193.914 1086.1012,191.297 1083.25,191.75 L 1084,184 L 1082.75,182.25 L 1069,185 L 1054.25,182.75 L 1072.75,178.75 L 1072.75,176.25 L 1066.25,175.25 C 1069.311,173.742 1076.95,175.6515 1075.75,170.25 L 1058,173 L 1054,173 L 1053.25,172.25 L 1069.75,166.75 L 1070,165 L 1058,166 L 1044.25,164.75 L 1044.25,163.25 C 1051.486,164.369 1047.662,158.901 1044.25,157.25 L 1046.75,155.75 L 1045.75,152.25 C 1040.62,154.394 1033.56,158.552 1029.75,152.25 L 1027.25,151.75 C 1030.756,151.2479 1036.12,149.505 1034.75,144.25 L 1001.25,145.75 L 1007.75,142.25 C 1004.001,138.995 999.031,142.6271 995,143 L 997.75,139.25 L 977,145 L 983.75,140.75 L 988.75,136.75 L 987,135 L 960.25,150.75 L 965,144 L 969.75,138.25 C 966.681,137.2807 963.03,137.192 960.25,139.25 L 956.75,140.75 L 949.75,145.75 C 944.566,143.473 940.06,142.589 935,147 L 932,144 L 906.25,155.25 C 904.516,154.7005 901.872,148.817 905.25,146.25 L 935,135 C 952.65,128.083 971.57,122.2 991,127 C 1009.23,124.367 1026.42,134.711 1042.25,142.75 C 1063.81,150.044 1080.23,165.83 1093.75,184.25 C 1127.12,235.43 1129.19,295.65 1128,357.05 L 1123,388.05 L 1108.75,428.8 L 1098.75,412.3 L 1090.75,405.3 C 1085.57,403.692 1085.303,399.255 1085.25,395.3 C 1093.912,398.017 1102.4,403.284 1110,409.05 L 1112.75,407.8 L 1106.25,401.8 L 1093.75,394.3 L 1078.25,384.3 L 1093.25,387.3 L 1095.75,388.3 L 1115.75,399.8 C 1116.3369,394.883 1109.89,392.977 1107,390.05 C 1101.254,385.995 1088.79,383.564 1090.75,375.3 L 1087.25,372.3 L 1108,381.05 L 1116,387.05 L 1120.75,388.8 L 1120.75,386.3 C 1112.627,380.161 1102.71,375.07 1096.25,366.3 L 1102.75,367.3 C 1104.271,368.709 1106.935,372.21 1108.75,369.8 L 1098,360.05 L 1084.25,352.3 C 1097.27,354.811 1110.13,363.08 1116,375.05 C 1118.653,373.494 1115.8235,370.033 1115,368.05 L 1112.25,353.8 L 1106.25,349.3 L 1125.75,357.8 C 1126.898,354.49 1122.106,353.574 1120,352.05 L 1113.75,348.3 C 1108.619,347.4555 1103.44,343.652 1098.25,345.8 L 1083.25,337.3 L 1105,342.05 C 1110.887,339.713 1117.05,340.604 1120.25,347.8 L 1123.75,348.8 C 1122.507,340.186 1111.69,338.19 1106.25,334.3 L 1117.75,336.8 C 1116.8827,333.431 1116.125,328.871 1112.25,327.3 L 1119.75,327.3 C 1121.217,328.383 1125.092,328.797 1126,327.05 C 1113.01,322.577 1098.77,318.019 1084,318.05 L 1079,315.05 L 1034.25,318.3 L 1033.75,322.8 L 1021.25,326.8 L 1021.25,317.3 L 1051.75,307.8 L 1089,303.05 C 1098.929,303.3153 1109.44,302.86 1118,300.05 L 1112,297.05 L 1052,303.05 L 1052,303 z "
+ style="fill:#e9e9e9;stroke:#e9e9e9"
+ id="path1528" />
+ <path
+ d="M 374,133 C 372.115,133.1398 374.1397,131.115 374,133 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1529" />
+ <path
+ d="M 361,133 L 362.75,136.25 L 362.75,136.75 C 360.304,137.862 361.104,134.3 361,133 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1530" />
+ <path
+ d="M 374,133 L 374.75,133.25 L 376.75,141.75 L 375.25,141.75 L 374,133 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1531" />
+ <path
+ d="M 320,135 C 318.115,135.1398 320.1397,133.115 320,135 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1532" />
+ <path
+ d="M 331,134 C 332.834,133.3347 333.559,135.431 332.75,136.75 L 331,134 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1533" />
+ <path
+ d="M 339,134 C 341.028,133.4978 342.817,135.958 341.75,137.75 L 341.25,137.75 L 339,134 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1534" />
+ <path
+ d="M 322,138 L 320,135 L 322,138 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1535" />
+ <path
+ d="M 315,137 C 313.115,137.1398 315.1397,135.115 315,137 z "
+ style="fill:#050505;stroke:#050505"
+ id="path1536" />
+ <path
+ d="M 317,140 L 315,137 L 317,140 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1537" />
+ <path
+ d="M 371,137 L 371.75,137.25 L 371.75,147.75 L 371.25,147.75 L 370.25,138.25 L 371,137 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1538" />
+ <path
+ d="M 322,138 L 323.75,140.75 L 322,138 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1539" />
+ <path
+ d="M 333,138 L 334.75,142.75 L 334.25,142.75 L 333,138 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1540" />
+ <path
+ d="M 307,139 C 308.834,138.3347 309.559,140.431 308.75,141.75 L 307,139 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1541" />
+ <path
+ d="M 282,142 L 281.25,140.25 L 282,142 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1542" />
+ <path
+ d="M 318,142 L 317,140 L 318,142 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1543" />
+ <path
+ d="M 345,147 L 342,141 L 342.25,140.25 L 344,143 L 345,147 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1544" />
+ <path
+ d="M 362,140 L 362.75,140.25 L 363.75,148.75 L 362.25,148.75 L 362,140 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1545" />
+ <path
+ d="M 292,147 L 289,146 L 290,151 L 290.25,156.75 L 287.25,143.25 L 290,141 L 291,142 L 292,147 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1546" />
+ <path
+ d="M 282,142 L 283.75,145.25 L 283.75,145.75 C 281.304,146.862 282.104,143.3 282,142 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1547" />
+ <path
+ d="M 302,156 L 300.25,148.75 L 298.25,142.25 L 299.75,142.25 L 301.25,146.75 L 302.75,147.25 L 306,160 L 307,162 L 305.25,179.25 L 305.75,180.75 L 305,181 L 304,180 L 304,167 L 302.75,156.25 L 302,156 z "
+ style="fill:#080808;stroke:#080808"
+ id="path1548" />
+ <path
+ d="M 320,146 L 318,142 L 320,146 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1549" />
+ <path
+ d="M 326,146 L 324.25,142.25 L 326,146 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1550" />
+ <path
+ d="M 310,148 L 309.25,143.25 L 310,148 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1551" />
+ <path
+ d="M 375,143 L 376.75,143.75 L 375,143 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1552" />
+ <path
+ d="M 336,147 L 335.25,144.25 L 336,147 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1553" />
+ <path
+ d="M 977,145 L 974.25,147.25 L 968.75,151.75 L 967.25,151.75 L 971.75,147.75 L 977,145 z "
+ style="fill:#989898;stroke:#989898"
+ id="path1554" />
+ <path
+ d="M 322,151 L 320,146 L 322,151 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1555" />
+ <path
+ d="M 327,148 L 326,146 L 327,148 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1556" />
+ <path
+ d="M 376,146 C 377.885,145.8602 375.8603,147.885 376,146 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1557" />
+ <path
+ d="M 282,147 L 284.75,152.25 L 284.75,152.75 L 282.25,147.75 L 282,147 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1558" />
+ <path
+ d="M 293,149 L 292,147 L 293,149 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1559" />
+ <path
+ d="M 336,147 L 336.75,150.75 L 336,147 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1560" />
+ <path
+ d="M 346,151 L 345,147 L 346,151 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1561" />
+ <path
+ d="M 311,153 L 310,148 L 311,153 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1562" />
+ <path
+ d="M 327,148 L 328.75,152.75 L 328.25,152.75 L 327,148 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1563" />
+ <path
+ d="M 376,148 L 376.75,153.75 L 376.25,153.75 L 376,148 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1564" />
+ <path
+ d="M 295,155 L 294.25,154.75 L 293,149 L 295,155 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1565" />
+ <path
+ d="M 371,149 L 372.75,149.25 L 372.75,157.75 L 372.25,157.75 L 371,150 L 371,149 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1566" />
+ <path
+ d="M 993,187 L 1004.75,185.25 C 1002.5,189.788 998.673,194.462 993.75,197.75 L 987.75,205.75 C 985.771,208.052 982.63,210.565 983.25,213.75 L 999.25,198.25 L 1012.75,194.25 L 1009.25,199.25 L 1002.25,209.25 C 998.595,213.827 991.7,219.41 995.25,224.75 C 1008.58,211.13 1023.13,194.99 1042.75,190.75 L 1058.75,187.25 C 1035.7,196.909 1012.47,212.58 998,234 C 979.38,212.34 951.32,199.78 924,194 C 890.23,190.794 854.62,199.535 824.25,183.75 C 816.213,177.823 809.22,169.81 807.25,160.25 L 814.75,162.25 C 831.68,174.48 852.03,167.856 869.75,162.25 L 872,166 L 865.75,169.75 C 853.97,172.68 843.19,178.347 830.25,177.25 L 828,180 C 840.72,183.286 853.39,178.153 865.75,175.25 L 860.75,178.75 L 848,185 L 864.25,184.75 L 866,185 L 890.25,172.25 L 893,174 L 912.25,164.25 L 915,166 L 896.75,174.75 L 884.25,181.75 C 896.46,180.086 908.54,174.869 919.25,168.25 L 925,170 L 953.75,154.25 L 946.75,179.75 L 943.75,185.75 C 944.235,189.35 949.423,181.99 950.75,179.75 L 956.25,171.25 C 965.098,161.796 975.55,152.94 988.75,152.25 C 979.188,159.323 967.11,165.57 960.75,176.75 C 957.447,181.943 953.839,187.73 953.25,193.75 L 955,194 C 963.028,169.3 989.72,152.61 1014.75,149.25 L 995.25,156.25 L 977.25,168.25 C 969.641,177.389 964.26,190.75 967,202 L 969,198 C 967.603,179.04 982.91,165.6 999,159 L 1008.75,157.25 L 994.75,165.75 L 979.25,180.25 C 973.14,188.022 971.751,199.19 974.25,208.75 L 989.75,192.75 L 993,187 z "
+ style="fill:#dedede;stroke:#dedede"
+ id="path1567" />
+ <path
+ d="M 257,150 C 258.885,149.8602 256.8603,151.885 257,150 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1568" />
+ <path
+ d="M 270,153 L 268.25,150.25 L 270,153 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1569" />
+ <path
+ d="M 363,150 L 363.75,150.25 L 363.75,162.75 L 363.25,162.75 L 363,150 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1570" />
+ <path
+ d="M 322,151 L 323.75,153.75 L 322,151 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1571" />
+ <path
+ d="M 346,151 L 346.75,151.25 L 348,157 L 348.75,163.75 L 348,164 L 347.25,163.75 L 347,158 L 346,151 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1572" />
+ <path
+ d="M 257,152 C 258.449,151.5827 260.612,153.213 259.75,154.75 L 257,152 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1573" />
+ <path
+ d="M 337,152 L 337.75,152.25 L 339,160 C 340.493,162.279 340.493,165.721 339,168 L 338,174 L 336.75,180.75 L 336.25,180.75 L 337,176 L 338,171 L 337.75,166.25 L 337,162 C 338.439,159.992 338.439,157.008 337,155 L 337,152 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1574" />
+ <path
+ d="M 270,153 L 271.75,156.75 L 270,153 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1575" />
+ <path
+ d="M 311,153 L 311.75,157.75 L 311,153 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1576" />
+ <path
+ d="M 960,153 L 963.75,154.25 L 960.75,158.75 C 958.977,159.761 958.977,161.239 960.75,162.25 L 960.75,163.75 L 948.75,176.75 L 947.25,176.75 C 949.715,168.248 954.092,159.91 960,153 z "
+ style="fill:#d7d7d7;stroke:#d7d7d7"
+ id="path1577" />
+ <path
+ d="M 245,154 C 246.885,153.8602 244.8602,155.885 245,154 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1578" />
+ <path
+ d="M 283,154 L 284.75,154.75 L 283,154 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1579" />
+ <path
+ d="M 329,154 L 331.75,161.75 L 330.25,161.75 L 329,154 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1580" />
+ <path
+ d="M 295,155 L 295.75,157.75 L 295,155 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1581" />
+ <path
+ d="M 302,156 L 301,164 L 300.25,167.75 L 300,160 C 298.961,158.599 298.345,156.357 300.25,155.25 L 302,156 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1582" />
+ <path
+ d="M 325,157 L 324.25,155.25 L 325,157 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1583" />
+ <path
+ d="M 247,157 C 245.115,157.1398 247.1398,155.115 247,157 z "
+ style="fill:#111;stroke:#111"
+ id="path1584" />
+ <path
+ d="M 261,157 C 259.115,157.1398 261.1397,155.115 261,157 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1585" />
+ <path
+ d="M 248,158 L 247,157 L 248,158 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1586" />
+ <path
+ d="M 263,161 L 261,157 L 263,161 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1587" />
+ <path
+ d="M 278,161 L 277.25,160.75 L 275.25,157.25 C 277.387,156.3293 278.495,159.328 278,161 z "
+ style="fill:#111;stroke:#111"
+ id="path1588" />
+ <path
+ d="M 284,157 L 284.75,157.25 L 286,167 L 285,168 L 284.25,167.75 L 284,157 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1589" />
+ <path
+ d="M 325,157 L 326.75,161.25 L 327,162 L 326.75,162.75 C 323.659,163.5984 325.488,158.713 325,157 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1590" />
+ <path
+ d="M 249,159 L 248,158 L 249,159 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1591" />
+ <path
+ d="M 274,162 C 272.387,161.7025 271.405,159.712 272.25,158.25 L 272.75,158.25 L 274,162 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1592" />
+ <path
+ d="M 1028,158 L 1030.75,158.25 L 1030.75,159.75 L 1029,160 L 1021.25,161.75 C 1018.988,160.728 1021.3422,158.442 1023,159 L 1028,158 z "
+ style="fill:#afafaf;stroke:#afafaf"
+ id="path1593" />
+ <path
+ d="M 245,161 L 241.25,159.75 L 241.25,159.25 C 242.712,158.4053 244.702,159.3873 245,161 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1594" />
+ <path
+ d="M 254,164 L 249,159 C 251.339,159.02673 253.973,161.661 254,164 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1595" />
+ <path
+ d="M 291,159 L 291.75,163.75 L 291,159 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1596" />
+ <path
+ d="M 296,159 L 297.75,163.25 L 297.75,163.75 C 294.932,164.763 296.32,160.488 296,159 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1597" />
+ <path
+ d="M 312,159 L 312.75,163.75 L 312,159 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1598" />
+ <path
+ d="M 372,159 C 373.885,158.8602 371.8603,160.885 372,159 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1599" />
+ <path
+ d="M 246,162 L 245,161 L 246,162 z "
+ style="fill:black;stroke:black"
+ id="path1600" />
+ <path
+ d="M 266,168 L 265.25,167.75 L 263,161 L 263.75,161.25 L 266,168 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1601" />
+ <path
+ d="M 278,161 L 278.75,163.75 L 278,161 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1602" />
+ <path
+ d="M 247,163 L 246,162 L 247,163 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1603" />
+ <path
+ d="M 274,162 L 276,166 L 276.75,169.75 L 274,163 L 274,162 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1604" />
+ <path
+ d="M 1000,167 L 1000.25,166.25 L 1010,162 L 1011.75,162.25 L 1011.75,162.75 L 1000,167 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path1605" />
+ <path
+ d="M 248,164 L 247,163 L 248,164 z "
+ style="fill:#111;stroke:#111"
+ id="path1606" />
+ <path
+ d="M 250,166 L 248,164 L 250,166 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1607" />
+ <path
+ d="M 255,165 L 254,164 L 255,165 z "
+ style="fill:black;stroke:black"
+ id="path1608" />
+ <path
+ d="M 327,171 L 326.25,170.75 L 326.25,164.25 L 326.75,164.25 L 327,171 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1609" />
+ <path
+ d="M 362,164 C 363.885,163.8602 361.8603,165.885 362,164 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1610" />
+ <path
+ d="M 372,164 L 373.75,164.75 L 372,164 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1611" />
+ <path
+ d="M 467,164 L 468.75,164.25 L 470,167 L 469,181 L 468.75,183.75 L 465.25,183.75 L 465,181 L 467,164 z "
+ style="fill:#3b3b3b;stroke:#3b3b3b"
+ id="path1612" />
+ <path
+ d="M 256,166 L 255,165 L 256,166 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1613" />
+ <path
+ d="M 291,165 L 292.75,165.25 L 292.75,176.75 L 292.25,176.75 L 291,165 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1614" />
+ <path
+ d="M 252,168 L 250,166 L 252,168 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1615" />
+ <path
+ d="M 257,167 L 256,166 L 257,167 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1616" />
+ <path
+ d="M 278,166 L 279.75,166.25 L 281,178 L 282,189 L 283,191 L 282,193 L 282.75,203.75 L 282,196 L 280,188 L 279,186 L 277,172 L 279,170 L 278,166 z "
+ style="fill:#080808;stroke:#080808"
+ id="path1617" />
+ <path
+ d="M 297,166 L 298.75,173.25 L 298.75,173.75 L 297.25,173.75 L 297,166 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1618" />
+ <path
+ d="M 312,166 L 313.75,166.75 L 312,166 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1619" />
+ <path
+ d="M 372,166 L 373.75,166.75 L 372,166 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1620" />
+ <path
+ d="M 942,166 C 943.885,165.8602 941.8603,167.885 942,166 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1621" />
+ <path
+ d="M 235,169 L 232.25,167.25 L 235,169 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1622" />
+ <path
+ d="M 264,176 L 262.25,174.75 L 257,167 C 259.962,169.196 263.641,172.117 264,176 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1623" />
+ <path
+ d="M 1000,167 L 999.75,172.75 L 992.25,180.25 L 991.75,181.75 L 990.25,182.25 L 976.25,199.75 L 979.25,184.25 L 1000,167 z "
+ style="fill:#e1e1e1;stroke:#e1e1e1"
+ id="path1624" />
+ <path
+ d="M 252,168 C 253.885,167.8602 251.8602,169.885 252,168 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1625" />
+ <path
+ d="M 265,186 L 269.75,191.75 L 265,178 L 267.25,180.75 L 269.25,184.75 L 269.75,184.75 L 267.75,175.25 C 265.766,173.451 263.926,170.369 266,168 L 273.25,182.75 L 273.75,182.75 L 274.75,178.25 L 276,188 C 277.595,191.467 277.595,196.533 276,200 L 275,205 L 274.75,207.75 L 274.25,207.75 L 265.25,187.75 L 265,186 z "
+ style="fill:#080808;stroke:#080808"
+ id="path1626" />
+ <path
+ d="M 312,168 L 313.75,168.75 L 312,168 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1627" />
+ <path
+ d="M 347,168 L 348.75,168.75 L 347,168 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1628" />
+ <path
+ d="M 478,169 C 476.115,169.1398 478.1397,167.115 478,169 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1629" />
+ <path
+ d="M 938,170 L 940.75,168.25 L 938,170 z "
+ style="fill:#111;stroke:#111"
+ id="path1630" />
+ <path
+ d="M 993,187 L 993.25,185.25 C 998.915,178.812 1004.65,170.62 1013,168 L 1015.75,168.25 L 1010.25,172.25 L 1005.25,178.25 L 1003.75,180.75 L 993,187 z "
+ style="fill:#cfcfcf;stroke:#cfcfcf"
+ id="path1631" />
+ <path
+ d="M 213,169 L 215.75,170.25 L 233.25,183.75 L 234.75,184.25 L 240.75,190.25 L 251.75,201.25 L 255,210 L 253.75,211.75 L 251.25,210.75 C 244.879,198.02 232.81,187.97 221.75,179.25 L 218.25,177.75 L 212.25,172.75 L 213,169 z "
+ style="fill:#343434;stroke:#343434"
+ id="path1632" />
+ <path
+ d="M 237,170 L 235,169 L 237,170 z "
+ style="fill:#111;stroke:#111"
+ id="path1633" />
+ <path
+ d="M 300,169 L 301.75,169.25 L 301.75,183.75 L 301.25,183.75 L 300,169 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1634" />
+ <path
+ d="M 372,169 L 372.75,173.75 L 372,169 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1635" />
+ <path
+ d="M 479,172 L 478,169 L 479,172 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1636" />
+ <path
+ d="M 239,172 L 237,170 L 239,172 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1637" />
+ <path
+ d="M 255,188 L 258.75,188.75 L 257,185 L 251.25,176.75 L 249.25,170.25 L 251.75,170.25 L 266,196 L 265.75,198.75 L 262.25,197.75 L 257.75,190.25 L 255,188 z "
+ style="fill:#070707;stroke:#070707"
+ id="path1638" />
+ <path
+ d="M 256,173 L 253.25,170.25 C 254.787,169.3878 256.417,171.551 256,173 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1639" />
+ <path
+ d="M 270,170 C 271.885,169.8602 269.8603,171.885 270,170 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1640" />
+ <path
+ d="M 285,170 L 286,171 L 286,201 L 285.25,201.75 L 284.25,199.75 L 285,198 L 285,170 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1641" />
+ <path
+ d="M 347,170 C 349.017,169.5475 348.931,170.8362 348,172 C 348.6457,174.164 346.354,174.164 347,172 L 347,170 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1642" />
+ <path
+ d="M 445,170 L 447,172 C 445.053,174.54 445.953,177.908 448,180 L 446.75,191.75 L 433.25,190.75 L 437.25,178.25 L 439.75,176.25 L 440.75,184.75 L 444,174 L 445,170 z "
+ style="fill:#d3d3d3;stroke:#d3d3d3"
+ id="path1643" />
+ <path
+ d="M 938,170 L 935,171 L 938,170 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1644" />
+ <path
+ d="M 327,171 L 327.75,174.75 L 327,171 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1645" />
+ <path
+ d="M 491,171 L 492.75,172.75 L 491,171 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1646" />
+ <path
+ d="M 873,171 L 875,171 C 880.475,171.0414 869.493,175.314 873,171 z "
+ style="fill:#9b9b9b;stroke:#9b9b9b"
+ id="path1647" />
+ <path
+ d="M 935,171 C 935.1398,172.885 933.115,170.8602 935,171 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1648" />
+ <path
+ d="M 240,173 L 239,172 L 240,173 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1649" />
+ <path
+ d="M 271,172 C 272.681,172.7312 274.849,174.684 273.75,176.75 L 273.25,176.75 L 271,172 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1650" />
+ <path
+ d="M 313,172 L 313.75,172.25 L 313.75,180.75 L 312.25,180.75 L 313,172 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1651" />
+ <path
+ d="M 479,172 L 480.75,178.25 L 480.75,178.75 L 479,172 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1652" />
+ <path
+ d="M 929,174 L 931.75,172.25 C 932.859,174.18 930.162,174.164 929,174 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1653" />
+ <path
+ d="M 1037,172 L 1041.75,172.25 C 1041.1259,176.28 1036.501,177.069 1033,177 L 1023.25,177.75 L 1023.25,177.25 L 1026,176 L 1030.75,174.75 L 1037,172 z "
+ style="fill:#c9c9c9;stroke:#c9c9c9"
+ id="path1654" />
+ <path
+ d="M 242,174 L 240,173 L 242,174 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1655" />
+ <path
+ d="M 258,176 L 256,173 L 258,176 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1656" />
+ <path
+ d="M 242,174 C 243.885,173.8602 241.8602,175.885 242,174 z "
+ style="fill:#070707;stroke:#070707"
+ id="path1657" />
+ <path
+ d="M 491,174 C 494.682,173.5818 491.5636,176.978 491.75,178.75 L 491,177 L 491,174 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1658" />
+ <path
+ d="M 929,174 C 929.1398,175.885 927.115,173.8602 929,174 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1659" />
+ <path
+ d="M 297,175 L 298.75,176.75 L 297,175 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1660" />
+ <path
+ d="M 371,178 L 371.75,175.25 L 371,178 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1661" />
+ <path
+ d="M 924,176 L 925.75,175.25 L 924,176 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1662" />
+ <path
+ d="M 246,178 L 244.25,176.25 L 246,178 z "
+ style="fill:#111;stroke:#111"
+ id="path1663" />
+ <path
+ d="M 258,176 L 259.75,178.75 L 258,176 z "
+ style="fill:#111;stroke:#111"
+ id="path1664" />
+ <path
+ d="M 265,178 L 264,176 L 265,178 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1665" />
+ <path
+ d="M 326,176 L 326.75,178.75 L 326,176 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1666" />
+ <path
+ d="M 924,176 L 922.25,176.75 L 924,176 z "
+ style="fill:#090909;stroke:#090909"
+ id="path1667" />
+ <path
+ d="M 915,179 L 915.25,178.25 L 919.75,177.25 L 915,179 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1668" />
+ <path
+ d="M 247,179 L 246,178 L 247,179 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1669" />
+ <path
+ d="M 371,178 L 369.75,183.75 L 369.25,183.75 L 370.25,178.25 L 371,178 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1670" />
+ <path
+ d="M 248,180 L 247,179 L 248,180 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1671" />
+ <path
+ d="M 297,179 L 297.75,180.75 L 297,179 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1672" />
+ <path
+ d="M 915,179 L 913,180 L 915,179 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1673" />
+ <path
+ d="M 250,182 L 248,180 L 250,182 z "
+ style="fill:#111;stroke:#111"
+ id="path1674" />
+ <path
+ d="M 260,180 L 261.75,181.75 L 260,180 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1675" />
+ <path
+ d="M 479,180 L 480.75,180.75 L 479,180 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1676" />
+ <path
+ d="M 913,180 L 911,181 L 913,180 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1677" />
+ <path
+ d="M 911,181 L 909,182 L 911,181 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1678" />
+ <path
+ d="M 933,182 L 934.75,181.25 L 933,182 z "
+ style="fill:#111;stroke:#111"
+ id="path1679" />
+ <path
+ d="M 1040,182 L 1042.75,181.25 L 1040,182 z "
+ style="fill:#969696;stroke:#969696"
+ id="path1680" />
+ <path
+ d="M 251,183 L 250,182 L 251,183 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1681" />
+ <path
+ d="M 312,182 L 313.75,182.75 L 312,182 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1682" />
+ <path
+ d="M 909,182 L 907,183 L 909,182 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1683" />
+ <path
+ d="M 933,182 L 931.25,182.75 L 933,182 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1684" />
+ <path
+ d="M 1040,182 L 1039.75,182.75 L 1031,187 L 1030.25,186.75 C 1032.318,183.914 1036.332,182.109 1040,182 z "
+ style="fill:#949494;stroke:#949494"
+ id="path1685" />
+ <path
+ d="M 252,184 L 251,183 L 252,184 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1686" />
+ <path
+ d="M 265,186 L 263.25,183.25 L 265,186 z "
+ style="fill:#111;stroke:#111"
+ id="path1687" />
+ <path
+ d="M 479,186 L 479.75,183.25 L 479,186 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1688" />
+ <path
+ d="M 907,183 L 905,184 L 907,183 z "
+ style="fill:#111;stroke:#111"
+ id="path1689" />
+ <path
+ d="M 929,183 C 930.885,182.8602 928.8603,184.885 929,183 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1690" />
+ <path
+ d="M 215,185 L 213.25,184.25 L 215,185 z "
+ style="fill:#080808;stroke:#080808"
+ id="path1691" />
+ <path
+ d="M 255,188 L 252,184 L 255,188 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1692" />
+ <path
+ d="M 301,192 C 300.5849,188.724 300.7589,184.615 304.75,184.25 C 305.4692,186.507 305.2856,190.005 302.25,190.25 L 301,192 z "
+ style="fill:#090909;stroke:#090909"
+ id="path1693" />
+ <path
+ d="M 312,184 C 314.362,185.032 312.3706,188.097 311.75,189.75 L 311.25,189.75 L 312,184 z "
+ style="fill:#111;stroke:#111"
+ id="path1694" />
+ <path
+ d="M 905,184 C 905.1398,185.885 903.115,183.8602 905,184 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1695" />
+ <path
+ d="M 927,184 C 928.885,183.8602 926.8603,185.885 927,184 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1696" />
+ <path
+ d="M 1046,184 C 1050.914,183.88 1043.028,188.047 1046,184 z "
+ style="fill:#999;stroke:#999"
+ id="path1697" />
+ <path
+ d="M 219,187 L 215,185 L 219,187 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1698" />
+ <path
+ d="M 923,186 L 924.75,185.25 L 923,186 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1699" />
+ <path
+ d="M 479,186 C 479.1397,187.885 477.115,185.8602 479,186 z "
+ style="fill:#111;stroke:#111"
+ id="path1700" />
+ <path
+ d="M 900,186 C 901.885,185.8602 899.8603,187.885 900,186 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1701" />
+ <path
+ d="M 923,186 L 920.25,186.75 L 923,186 z "
+ style="fill:#111;stroke:#111"
+ id="path1702" />
+ <path
+ d="M 221,188 L 219,187 L 221,188 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1703" />
+ <path
+ d="M 223,189 L 221,188 L 223,189 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1704" />
+ <path
+ d="M 916,188 L 917.75,188.75 L 916,188 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1705" />
+ <path
+ d="M 224,190 L 223,189 L 224,190 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1706" />
+ <path
+ d="M 226,192 L 224,190 L 226,192 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1707" />
+ <path
+ d="M 228,194 L 226,192 L 228,194 z "
+ style="fill:#111;stroke:#111"
+ id="path1708" />
+ <path
+ d="M 301,192 C 301.1397,193.885 299.115,191.8602 301,192 z "
+ style="fill:#111;stroke:#111"
+ id="path1709" />
+ <path
+ d="M 229,195 L 228,194 L 229,195 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1710" />
+ <path
+ d="M 230,196 L 229,195 L 230,196 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1711" />
+ <path
+ d="M 231,197 L 230,196 L 231,197 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1712" />
+ <path
+ d="M 232,198 L 231,197 L 232,198 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1713" />
+ <path
+ d="M 233,199 L 232,198 L 233,199 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1714" />
+ <path
+ d="M 234,200 L 233,199 L 234,200 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1715" />
+ <path
+ d="M 235,201 L 234,200 L 235,201 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1716" />
+ <path
+ d="M 1013,200 L 1016.75,200.25 L 1010.75,205.75 L 1009.25,205.75 L 1011.25,203.25 L 1013,200 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path1717" />
+ <path
+ d="M 196,201 C 197.885,200.8602 195.8602,202.885 196,201 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1718" />
+ <path
+ d="M 235,201 C 236.885,200.8602 234.8602,202.885 235,201 z "
+ style="fill:#111;stroke:#111"
+ id="path1719" />
+ <path
+ d="M 201,204 L 198.25,202.25 L 201,204 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1720" />
+ <path
+ d="M 204,206 L 201,204 L 204,206 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1721" />
+ <path
+ d="M 204,206 C 205.449,205.5827 207.612,207.213 206.75,208.75 L 204,206 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1722" />
+ <path
+ d="M 209,210 C 207.115,210.1398 209.1398,208.115 209,210 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1723" />
+ <path
+ d="M 275,209 C 276.885,208.8602 274.8603,210.885 275,209 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1724" />
+ <path
+ d="M 1033,209 L 1036.25,212.75 L 1043.75,210.25 L 1021.75,227.75 C 1015.479,231.034 1010.67,236.784 1006.25,242.25 L 1006.25,244.75 L 1007.25,245.75 L 1013.25,246.25 L 1024.75,236.75 L 1043.75,226.25 L 1045.75,227.75 L 1041.75,233.75 C 1030.55,239.257 1023.09,250.34 1016.25,260.25 L 1015.25,264.75 L 1017.75,266.75 L 1017,272 L 1016.25,271.75 L 1009.75,255.25 L 1000,239 L 1006.75,231.75 L 1031.75,210.75 L 1033,209 z "
+ style="fill:#e2e2e2;stroke:#e2e2e2"
+ id="path1725" />
+ <path
+ d="M 210,211 L 209,210 L 210,211 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1726" />
+ <path
+ d="M 198,212 L 194.25,211.25 L 198,212 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1727" />
+ <path
+ d="M 211,212 L 210,211 L 211,212 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1728" />
+ <path
+ d="M 200,213 L 198,212 L 200,213 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1729" />
+ <path
+ d="M 211,212 L 212.75,213.75 L 211,212 z "
+ style="fill:#111;stroke:#111"
+ id="path1730" />
+ <path
+ d="M 202,214 L 200,213 L 202,214 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1731" />
+ <path
+ d="M 202,214 L 203.75,214.75 L 202,214 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1732" />
+ <path
+ d="M 1045,214 C 1048.035,214.7812 1043.519,217.546 1043.25,215.25 L 1044,215 L 1045,214 z "
+ style="fill:#949494;stroke:#949494"
+ id="path1733" />
+ <path
+ d="M 215,216 C 213.115,216.1398 215.1398,214.115 215,216 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1734" />
+ <path
+ d="M 209,218 L 206.25,216.25 L 209,218 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1735" />
+ <path
+ d="M 216,217 L 215,216 L 216,217 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1736" />
+ <path
+ d="M 218,219 L 216,217 L 218,219 z "
+ style="fill:#111;stroke:#111"
+ id="path1737" />
+ <path
+ d="M 193,218 C 194.885,217.8602 192.8602,219.885 193,218 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1738" />
+ <path
+ d="M 209,218 C 210.885,217.8602 208.8602,219.885 209,218 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1739" />
+ <path
+ d="M 198,220 L 195.25,219.25 L 198,220 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1740" />
+ <path
+ d="M 211,219 L 212.75,220.75 L 211,219 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1741" />
+ <path
+ d="M 219,220 L 218,219 L 219,220 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1742" />
+ <path
+ d="M 203,222 L 198,220 L 203,222 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1743" />
+ <path
+ d="M 225,226 C 221.509,226.7559 219.264,222.851 219,220 C 221.864,220.9114 224.089,223.136 225,226 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1744" />
+ <path
+ d="M 217,224 C 215.551,224.4173 213.388,222.787 214.25,221.25 L 217,224 z "
+ style="fill:#111;stroke:#111"
+ id="path1745" />
+ <path
+ d="M 205,223 L 203,222 L 205,223 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1746" />
+ <path
+ d="M 207,224 L 205,223 L 207,224 z "
+ style="fill:#111;stroke:#111"
+ id="path1747" />
+ <path
+ d="M 1016,238 L 1022.75,231.75 C 1026.606,227.345 1031.761,224.832 1037,223 L 1038.75,223.25 L 1038.75,223.75 L 1017.75,237.75 L 1016,238 z "
+ style="fill:#b0b0b0;stroke:#b0b0b0"
+ id="path1748" />
+ <path
+ d="M 182,224 L 190.75,228.75 L 190,229 L 182,224 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1749" />
+ <path
+ d="M 207,224 C 208.885,223.8602 206.8602,225.885 207,224 z "
+ style="fill:#070707;stroke:#070707"
+ id="path1750" />
+ <path
+ d="M 217,224 C 218.885,223.8602 216.8602,225.885 217,224 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1751" />
+ <path
+ d="M 213,229 L 209.25,226.25 L 213,229 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1752" />
+ <path
+ d="M 226,227 L 225,226 L 226,227 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1753" />
+ <path
+ d="M 229,229 L 226,227 L 229,229 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1754" />
+ <path
+ d="M 193,230 C 191.115,230.1398 193.1398,228.115 193,230 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1755" />
+ <path
+ d="M 213,229 L 214.75,230.75 L 213,229 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1756" />
+ <path
+ d="M 230,230 L 229,229 L 230,230 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1757" />
+ <path
+ d="M 195,231 L 193,230 L 195,231 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1758" />
+ <path
+ d="M 232,233 L 230,230 L 232,233 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1759" />
+ <path
+ d="M 195,231 C 196.162,230.8355 198.859,230.8198 197.75,232.75 L 195,231 z "
+ style="fill:#111;stroke:#111"
+ id="path1760" />
+ <path
+ d="M 217,232 C 215.115,232.1398 217.1398,230.115 217,232 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1761" />
+ <path
+ d="M 220,234 L 217,232 L 220,234 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1762" />
+ <path
+ d="M 202,235 L 199.25,233.25 L 202,235 z "
+ style="fill:#111;stroke:#111"
+ id="path1763" />
+ <path
+ d="M 233,234 L 232,233 L 233,234 z "
+ style="fill:#111;stroke:#111"
+ id="path1764" />
+ <path
+ d="M 220,234 L 221.75,235.75 L 220,234 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1765" />
+ <path
+ d="M 234,235 L 233,234 L 234,235 z "
+ style="fill:#111;stroke:#111"
+ id="path1766" />
+ <path
+ d="M 179,236 L 177.25,235.25 L 179,236 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1767" />
+ <path
+ d="M 204,236 L 202,235 L 204,236 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1768" />
+ <path
+ d="M 236,237 L 234,235 L 236,237 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1769" />
+ <path
+ d="M 1040,240 L 1041.25,238.25 L 1050.75,235.25 L 1041.75,239.75 L 1040,240 z "
+ style="fill:#a1a1a1;stroke:#a1a1a1"
+ id="path1770" />
+ <path
+ d="M 1064,235 L 1066.75,235.75 C 1061.267,238.199 1056.31,241.5 1052.25,246.25 L 1052.75,247.75 L 1050.75,249.75 L 1044.75,252.75 L 1029.75,260.75 L 1028.25,260.75 L 1054.25,239.25 L 1064,235 z "
+ style="fill:#cacaca;stroke:#cacaca"
+ id="path1771" />
+ <path
+ d="M 183,238 L 179,236 L 183,238 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1772" />
+ <path
+ d="M 205,237 L 204,236 L 205,237 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1773" />
+ <path
+ d="M 224,237 C 222.115,237.1398 224.1398,235.115 224,237 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1774" />
+ <path
+ d="M 208,239 L 205,237 L 208,239 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1775" />
+ <path
+ d="M 225,238 L 224,237 L 225,238 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1776" />
+ <path
+ d="M 237,238 L 236,237 L 237,238 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1777" />
+ <path
+ d="M 183,238 C 184.885,237.8602 182.8602,239.885 183,238 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1778" />
+ <path
+ d="M 226,239 L 225,238 L 226,239 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1779" />
+ <path
+ d="M 237,238 C 238.885,237.8602 236.8602,239.885 237,238 z "
+ style="fill:#111;stroke:#111"
+ id="path1780" />
+ <path
+ d="M 1016,238 C 1016.1398,239.885 1014.115,237.8602 1016,238 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1781" />
+ <path
+ d="M 186,240 C 184.115,240.1398 186.1398,238.115 186,240 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1782" />
+ <path
+ d="M 208,239 C 209.885,238.8602 207.8602,240.885 208,239 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1783" />
+ <path
+ d="M 227,240 L 226,239 L 227,240 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1784" />
+ <path
+ d="M 186,240 L 187.75,240.75 L 186,240 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1785" />
+ <path
+ d="M 229,242 L 227,240 L 229,242 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1786" />
+ <path
+ d="M 1013,240 C 1014.885,239.8602 1012.8603,241.885 1013,240 z "
+ style="fill:#919191;stroke:#919191"
+ id="path1787" />
+ <path
+ d="M 1040,240 L 1037,242 L 1040,240 z "
+ style="fill:#939393;stroke:#939393"
+ id="path1788" />
+ <path
+ d="M 191,243 L 189.25,241.25 L 191,243 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1789" />
+ <path
+ d="M 214,244 L 210.25,241.25 L 214,244 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1790" />
+ <path
+ d="M 229,242 L 230.75,244.75 L 229,242 z "
+ style="fill:#111;stroke:#111"
+ id="path1791" />
+ <path
+ d="M 1037,242 C 1037.1398,243.885 1035.115,241.8602 1037,242 z "
+ style="fill:#929292;stroke:#929292"
+ id="path1792" />
+ <path
+ d="M 193,244 L 191,243 L 193,244 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1793" />
+ <path
+ d="M 162,244 L 165.75,245.25 L 165.75,245.75 C 164.288,246.5947 162.298,245.6127 162,244 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1794" />
+ <path
+ d="M 194,245 L 193,244 L 194,245 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1795" />
+ <path
+ d="M 216,246 L 214,244 L 216,246 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1796" />
+ <path
+ d="M 197,247 L 194,245 L 197,247 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1797" />
+ <path
+ d="M 168,247 C 166.115,247.1398 168.1398,245.115 168,247 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1798" />
+ <path
+ d="M 217,247 L 216,246 L 217,247 z "
+ style="fill:black;stroke:black"
+ id="path1799" />
+ <path
+ d="M 232,246 L 232.75,247.75 L 232,246 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1800" />
+ <path
+ d="M 171,248 L 168,247 L 171,248 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1801" />
+ <path
+ d="M 171,248 L 175,247 L 182.25,250.75 C 189.189,251.787 196.24,254.391 201.25,259.75 L 211.75,266.25 L 216.75,271.25 L 223.75,279.25 L 222.75,282.75 L 218.25,278.75 L 213.75,271.25 L 199.75,260.25 L 173.25,249.75 L 171,248 z "
+ style="fill:#070707;stroke:#070707"
+ id="path1802" />
+ <path
+ d="M 199,248 L 197,247 L 199,248 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1803" />
+ <path
+ d="M 218,248 L 217,247 L 218,248 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1804" />
+ <path
+ d="M 201,249 L 199,248 L 201,249 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1805" />
+ <path
+ d="M 219,249 L 218,248 L 219,249 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1806" />
+ <path
+ d="M 204,251 L 201,249 L 204,251 z "
+ style="fill:#111;stroke:#111"
+ id="path1807" />
+ <path
+ d="M 225,255 L 219,249 C 221.666,249.3068 224.693,252.334 225,255 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1808" />
+ <path
+ d="M 206,252 L 204,251 L 206,252 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1809" />
+ <path
+ d="M 475,310 L 461,297 C 456.874,300.636 464.366,306.802 466,311 L 470.75,324.75 L 466.75,319.25 L 461.75,310.25 C 460.031,307.165 458.978,302.322 455.25,301.25 L 454,304 C 459.4,313.319 465.74,322.82 465.75,333.75 L 460.25,323.75 C 456.114,317.796 454.682,306.43 446.25,305.25 L 458.25,330.75 L 459.75,335.75 L 444,309 L 442.25,310.25 L 444,314 C 446.954,318.039 450.579,322.734 450.75,327.75 L 449.75,326.25 L 444.75,317.25 C 442.615,315.59 441.905,310.83 438.25,312.25 L 437.25,313.75 C 444.156,323.9 452.72,335.83 451.25,347.75 L 449,339 L 444.25,328.75 L 436.75,315.25 C 430.305,314.088 435.9177,320.093 435.75,322.75 L 433.25,320.75 L 430.25,317.25 L 429,321 C 436.008,331.94 441.84,345.73 442.25,357.75 C 436.615,346.71 435.613,331.1 424.25,324.25 L 431.75,343.75 L 426.75,334.25 C 424.245,331.605 419.67,322.49 416,328 C 425.286,335.623 430.23,348.27 431.75,359.75 L 424.75,348.25 L 413.25,336.75 C 409.46,333.817 409.448,339.544 411.75,341.25 L 423,359 C 424.859,362.214 422.0676,362.549 419.25,361.75 C 416.037,357.065 413.249,344.75 407.25,347.25 L 416.75,366.75 L 404.25,347.75 C 403.046,346.8404 401.59,343.833 400.25,346.25 L 414,378 L 408.25,367.75 L 402.25,355.75 C 403.1411,351.964 396.859,351.964 397.75,355.75 C 395.962,355.4336 393.74,352.643 392.25,355.25 L 398.75,369.75 L 396,368 L 393,369 C 396.049,373.671 398.532,379.01 398.75,384.75 C 395.545,377.146 390.731,371.37 383.25,369.25 L 385.75,378.75 L 374.25,373.25 L 379.75,384.75 L 371.25,380.25 L 379.75,397.75 L 367.25,384.25 L 368.75,389.25 L 357.25,388.25 L 362.75,401.75 L 357.75,397.25 L 353,391 L 383.75,355.75 C 414.48,311.86 454.81,268.88 507.05,253.95 C 518.72,252.35 530.29,250.236 542.05,252.95 C 573.72,249.92 604.62,264.88 627.8,285.2 L 641.8,297.2 C 645.456,305.386 645.984,316.3 644.3,324.7 C 641.493,317.636 643,304.58 635.3,302.2 L 634.3,304.7 C 632.193,299.513 629.785,293.16 623.3,295.2 C 624.44,297.78 625.751,307.74 622.05,302.95 L 620.05,288.95 L 618.05,304.95 L 616.8,312.7 L 610.8,276.2 C 609.363,275.6447 606.322,275.5234 607.05,277.95 L 605.3,291.7 C 606.365,295.331 609.534,300.676 607.05,303.95 L 601.8,293.2 L 598.8,277.2 L 588.3,263.2 L 586.05,286.95 L 588.05,296.95 L 587.05,299.95 L 574.8,261.2 L 571.3,262.2 C 572.868,265.82 576.137,269.253 574.8,273.7 C 574.3066,271.98 571.923,263.5 569.05,268.95 L 571.8,284.7 L 565.05,272.95 L 563.05,274.95 C 566.521,285 574.48,294.95 571.8,305.7 L 559.05,278.95 L 554.3,268.7 L 551.05,263.95 C 545.656,266.375 557.858,276.83 549.3,276.2 L 548.3,277.7 C 550.946,282.43 555.729,286.843 554.8,292.7 L 550.05,285.95 L 544.8,276.2 L 540.3,275.2 L 551.8,302.7 L 530.8,272.2 L 528.8,273.7 L 534.05,283.95 L 539.05,294.95 L 538.8,299.7 L 520.3,271.2 L 520.05,273.95 C 523.015,276.801 523.036,280.161 522.3,283.7 L 525.05,294.95 L 524.8,300.7 C 516.947,297.077 521.112,280.72 511.3,279.2 L 516.8,299.7 C 502.43,300.2978 505.42,274.7 494.3,270.2 L 503.8,295.7 L 490.8,287.2 L 486.3,282.7 C 485.133,279.99 479.414,280.309 480.3,283.7 C 485.591,285.217 495.25,295.78 490.3,298.2 C 488.938,300.293 488.074,299.638 487.3,297.7 C 485.94,295.68 485.119,289.521 481.8,291.7 C 467.12,278.69 486.473,307.73 475.3,307.2 L 475.05,309.95 L 475,310 z "
+ style="fill:#e8e8e8;stroke:#e8e8e8"
+ id="path1810" />
+ <path
+ d="M 209,254 L 206,252 L 209,254 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1811" />
+ <path
+ d="M 1052,253 L 1059.75,253.25 L 1054,256 L 1045.25,259.25 L 1042.75,262.75 C 1035.218,266.136 1027.75,270.79 1022.25,276.75 L 1024.25,278.75 L 1043.25,266.25 L 1057,260 L 1068.75,257.25 L 1059.25,261.25 L 1058.75,265.75 L 1023.25,282.25 L 1023.25,284.75 L 1038.25,278.25 L 1054,272 C 1057.27,269.884 1060.166,270.328 1063.75,271.25 C 1060.099,272.71 1054.541,273.419 1051.75,277.75 C 1044.901,277.8576 1039.2,281.79 1034.25,286.25 L 1042.75,286.25 L 1023.25,300.25 L 1031.75,299.25 L 1031.75,301.75 L 1021.25,307.75 L 1019.25,276.25 C 1027.65,265.58 1040.17,257.94 1052,253 z "
+ style="fill:#d2d2d2;stroke:#d2d2d2"
+ id="path1812" />
+ <path
+ d="M 211,256 L 209,254 L 211,256 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1813" />
+ <path
+ d="M 228,258 C 226.309,258.4122 224.588,256.691 225,255 L 228,258 z "
+ style="fill:#111;stroke:#111"
+ id="path1814" />
+ <path
+ d="M 213,257 L 211,256 L 213,257 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1815" />
+ <path
+ d="M 214,258 L 213,257 L 214,258 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1816" />
+ <path
+ d="M 214,258 C 215.885,257.8603 213.8602,259.885 214,258 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1817" />
+ <path
+ d="M 231,262 L 228,258 L 231,262 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1818" />
+ <path
+ d="M 218,262 L 216.25,259.25 L 218,262 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1819" />
+ <path
+ d="M 720,259 L 722.75,260.25 L 723,264 L 724,278 L 722.75,284.75 L 721.25,285.75 L 721,262 L 720,259 z "
+ style="fill:#bfbfbf;stroke:#bfbfbf"
+ id="path1820" />
+ <path
+ d="M 727,259 L 729.75,259.25 L 732,265 L 733,271 L 732,273 C 733.607,276.78 733.607,282.22 732,286 L 731.75,291.75 L 729.75,295.75 L 729.25,295.75 L 729,276 L 728,269 L 727,259 z "
+ style="fill:#cecece;stroke:#cecece"
+ id="path1821" />
+ <path
+ d="M 734,259 C 735.864,258.6679 739.667,258.99954 738.75,261.75 L 738.25,261.75 L 734,259 z "
+ style="fill:#a0a0a0;stroke:#a0a0a0"
+ id="path1822" />
+ <path
+ d="M 165,264 L 157.25,261.75 L 157.25,261.25 L 162,262 L 165,264 z "
+ style="fill:#111;stroke:#111"
+ id="path1823" />
+ <path
+ d="M 220,264 L 218,262 L 220,264 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1824" />
+ <path
+ d="M 232,263 L 231,262 L 232,263 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1825" />
+ <path
+ d="M 707,262 C 710.2,261.451 708.876,265.946 708.75,267.75 C 704.956,269.453 705.522,263.738 707,262 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path1826" />
+ <path
+ d="M 1068,262 L 1070.75,262.75 L 1068,262 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1827" />
+ <path
+ d="M 233,264 L 232,263 L 233,264 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1828" />
+ <path
+ d="M 168,265 L 165,264 L 168,265 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1829" />
+ <path
+ d="M 221,265 L 220,264 L 221,265 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1830" />
+ <path
+ d="M 234,265 L 233,264 L 234,265 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1831" />
+ <path
+ d="M 169,266 L 168,265 L 169,266 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1832" />
+ <path
+ d="M 186,266 L 182.25,265.25 L 186,266 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1833" />
+ <path
+ d="M 224,269 L 221,265 L 224,269 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1834" />
+ <path
+ d="M 235,266 L 234,265 L 235,266 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1835" />
+ <path
+ d="M 745,265 L 750.25,267.75 L 758.25,271.75 L 762.75,271.25 L 766,278 L 764.75,287.75 L 763,289 L 761.75,285.25 C 759.446,284.082 757.743,281.608 758.75,279.25 L 756,275 L 754,271 C 751.518,271.5057 751.642,274.485 753,276 L 752,284 L 750.75,275.25 C 747.717,275.32651 744.813,274.8996 746,271 C 744.61,269.226 742.977,267.098 745,265 z "
+ style="fill:#d0d0d0;stroke:#d0d0d0"
+ id="path1836" />
+ <path
+ d="M 173,268 L 169,266 L 173,268 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1837" />
+ <path
+ d="M 188,267 L 186,266 L 188,267 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1838" />
+ <path
+ d="M 235,266 C 236.885,265.8603 234.8602,267.885 235,266 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1839" />
+ <path
+ d="M 759,266 L 761.75,267.75 L 759,266 z "
+ style="fill:#979797;stroke:#979797"
+ id="path1840" />
+ <path
+ d="M 190,268 L 188,267 L 190,268 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1841" />
+ <path
+ d="M 175,269 L 173,268 L 175,269 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1842" />
+ <path
+ d="M 190,268 L 191.75,268.75 L 190,268 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1843" />
+ <path
+ d="M 177,270 L 175,269 L 177,270 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1844" />
+ <path
+ d="M 224,269 L 226.75,272.75 L 224,269 z "
+ style="fill:#111;stroke:#111"
+ id="path1845" />
+ <path
+ d="M 691,269 C 696.307,269.4883 693.38,277.176 693.75,280.75 L 690.25,280.75 C 687.858,277.037 689.237,272.536 691,269 z "
+ style="fill:#cecece;stroke:#cecece"
+ id="path1846" />
+ <path
+ d="M 178,271 L 177,270 L 178,271 z "
+ style="fill:#060606;stroke:#060606"
+ id="path1847" />
+ <path
+ d="M 196,271 L 194.25,270.25 L 196,271 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1848" />
+ <path
+ d="M 182,273 L 178,271 L 182,273 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1849" />
+ <path
+ d="M 199,273 L 196,271 L 199,273 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1850" />
+ <path
+ d="M 702,271 L 704,272 L 711.75,272.25 L 711,279 C 711.1045,281.061 711.5389,285.605 708.25,284.75 L 707.75,280.25 L 705.25,280.25 L 702.75,288.75 L 702.25,288.75 L 702,281 C 700.441,278.143 700.441,273.857 702,271 z "
+ style="fill:#d0d0d0;stroke:#d0d0d0"
+ id="path1851" />
+ <path
+ d="M 185,275 L 182,273 L 185,275 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1852" />
+ <path
+ d="M 203,275 L 199,273 L 203,275 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1853" />
+ <path
+ d="M 681,273 L 682,275 L 677,290 L 675.75,299.75 L 675.25,299.75 L 675,294 L 676,292 L 677,283 C 676.5645,279.019 678.576,275.845 681,273 z "
+ style="fill:#a9a9a9;stroke:#a9a9a9"
+ id="path1854" />
+ <path
+ d="M 229,275 C 227.115,275.1397 229.1398,273.115 229,275 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1855" />
+ <path
+ d="M 775,274 C 782.058,275.33 777.88,284.91 779,290 L 778.25,290.75 L 777.75,281.25 C 774.007,280.8385 775.123,276.606 775,274 z "
+ style="fill:#aeaeae;stroke:#aeaeae"
+ id="path1856" />
+ <path
+ d="M 187,276 L 185,275 L 187,276 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1857" />
+ <path
+ d="M 206,277 L 203,275 L 206,277 z "
+ style="fill:#111;stroke:#111"
+ id="path1858" />
+ <path
+ d="M 229,275 C 230.885,274.8603 228.8602,276.885 229,275 z "
+ style="fill:#090909;stroke:#090909"
+ id="path1859" />
+ <path
+ d="M 189,277 L 187,276 L 189,277 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1860" />
+ <path
+ d="M 190,278 L 189,277 L 190,278 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1861" />
+ <path
+ d="M 209,279 L 206,277 L 209,279 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1862" />
+ <path
+ d="M 192,279 L 190,278 L 192,279 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1863" />
+ <path
+ d="M 746,278 L 746.75,278.25 L 744.75,294.75 L 743.25,296.75 L 743,295 L 745,285 L 746,278 z "
+ style="fill:#969696;stroke:#969696"
+ id="path1864" />
+ <path
+ d="M 1066,278 L 1069.75,278.75 L 1056,284 L 1054.25,283.75 L 1055.75,282.75 L 1060.25,280.25 L 1066,278 z "
+ style="fill:#9a9a9a;stroke:#9a9a9a"
+ id="path1865" />
+ <path
+ d="M 194,280 L 192,279 L 194,280 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1866" />
+ <path
+ d="M 210,280 L 209,279 L 210,280 z "
+ style="fill:#111;stroke:#111"
+ id="path1867" />
+ <path
+ d="M 672,279 L 672.75,283.75 L 672,284 C 669.39,281.263 666.594,285.529 668.75,287.75 L 667.75,288.75 L 662.25,285.25 L 671.75,279.75 L 672,279 z "
+ style="fill:#c1c1c1;stroke:#c1c1c1"
+ id="path1868" />
+ <path
+ d="M 783,279 L 786.75,279.25 C 786.67474,283.435 789.082,290.84 783.25,291.75 L 783,279 z "
+ style="fill:#c8c8c8;stroke:#c8c8c8"
+ id="path1869" />
+ <path
+ d="M 197,282 L 194,280 L 197,282 z "
+ style="fill:#111;stroke:#111"
+ id="path1870" />
+ <path
+ d="M 213,282 L 210,280 L 213,282 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1871" />
+ <path
+ d="M 199,283 L 197,282 L 199,283 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1872" />
+ <path
+ d="M 214,283 L 213,282 L 214,283 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1873" />
+ <path
+ d="M 199,283 C 200.885,282.8603 198.8602,284.885 199,283 z "
+ style="fill:#0b0b0b;stroke:#0b0b0b"
+ id="path1874" />
+ <path
+ d="M 217,285 L 214,283 L 217,285 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1875" />
+ <path
+ d="M 793,283 C 794.921,282.3866 796.985,283.6306 795.75,285.75 C 794.213,286.6122 792.583,284.449 793,283 z "
+ style="fill:#adadad;stroke:#adadad"
+ id="path1876" />
+ <path
+ d="M 202,285 C 200.115,285.1397 202.1398,283.115 202,285 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1877" />
+ <path
+ d="M 593,284 L 593.75,284.25 L 594.75,297.75 L 593.25,296.75 L 593,284 z "
+ style="fill:#b9b9b9;stroke:#b9b9b9"
+ id="path1878" />
+ <path
+ d="M 203,286 L 202,285 L 203,286 z "
+ style="fill:#0a0a0a;stroke:#0a0a0a"
+ id="path1879" />
+ <path
+ d="M 217,285 L 218.75,285.75 L 217,285 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1880" />
+ <path
+ d="M 205,287 L 203,286 L 205,287 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1881" />
+ <path
+ d="M 208,289 L 205,287 L 208,289 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1882" />
+ <path
+ d="M 800,288 L 804.75,289.25 L 813.75,295.25 L 812,301 L 812.75,310.75 L 809.25,311.75 C 809.628,307.893 806.393,302.81 802.25,305.75 L 801,296 L 800.75,291.25 L 800,288 z "
+ style="fill:#e4e4e4;stroke:#e4e4e4"
+ id="path1883" />
+ <path
+ d="M 1047,288 L 1049.75,289.75 L 1037,296 L 1036.25,295.75 L 1037.25,294.25 L 1041,292 L 1046.75,288.75 L 1047,288 z "
+ style="fill:#a1a1a1;stroke:#a1a1a1"
+ id="path1884" />
+ <path
+ d="M 209,290 L 208,289 L 209,290 z "
+ style="fill:black;stroke:black"
+ id="path1885" />
+ <path
+ d="M 212,292 L 209,290 L 212,292 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1886" />
+ <path
+ d="M 646,290 C 647.425,289.7879 649.671,289.8865 649,292 L 649.75,296.75 L 646,290 z "
+ style="fill:#a2a2a2;stroke:#a2a2a2"
+ id="path1887" />
+ <path
+ d="M 1052,303 L 1051.75,303.75 L 1028.25,311.25 L 1023.25,312.75 L 1024.25,310.25 C 1037.32,301.804 1051.8,296.58 1066,291 L 1077.75,290.25 L 1077.75,290.75 L 1058,297 L 1055,298 C 1051.437,296.684 1045.995,302.68 1052,303 z "
+ style="fill:#c6c6c6;stroke:#c6c6c6"
+ id="path1888" />
+ <path
+ d="M 212,292 C 213.885,291.8603 211.8602,293.885 212,292 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1889" />
+ <path
+ d="M 640,292 C 641.834,291.3347 642.559,293.431 641.75,294.75 L 640,292 z "
+ style="fill:#999;stroke:#999"
+ id="path1890" />
+ <path
+ d="M 498,296 L 499.75,297.25 L 506.75,309.75 L 505.75,310.75 L 503.25,308.75 L 498,296 z "
+ style="fill:#ababab;stroke:#ababab"
+ id="path1891" />
+ <path
+ d="M 783,296 L 783.75,298.75 L 783,296 z "
+ style="fill:#a0a0a0;stroke:#a0a0a0"
+ id="path1892" />
+ <path
+ d="M 188,297 L 190,297 L 190.75,297.25 L 199.75,303.75 L 194.25,303.75 C 194.5274,300.343 190.943,299.138 188.25,298.75 L 188,297 z "
+ style="fill:#090909;stroke:#090909"
+ id="path1893" />
+ <path
+ d="M 651,299 L 651.75,299.25 C 652.6801,301.754 656.133,306.223 652.25,307.75 L 651,299 z "
+ style="fill:#acacac;stroke:#acacac"
+ id="path1894" />
+ <path
+ d="M 714,299 C 715.162,298.8355 717.859,298.8197 716.75,300.75 C 715.431,301.5589 713.335,300.83368 714,299 z "
+ style="fill:#999;stroke:#999"
+ id="path1895" />
+ <path
+ d="M 544,300 L 544.75,300.25 L 548,310 L 548.75,316.75 L 547.25,315.75 L 545,305 L 544,300 z "
+ style="fill:#b0b0b0;stroke:#b0b0b0"
+ id="path1896" />
+ <path
+ d="M 599,300 L 600.75,300.25 C 603.945,306.052 603.075,313.17 602.75,319.75 L 602.25,319.75 L 599,310 L 599,300 z "
+ style="fill:#c2c2c2;stroke:#c2c2c2"
+ id="path1897" />
+ <path
+ d="M 818,302 L 824.75,306.25 L 824.75,306.75 L 822.75,313.75 L 819.25,311.75 L 818,304 L 818,302 z "
+ style="fill:#c8c8c8;stroke:#c8c8c8"
+ id="path1898" />
+ <path
+ d="M 658,303 C 659.316,303.5518 662.088,305.554 659.75,306.75 L 658,303 z "
+ style="fill:#9a9a9a;stroke:#9a9a9a"
+ id="path1899" />
+ <path
+ d="M 204,306 L 201.25,304.25 L 204,306 z "
+ style="fill:#111;stroke:#111"
+ id="path1900" />
+ <path
+ d="M 591,305 L 592.75,305.25 L 592.75,314.75 L 592.25,314.75 L 591,305 z "
+ style="fill:#adadad;stroke:#adadad"
+ id="path1901" />
+ <path
+ d="M 207,308 L 204,306 L 207,308 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1902" />
+ <path
+ d="M 1074,307 L 1096,307 C 1103.803,307.1919 1111.97,306.1147 1119,309 L 1122.75,309.25 L 1122.75,311.75 L 1120,312 L 1118,311 L 1073,311 L 1071,312 L 1061,312 L 1054,314 L 1051.25,313.75 L 1051.25,310.25 L 1073,308 L 1074,307 z "
+ style="fill:#393939;stroke:#393939"
+ id="path1903" />
+ <path
+ d="M 208,309 L 207,308 L 208,309 z "
+ style="fill:#111;stroke:#111"
+ id="path1904" />
+ <path
+ d="M 209,310 L 208,309 L 209,310 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1905" />
+ <path
+ d="M 523,309 L 526,315 C 525.3527,317.675 526.7061,319.703 528.75,321.25 L 527.75,323.75 L 525,318 L 523,309 z "
+ style="fill:#adadad;stroke:#adadad"
+ id="path1906" />
+ <path
+ d="M 827,309 L 827.75,309.25 L 827.75,316.75 L 827.25,316.75 L 827,309 z "
+ style="fill:#939393;stroke:#939393"
+ id="path1907" />
+ <path
+ d="M 213,313 L 209,310 L 213,313 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1908" />
+ <path
+ d="M 475,310 L 475.75,310.25 L 478,319 L 479.75,330.75 L 477.25,328.75 L 476,317 L 475,310 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path1909" />
+ <path
+ d="M 186,312 C 189.021,312.3772 193.165,312.9935 194.75,315.75 L 186.25,312.75 L 186,312 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path1910" />
+ <path
+ d="M 659,312 L 660.75,312.25 L 661.75,321.75 L 661.25,321.75 L 660.75,315.25 C 658.964,315.0462 657.418,313.488 659,312 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1911" />
+ <path
+ d="M 214,314 L 213,313 L 214,314 z "
+ style="fill:#070707;stroke:#070707"
+ id="path1912" />
+ <path
+ d="M 688,313 L 688.75,313.25 L 690.75,319.75 L 688.75,317.25 C 687.02,316.187 686.65,314.486 688,313 z "
+ style="fill:#acacac;stroke:#acacac"
+ id="path1913" />
+ <path
+ d="M 215,315 L 214,314 L 215,315 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1914" />
+ <path
+ d="M 830,338 L 834,323 C 830.473,320.909 830.759,316.9 833,314 L 838.75,317.25 C 855.48,333.2 868.8,351.25 881,370 L 875.75,382.75 L 874.25,381.25 C 876.817,377.115 880.445,370.35 874.25,367.75 L 872,364 L 869.75,371.75 C 866.188,370.101 864.048,371.9272 861.25,373.75 L 859.25,360.75 L 856.25,353.25 C 855.8796,355.096 855.9371,359.394 853.25,358.75 L 854.75,339.25 L 848.25,345.75 L 848,343 L 847.75,338.25 L 841.25,348.75 L 840.75,335.25 C 839.431,334.4411 837.335,335.16632 838,337 L 837.75,338.75 L 836,329 L 830,338 z "
+ style="fill:#e2e2e2;stroke:#e2e2e2"
+ id="path1915" />
+ <path
+ d="M 216,316 L 215,315 L 216,316 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1916" />
+ <path
+ d="M 216,316 L 217.75,316.75 L 216,316 z "
+ style="fill:#121212;stroke:#121212"
+ id="path1917" />
+ <path
+ d="M 666,316 L 667.75,316.25 L 675.25,338.75 L 683,353 C 682.0544,360.649 684.114,369.18 690.75,373.25 C 694.566,381.653 704.5,382.943 712.75,381.75 C 714.619,380.669 718.036,379.084 716.75,376.25 L 715,376 C 709.037,379.483 699.05,379.276 695,373 L 695.75,371.75 L 693.75,351.25 C 695.762,357.798 696.007,367.31 705,368 L 727.75,375.25 C 723.909,385.54 711.54,389.53 701.25,389.25 L 699.75,392.75 L 697.25,392.75 C 684.76,383.442 677.89,368.09 677,353 L 668,331 L 668,323 L 666,316 z "
+ style="fill:#e0e0e0;stroke:#e0e0e0"
+ id="path1918" />
+ <path
+ d="M 670,316 L 676.75,316.25 L 678.75,318.25 L 687.25,335.75 L 688.25,336.75 L 690.75,337.25 L 688.75,347.75 L 685.25,347.75 L 678.25,335.75 L 670,316 z "
+ style="fill:#e4e4e4;stroke:#e4e4e4"
+ id="path1919" />
+ <path
+ d="M 489,317 L 489.75,317.25 L 493.75,327.75 L 491.25,327.75 L 490,323 L 489,317 z "
+ style="fill:#adadad;stroke:#adadad"
+ id="path1920" />
+ <path
+ d="M 148,322 L 159.75,322.25 L 159.75,322.75 L 148.25,322.75 L 148,322 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1921" />
+ <path
+ d="M 162,322 L 162.75,323.75 L 162,322 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1922" />
+ <path
+ d="M 696,322 L 698,325 L 698,340 L 694.25,346.75 L 696,336 L 696,322 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path1923" />
+ <path
+ d="M 141,323 L 145.75,323.75 L 141,323 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1924" />
+ <path
+ d="M 164,323 L 171.75,323.25 L 171.75,323.75 L 164.25,323.75 L 164,323 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1925" />
+ <path
+ d="M 702,323 L 704.75,323.25 C 704.4059,329.292 709.084,337.7 703,342 L 702.25,341.75 L 701.25,324.25 L 702,323 z "
+ style="fill:#cacaca;stroke:#cacaca"
+ id="path1926" />
+ <path
+ d="M 174,324 C 175.885,323.8603 173.8602,325.885 174,324 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1927" />
+ <path
+ d="M 203,330 C 205.836,329.0702 206.545,332.112 207.75,333.75 L 193,333 L 191,332 C 182.968,331.648 175.12,332.2494 168.25,335.75 L 168,330 L 168.25,329.25 L 180,326 L 176.25,324.25 C 182.877,323.8143 191.14,323.053 195.75,327.75 L 183.25,328.25 L 184,329 L 192,330 L 201,332 L 203.75,331.75 L 203,330 z "
+ style="fill:#080808;stroke:#080808"
+ id="path1928" />
+ <path
+ d="M 437,325 L 438.75,326.25 L 444,338 L 442.75,339.75 L 442.25,339.75 L 438.75,330.25 L 437,325 z "
+ style="fill:#acacac;stroke:#acacac"
+ id="path1929" />
+ <path
+ d="M 203,330 L 198.25,328.75 L 198.25,328.25 L 203,330 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1930" />
+ <path
+ d="M 1027,328 C 1030.419,326.768 1030.762,329.766 1033,331 C 1039.888,330.6371 1048.21,325.771 1054,331 L 1059.75,332.25 L 1059.25,334.75 L 1062.25,336.75 C 1071.592,339.297 1081.76,341.576 1089.75,346.75 L 1063,342 L 1061,341 L 1057.25,342.25 L 1055,345 L 1045.25,345.25 L 1055.75,351.25 L 1055,352 L 1037.25,350.25 L 1036.75,352.75 C 1029.625,356.655 1039.253,357.878 1041.75,359.75 L 1029.75,363.75 C 1027.591,363.9245 1026.806,365.897 1026.75,367.75 L 1019,370 L 1021.75,372.25 C 1020.9028,380.252 1033.22,370.41 1030.75,378.75 L 1022,381 L 1032,382 L 1032.75,382.75 C 1027.861,381.7549 1028.6,386.722 1031.75,387.75 C 1028.01,387.68921 1023.127,385.394 1020,389 C 1017.58,388.9744 1014.344,385.26 1013.75,389.75 C 1009.504,389.0719 1009.934,392.065 1010.75,394.75 C 1006.313,394.74033 1005.53,398.501 1008.75,400.75 C 1002.858,400.5944 1007.412,404.807 1006.75,407.75 L 1004.75,406.25 C 1001.388,399.18 992.49,410.103 1000.75,410.25 L 1002,413 L 1001,414 C 998.705,414.1273 996.143,412.103 995.75,415.75 L 992,421 L 982.25,412.75 L 982.25,410.25 L 994.25,398.25 C 1012.52,381.23 1018.4,356.86 1020.25,333.25 L 1027,328 z "
+ style="fill:#ececec;stroke:#ececec"
+ id="path1931" />
+ <path
+ d="M 161,329 C 164.035,329.7812 159.519,332.546 159.25,330.25 L 160,330 L 161,329 z "
+ style="fill:#131313;stroke:#131313"
+ id="path1932" />
+ <path
+ d="M 165,329 L 166.75,330.75 L 165,329 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1933" />
+ <path
+ d="M 710,329 L 717.75,331.25 L 719.25,334.75 L 721.75,335.25 L 725.75,340.75 L 723.25,340.25 C 719.374,345.399 713.809,348.668 707.25,346.75 L 707,344 L 709,339 L 710,329 z "
+ style="fill:#e6e6e6;stroke:#e6e6e6"
+ id="path1934" />
+ <path
+ d="M 758,332 C 759.449,331.5827 761.612,333.213 760.75,334.75 L 758,332 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1935" />
+ <path
+ d="M 182,335 L 195,335 L 204.75,336.25 L 203.75,339.75 L 199.75,339.25 L 185.75,340.75 L 179,340 L 158,341 L 156.25,341.75 L 156,341 L 156.25,340.25 L 168,340 L 178,339 L 186.75,337.75 L 182,335 z "
+ style="fill:#090909;stroke:#090909"
+ id="path1936" />
+ <path
+ d="M 179,336 C 180.885,335.8603 178.8602,337.885 179,336 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1937" />
+ <path
+ d="M 733,336 L 734.25,337.75 C 738.714,338.3115 735.777,342.287 736.25,344.75 C 737.607,347.276 739.344,344.8806 740.75,344.25 L 741.25,350.75 L 745.75,352.25 L 745,357 C 745.6672,361.781 739.914,363.327 736.25,363.25 C 731.111,366.213 723.45,369.329 718.25,364.75 L 718.25,364.25 C 725.686,362.41 729.27,355.389 732,349 L 732,344 L 733,336 z "
+ style="fill:#e9e9e9;stroke:#e9e9e9"
+ id="path1938" />
+ <path
+ d="M 830,338 L 826.75,350.75 L 826.25,350.75 L 829.25,338.25 L 830,338 z "
+ style="fill:#afafaf;stroke:#afafaf"
+ id="path1939" />
+ <path
+ d="M 151,341 L 154.75,341.75 L 151,341 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1940" />
+ <path
+ d="M 194,344 L 199.75,344.25 L 199.75,344.75 L 197,345 L 182.25,346.25 L 181.25,347.75 L 184,348 L 195,347 L 196,348 L 195,349 L 164.25,348.75 L 164.25,348.25 L 177,348 L 177.75,347.75 L 177.25,346.25 L 178.25,345.25 L 179.75,345.75 L 193.75,344.75 L 194,344 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1941" />
+ <path
+ d="M 797,346 L 797.75,347.75 C 795.872,348.6425 796.195,346.941 797,346 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path1942" />
+ <path
+ d="M 198,347 L 202.75,347.75 L 198,347 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1943" />
+ <path
+ d="M 159,348 L 159.75,349.75 L 137,350 L 136.25,349.25 L 158,349 L 159,348 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1944" />
+ <path
+ d="M 161,348 L 161.75,349.75 L 161,348 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1945" />
+ <path
+ d="M 663,348 L 664,349 L 665,354 L 669.25,377.75 L 670.75,379.25 C 671.6942,385.759 675.838,391.31 679.25,396.75 L 685.75,401.75 L 675.25,399.75 L 672.75,396.25 L 669.25,392.75 L 667.75,388.25 L 660,364 L 660,353 L 663,348 z "
+ style="fill:#dbdbdb;stroke:#dbdbdb"
+ id="path1946" />
+ <path
+ d="M 130,349 L 131.75,350.75 L 130,349 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1947" />
+ <path
+ d="M 133,349 L 133.75,350.75 L 133,349 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1948" />
+ <path
+ d="M 618,349 L 623.75,353.25 L 621.75,356.75 C 619.914,355.9467 617.297,356.1982 617.75,353.25 L 614.25,352.25 L 618,349 z "
+ style="fill:#c7c7c7;stroke:#c7c7c7"
+ id="path1949" />
+ <path
+ d="M 653,349 L 655,350 L 654,359 L 663.75,389.25 L 663.75,390.75 C 656.008,383.747 651.62,373.15 650,363 L 649,361 L 649,354 L 649.25,353.25 C 651.761,353.5283 652.919,351.18 653,349 z "
+ style="fill:#d7d7d7;stroke:#d7d7d7"
+ id="path1950" />
+ <path
+ d="M 124,350 L 128.75,350.75 L 124,350 z "
+ style="fill:#0e0e0e;stroke:#0e0e0e"
+ id="path1951" />
+ <path
+ d="M 703,350 L 708.75,352.25 C 710.137,357.091 717.751,355.364 720.25,352.25 L 723,351 L 724.75,351.25 L 721.75,356.75 C 720.8666,358.633 719.603,360.804 717,360 C 712.458,362.685 704.41,359.7791 703.75,354.25 L 703,350 z "
+ style="fill:#d6d6d6;stroke:#d6d6d6"
+ id="path1952" />
+ <path
+ d="M 192,353 L 196,353 L 197.75,353.75 L 192,355 L 187.25,355.75 L 192,353 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1953" />
+ <path
+ d="M 435,353 L 437.75,358.75 L 436,357 L 435,353 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1954" />
+ <path
+ d="M 758,354 L 761,355 L 759.75,357.75 C 757.304,358.862 758.104,355.3 758,354 z "
+ style="fill:#a8a8a8;stroke:#a8a8a8"
+ id="path1955" />
+ <path
+ d="M 178,355 L 185.75,355.25 L 185.75,355.75 L 178.25,355.75 L 178,355 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1956" />
+ <path
+ d="M 1044,355 L 1049,356 L 1056.75,358.25 L 1056.75,358.75 C 1055.212,361.207 1052.414,359.4001 1050.75,358.25 L 1044.25,355.75 L 1044,355 z "
+ style="fill:#aaa;stroke:#aaa"
+ id="path1957" />
+ <path
+ d="M 160,358 L 161,357 L 169,356 L 176.75,356.25 L 176,357 L 168,358 L 160,358 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1958" />
+ <path
+ d="M 203,356 L 206.75,356.75 L 203,356 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1959" />
+ <path
+ d="M 192,357 L 192.75,358.75 L 185,359 L 180.25,359.75 L 180.25,359.25 L 182,359 L 191.75,357.75 L 192,357 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1960" />
+ <path
+ d="M 197,357 L 200.75,357.75 L 197,357 z "
+ style="fill:#0c0c0c;stroke:#0c0c0c"
+ id="path1961" />
+ <path
+ d="M 404,369 L 403.25,368.75 L 399.75,362.25 L 398.25,357.25 L 398.75,357.25 L 404,369 z "
+ style="fill:#979797;stroke:#979797"
+ id="path1962" />
+ <path
+ d="M 555,357 C 556.885,356.8603 554.8603,358.885 555,357 z "
+ style="fill:#929292;stroke:#929292"
+ id="path1963" />
+ <path
+ d="M 160,358 L 158.25,358.75 L 160,358 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1964" />
+ <path
+ d="M 178,359 L 178.75,359.25 L 171.75,361.75 L 171.25,361.75 C 170.141,359.82 172.838,359.836 174,360 L 178,359 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path1965" />
+ <path
+ d="M 129,361 L 147.75,361.25 L 147,362 L 130,362 L 129,361 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1966" />
+ <path
+ d="M 152,361 L 152.75,362.75 L 152,361 z "
+ style="fill:#101010;stroke:#101010"
+ id="path1967" />
+ <path
+ d="M 154,361 L 154.75,362.75 L 154,361 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1968" />
+ <path
+ d="M 157,361 C 158.885,360.8603 156.8602,362.885 157,361 z "
+ style="fill:#141414;stroke:#141414"
+ id="path1969" />
+ <path
+ d="M 159,361 L 167.75,361.25 L 167.75,361.75 L 159.25,362.75 L 159,361 z "
+ style="fill:#0d0d0d;stroke:#0d0d0d"
+ id="path1970" />
+ <path
+ d="M 791,361 L 793.75,361.25 L 794,362 C 791.669,367.87 786.555,371.878 781.75,375.75 L 779.25,376.25 L 777.25,377.75 L 776.25,374.25 C 782.001,372.307 785.482,366.582 789.25,362.25 L 790.75,361.75 L 791,361 z "
+ style="fill:#cdcdcd;stroke:#cdcdcd"
+ id="path1971" />
+ <path
+ d="M 764,365 L 764.75,367.75 C 763.067,367.68756 763.038,365.981 764,365 z "
+ style="fill:#919191;stroke:#919191"
+ id="path1972" />
+ <path
+ d="M 493,367 L 493.75,369.75 L 493,367 z "
+ style="fill:#999;stroke:#999"
+ id="path1973" />
+ <path
+ d="M 745,367 L 747.75,367.25 L 744.75,375.75 L 741.25,377.75 L 740,376 L 736.25,375.75 C 735.5794,373.837 738.129,372.181 739.75,371.75 L 745,367 z "
+ style="fill:#cdcdcd;stroke:#cdcdcd"
+ id="path1974" />
+ <path
+ d="M 857,367 L 857.75,368.75 L 857,367 z "
+ style="fill:#ababab;stroke:#ababab"
+ id="path1975" />
+ <path
+ d="M 478,368 C 480.566,368.5745 480.229,371.813 479.75,373.75 L 479.25,373.75 L 478,368 z "
+ style="fill:#969696;stroke:#969696"
+ id="path1976" />
+ <path
+ d="M 405,371 L 404,369 L 405,371 z "
+ style="fill:#949494;stroke:#949494"
+ id="path1977" />
+ <path
+ d="M 442,369 L 442.75,369.25 L 444.75,375.75 L 443,373 L 442,369 z "
+ style="fill:#9b9b9b;stroke:#9b9b9b"
+ id="path1978" />
+ <path
+ d="M 451,369 C 453.856,368.5439 457.666,368.97623 457.25,372.75 L 461.75,381.75 C 460.431,382.5589 458.335,381.83368 459,380 L 457.75,375.25 L 452.25,374.75 L 451,369 z "
+ style="fill:#bdbdbd;stroke:#bdbdbd"
+ id="path1979" />
+ <path
+ d="M 405,371 L 405.75,372.75 L 405,371 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1980" />
+ <path
+ d="M 882,372 L 885.75,375.25 C 892.703,387.59 905.03,396.62 914.75,407.25 C 913.33,419.31 911.6,432.33 902.75,441.75 L 901.25,441.75 L 900,440 L 901.75,437.75 L 902.25,434.25 C 907.892,427.297 912.71,419.08 913,410 L 912,409 L 907,413 C 908.076,409.957 909.627,403.48 904.25,403.75 L 902.75,399.25 L 895,394 L 877.25,410.75 L 892.75,392.75 L 892.25,391.75 L 885.25,395.75 L 885.25,393.25 C 888.536,391.654 888.973,384.136 884.25,386.75 C 884.4156,384.633 886.591,380.268 882.25,380.75 L 882,372 z "
+ style="fill:#c8c8c8;stroke:#c8c8c8"
+ id="path1981" />
+ <path
+ d="M 731,373 C 733.303,373.5843 735.411,377.242 732,378 L 731,373 z "
+ style="fill:#989898;stroke:#989898"
+ id="path1982" />
+ <path
+ d="M 167,387 C 168.455,384.649 172.006,382.858 174.75,383.25 L 167,387 z "
+ style="fill:#a3a3a3;stroke:#a3a3a3"
+ id="path1983" />
+ <path
+ d="M 476,384 C 479.418,383.506 477.325,386.872 475.75,387.75 L 476,384 z "
+ style="fill:#acacac;stroke:#acacac"
+ id="path1984" />
+ <path
+ d="M 167,387 L 166.75,387.75 L 157.25,390.75 L 157.25,390.25 L 167,387 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1985" />
+ <path
+ d="M 383,390 L 387.75,396.25 L 387.75,397.75 C 385.591,398.6821 384.806,395.796 384.75,394.25 L 383,390 z "
+ style="fill:#969696;stroke:#969696"
+ id="path1986" />
+ <path
+ d="M 459,390 C 460.551,389.5756 463.075,390.34 460.75,391.75 C 459.9796,392.4377 457.042,391.9591 458.75,390.75 L 459,390 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path1987" />
+ <path
+ d="M 730,390 L 731.75,390.25 L 723.25,398.25 C 721.502,400.123 718.871,401.686 716.25,400.75 L 716.25,399.25 L 728.75,391.75 L 730,390 z "
+ style="fill:#b8b8b8;stroke:#b8b8b8"
+ id="path1988" />
+ <path
+ d="M 452,391 L 454.75,392.75 L 451.25,393.75 L 452,391 z "
+ style="fill:#a4a4a4;stroke:#a4a4a4"
+ id="path1989" />
+ <path
+ d="M 363,392 L 364.75,394.75 L 363,392 z "
+ style="fill:#939393;stroke:#939393"
+ id="path1990" />
+ <path
+ d="M 710,392 L 712.75,392.25 L 709,395 L 704.25,395.75 L 707.25,393.25 L 710,392 z "
+ style="fill:#aaa;stroke:#aaa"
+ id="path1991" />
+ <path
+ d="M 351,393 L 351.75,393.25 L 356.75,405.75 L 351.75,398.25 L 349.25,398.25 C 348.9521,399.569 349.1313,401.665 347,401 C 345.568,402.651 343.55,400.90683 344,399 L 351,393 z "
+ style="fill:#bababa;stroke:#bababa"
+ id="path1992" />
+ <path
+ d="M 421,393 C 423.01,393.8357 424.401,396.662 423.75,398.75 C 419.807,400.412 421.215,395.161 421,393 z "
+ style="fill:#9b9b9b;stroke:#9b9b9b"
+ id="path1993" />
+ <path
+ d="M 439,393 L 441.75,394.75 C 440.9024,395.2331 436.964,395.374 438.75,393.75 L 439,393 z "
+ style="fill:#a3a3a3;stroke:#a3a3a3"
+ id="path1994" />
+ <path
+ d="M 398,398 C 399.997,399.229 400.46,402.444 399.75,404.75 L 399.25,404.75 L 398,398 z "
+ style="fill:#969696;stroke:#969696"
+ id="path1995" />
+ <path
+ d="M 695,399 L 698.75,399.25 C 705.16,404.661 709.15,412.33 707.75,420.75 L 706,422 L 704.25,420.75 L 701.75,408.25 L 694.25,401.75 L 695,399 z "
+ style="fill:#3a3a3a;stroke:#3a3a3a"
+ id="path1996" />
+ <path
+ d="M 203,401 C 204.885,400.8603 202.8602,402.885 203,401 z "
+ style="fill:#959595;stroke:#959595"
+ id="path1997" />
+ <path
+ d="M 711,401 L 713.25,405.75 C 724.31,406.861 727.34,420.36 725.75,429.75 L 722.25,434.25 L 719.25,437.75 L 719.25,436.25 L 723,425 L 721.75,417.25 L 719.75,411.25 L 717.25,412.25 L 717,414 L 718,420 C 719.531,422.563 719.531,426.437 718,429 C 717.92816,431.925 716.532,435.466 714,437 L 713,435 L 714,427 L 713.25,417.75 L 710,403 L 711,401 z "
+ style="fill:#c9c9c9;stroke:#c9c9c9"
+ id="path1998" />
+ <path
+ d="M 871,401 C 873.551,401.7756 869.856,405.678 868.25,404.75 L 871,401 z "
+ style="fill:#999;stroke:#999"
+ id="path1999" />
+ <path
+ d="M 1072,404 L 1067.25,401.25 C 1069.316,400.151 1071.269,402.319 1072,404 z "
+ style="fill:#a2a2a2;stroke:#a2a2a2"
+ id="path2000" />
+ <path
+ d="M 340,402 C 342.01,402.8357 343.401,405.662 342.75,407.75 L 340,402 z "
+ style="fill:#969696;stroke:#969696"
+ id="path2001" />
+ <path
+ d="M 392,402 L 395,411 L 394.75,411.75 L 392.25,412.75 L 391.25,411.75 L 391,404 L 392,402 z "
+ style="fill:#adadad;stroke:#adadad"
+ id="path2002" />
+ <path
+ d="M 383,404 L 386,410 C 385.1151,412.541 382.912,415.459 385,418 L 384.75,418.75 L 382.25,419.75 L 383,404 z "
+ style="fill:#a4a4a4;stroke:#a4a4a4"
+ id="path2003" />
+ <path
+ d="M 1072,404 C 1080.541,405.877 1086.66,413.291 1093.75,418.25 L 1101.25,428.75 L 1105.75,429.25 L 1107,432 L 1100,445 L 1098.25,443.75 L 1079,419 L 1080.25,417.25 L 1081.75,417.25 L 1096.25,435.75 L 1099.75,434.75 L 1098.75,432.25 L 1094.75,426.25 L 1083.75,413.25 L 1082.25,412.75 C 1080.854,408.953 1076.566,407.441 1073.25,405.75 L 1072,404 z "
+ style="fill:#cfcfcf;stroke:#cfcfcf"
+ id="path2004" />
+ <path
+ d="M 335,405 L 342.25,415.75 L 347,424 L 346.75,425.75 L 345.25,424.75 L 334.25,406.75 L 335,405 z "
+ style="fill:#adadad;stroke:#adadad"
+ id="path2005" />
+ <path
+ d="M 1009,409 L 1010.75,410.75 L 1009,409 z "
+ style="fill:#929292;stroke:#929292"
+ id="path2006" />
+ <path
+ d="M 199,411 C 203.914,410.88 196.028,415.047 199,411 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2007" />
+ <path
+ d="M 375,411 C 376.613,411.2975 377.595,413.288 376.75,414.75 C 375.262,414.3112 373.331,412.46 375,411 z "
+ style="fill:#999;stroke:#999"
+ id="path2008" />
+ <path
+ d="M 1074,411 L 1077.75,413.75 L 1074,411 z "
+ style="fill:#979797;stroke:#979797"
+ id="path2009" />
+ <path
+ d="M 920,412 L 944,417 L 949.75,418.25 L 950,422 L 955,460 L 954.75,463.75 L 952,459 L 950,461 C 950.8506,467.576 949.4883,474.93 946,481 L 947,485 L 937.25,502.75 L 932.25,493.25 L 937.75,485.75 C 939.135,480.853 941.518,475.55 945.75,472.75 C 948.974,459.29 948.054,444.48 947.75,430.25 L 946.75,429.25 L 940.75,421.25 L 939,421 L 929.25,420.75 L 926.25,416.25 L 925,418 C 923.836,431.81 920.067,446.05 911.25,456.75 L 908,453 L 909.25,448.25 C 916.897,440.073 921.49,429.8 923,419 L 920.25,415.75 L 920,412 z "
+ style="fill:#dbdbdb;stroke:#dbdbdb"
+ id="path2010" />
+ <path
+ d="M 323,414 C 324.864,413.6679 328.667,413.99954 327.75,416.75 L 324.25,419.25 C 325.963,423.929 329.932,428.368 328.75,433.75 L 324,426 L 322,430 L 324.75,442.75 C 323.036,439.99 319.021,425.91 317.75,435.75 L 315.75,435.25 C 313.236,435.4817 312.748,437.472 312.75,439.75 L 310.25,438.25 L 312.75,462.75 L 309,448 C 307.281,445.207 308.6141,437.96 303.25,439.25 C 305.364,449.115 306.828,459.96 305.75,470.75 L 304,468 C 302.597,458.787 302.775,447.96 296,441 L 293.25,442.25 L 298,454 L 302,484 L 302.25,489.75 C 303.0137,491.097 303.734,492.934 305.75,491.75 L 305.25,477.25 C 310.203,475.129 308.942,481.065 309,484 C 309.1536,487.042 308.1869,491.216 311,493 L 314.25,473.25 L 318.75,495.75 C 314.727,503.551 315.176,514.65 306.25,519.25 L 295.75,573.75 C 270.66,572.579 245.91,570.925 224.25,558.75 L 246.25,518.25 C 248.263,515.546 249.745,512.441 246,511 C 245.2018,506.724 247.241,502.346 250.75,500.25 L 250,508 L 258.75,496.75 L 264.75,487.25 C 261.713,512.31 250.17,535.35 237.25,557.25 L 237.25,560.75 L 247,547 L 257,528 L 263,513 L 266.25,502.25 C 266.9424,519.48 258.591,537.33 253,554 L 254.25,556.75 L 260,550 L 259.75,552.75 L 257.25,565.75 L 260.75,564.75 L 267,534 C 267.9788,527.227 268.634,520.1 272.25,514.25 L 274.75,514.75 L 276.75,512.75 L 279,494 L 281.75,472.25 L 285,514 L 288.75,514.25 L 292,527 C 291.6895,529.061 289.473,532.89 292.75,533.75 L 294.75,531.75 C 301.04,506.41 298.864,478.95 296,453 L 290.25,440.75 C 282.888,438.104 290.8681,452.13 284.25,453.25 L 283.75,455.75 L 280.25,454.75 C 282.124,446.449 268.53,447.722 272,456 L 271.75,470.75 L 270,448 L 266.25,445.75 C 267.251,440.227 261.901,437.421 260,434 L 256.25,435.75 L 256.75,432.75 L 256.25,429.25 C 278.26,442.55 304.55,428.5872 323,414 z "
+ style="fill:#e8e8e8;stroke:#e8e8e8"
+ id="path2011" />
+ <path
+ d="M 1037,436 L 1057.75,463.25 L 1063.25,485.75 L 1065,484 L 1060,462 L 1059.75,457.25 L 1063.25,465.75 L 1065.25,472.75 L 1067.75,473.75 L 1061.25,452.25 C 1063.569,451.6245 1065.277,454.739 1065.25,456.75 L 1068.75,462.25 L 1073.25,471.75 C 1076.932,473.296 1075.83,469.242 1074.25,467.75 L 1061.25,442.25 C 1069.454,448.567 1075.78,457.26 1079.25,466.75 L 1081.75,467.75 L 1082.75,466.25 C 1079.714,454.39 1071.02,445.18 1062.25,437.75 L 1044.25,414.25 L 1058.75,422.25 L 1058,425 L 1075.25,442.75 L 1086.75,465.75 L 1074.25,478.25 L 1050.75,499.75 L 1048.25,499.75 L 1049,483 L 1047.25,479.75 L 1041,466 L 1038.25,459.25 L 1056.25,488.75 L 1058.75,488.75 C 1057.096,478.54 1049.078,469.96 1044.25,460.75 L 1044.25,459.25 L 1045.75,458.75 L 1036.25,441.75 C 1038.815,440.7525 1037.576,438.234 1037,436 z "
+ style="fill:#dcdcdc;stroke:#dcdcdc"
+ id="path2012" />
+ <path
+ d="M 203,415 L 202.75,418.75 C 201.376,419.5345 197.555,418.9611 200.25,417.25 L 203,415 z "
+ style="fill:#acacac;stroke:#acacac"
+ id="path2013" />
+ <path
+ d="M 977,415 C 978.885,414.8603 976.8603,416.885 977,415 z "
+ style="fill:#939393;stroke:#939393"
+ id="path2014" />
+ <path
+ d="M 973,416 L 974.75,418.75 L 973,416 z "
+ style="fill:#969696;stroke:#969696"
+ id="path2015" />
+ <path
+ d="M 1057,416 C 1058.885,415.8603 1056.8602,417.885 1057,416 z "
+ style="fill:#939393;stroke:#939393"
+ id="path2016" />
+ <path
+ d="M 1059,417 C 1060.885,416.8603 1058.8602,418.885 1059,417 z "
+ style="fill:#969696;stroke:#969696"
+ id="path2017" />
+ <path
+ d="M 633,418 L 637,420 L 635.75,421.75 L 622.25,423.75 L 623.25,421.25 L 628,420 L 633,418 z "
+ style="fill:#202020;stroke:#202020"
+ id="path2018" />
+ <path
+ d="M 650,418 L 652.75,418.25 L 652.75,420.75 L 636,428 L 633.25,427.75 C 632.5347,424.626 637.06,424.724 639,424 L 650,418 z "
+ style="fill:#2a2a2a;stroke:#2a2a2a"
+ id="path2019" />
+ <path
+ d="M 953,418 L 954.75,423.75 L 953,421 L 953,418 z "
+ style="fill:#a9a9a9;stroke:#a9a9a9"
+ id="path2020" />
+ <path
+ d="M 962,418 L 970.75,418.25 L 971.75,420.75 L 970.25,421.25 L 967.25,423.75 L 963.75,424.75 L 962.25,424.75 L 962,418 z "
+ style="fill:#d5d5d5;stroke:#d5d5d5"
+ id="path2021" />
+ <path
+ d="M 377,421 L 378.75,422.75 L 377,421 z "
+ style="fill:#9c9c9c;stroke:#9c9c9c"
+ id="path2022" />
+ <path
+ d="M 249,422 C 251.224,421.4169 253.226,423.074 254,425 L 250.75,429.75 L 242.75,438.75 L 228.25,454.75 L 228.25,449.25 L 229.25,447.25 C 235.715,441.283 241.2,434.71 245.25,427.25 L 248.75,423.75 L 249,422 z "
+ style="fill:#d5d5d5;stroke:#d5d5d5"
+ id="path2023" />
+ <path
+ d="M 1067,422 L 1077,428 L 1079,427 L 1080.75,427.25 L 1096.25,447.75 L 1097.75,449.25 L 1096.75,450.75 C 1088.365,442.02 1080.52,430.47 1069.25,424.75 L 1067,422 z "
+ style="fill:#bdbdbd;stroke:#bdbdbd"
+ id="path2024" />
+ <path
+ d="M 208,423 C 211.17,423.6726 209.935,428.001 208,429 L 208,423 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path2025" />
+ <path
+ d="M 938,426 L 938.75,426.25 C 942.757,434.054 942.046,443.08 942,452 L 939,462 L 940,466 L 930.75,485.75 L 929.25,486.75 L 928.25,483.25 L 930.75,480.75 C 937.51,467.5 939.827,453.1 940,438 L 938,434 L 938,426 z "
+ style="fill:#bcbcbc;stroke:#bcbcbc"
+ id="path2026" />
+ <path
+ d="M 1030,427 C 1028.115,427.1397 1030.1398,425.115 1030,427 z "
+ style="fill:#979797;stroke:#979797"
+ id="path2027" />
+ <path
+ d="M 1064,426 C 1076.01,431.468 1084.1,442.47 1092.75,452.25 L 1094,456 L 1087.25,463.75 C 1086.3759,455.526 1081.613,448.41 1077.75,441.25 L 1064.25,427.75 L 1064,426 z "
+ style="fill:#dedede;stroke:#dedede"
+ id="path2028" />
+ <path
+ d="M 1030,427 L 1032.75,430.75 L 1030,427 z "
+ style="fill:#919191;stroke:#919191"
+ id="path2029" />
+ <path
+ d="M 647,428 C 648.864,427.6679 652.667,427.99954 651.75,430.75 L 647.25,432.75 L 652.75,434.25 L 647,436 C 645.784,437.582 643.374,436.03613 644.25,434.25 L 645.75,433.75 L 645,433 L 638.25,433.75 L 640.25,429.25 L 646,429 L 647,428 z "
+ style="fill:#2b2b2b;stroke:#2b2b2b"
+ id="path2030" />
+ <path
+ d="M 914,430 L 916,431 L 905.75,446.75 L 904.25,446.75 L 904.25,445.25 C 908.128,440.598 912.507,435.887 914,430 z "
+ style="fill:#a5a5a5;stroke:#a5a5a5"
+ id="path2031" />
+ <path
+ d="M 1012,435 C 1010.115,435.1397 1012.1397,433.115 1012,435 z "
+ style="fill:#9f9f9f;stroke:#9f9f9f"
+ id="path2032" />
+ <path
+ d="M 356,435 L 356.75,435.25 L 358.75,439.75 C 355.285,441.387 355.954,437.039 356,435 z "
+ style="fill:#979797;stroke:#979797"
+ id="path2033" />
+ <path
+ d="M 1014,438 L 1012,435 L 1014,438 z "
+ style="fill:#939393;stroke:#939393"
+ id="path2034" />
+ <path
+ d="M 1037,436 C 1035.115,436.1397 1037.1398,434.115 1037,436 z "
+ style="fill:#a1a1a1;stroke:#a1a1a1"
+ id="path2035" />
+ <path
+ d="M 659,438 C 660.484,438.2 661.507,439.986 659.75,440.75 C 658.067,440.68756 658.038,438.981 659,438 z "
+ style="fill:#454545;stroke:#454545"
+ id="path2036" />
+ <path
+ d="M 1014,438 C 1015.885,437.8603 1013.8603,439.885 1014,438 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2037" />
+ <path
+ d="M 251,439 L 252.75,439.25 C 253.938,442.595 252.5283,446.497 249.25,447.75 L 251,439 z "
+ style="fill:#bebebe;stroke:#bebebe"
+ id="path2038" />
+ <path
+ d="M 667,445 L 670,445 L 670.75,446.75 L 663.25,447.75 L 663.25,446.25 L 665,446 L 667,445 z "
+ style="fill:#363636;stroke:#363636"
+ id="path2039" />
+ <path
+ d="M 922,446 L 925.75,446.25 L 922.25,461.75 L 923.75,462.25 L 924,464 C 922.444,465.88 921.192,468.149 922.25,470.75 L 924.25,473.75 L 931.75,461.25 L 926.75,478.75 L 925.75,479.75 L 917.25,468.75 L 914,462 L 922,446 z "
+ style="fill:#d5d5d5;stroke:#d5d5d5"
+ id="path2040" />
+ <path
+ d="M 261,447 L 261.75,447.25 L 258.75,454.75 L 258.25,454.75 L 261,447 z "
+ style="fill:#969696;stroke:#969696"
+ id="path2041" />
+ <path
+ d="M 1030,447 C 1031.885,446.8603 1029.8602,448.885 1030,447 z "
+ style="fill:#939393;stroke:#939393"
+ id="path2042" />
+ <path
+ d="M 206,449 C 205.8602,447.115 207.885,449.1397 206,449 z "
+ style="fill:#a9a9a9;stroke:#a9a9a9"
+ id="path2043" />
+ <path
+ d="M 244,448 C 246.287,449.305 244.6259,452.857 243.75,454.75 L 243.25,454.75 L 244,448 z "
+ style="fill:#a5a5a5;stroke:#a5a5a5"
+ id="path2044" />
+ <path
+ d="M 206,449 L 204.25,450.75 L 206,449 z "
+ style="fill:#959595;stroke:#959595"
+ id="path2045" />
+ <path
+ d="M 485,450 C 484.8603,448.115 486.885,450.1397 485,450 z "
+ style="fill:#9a9a9a;stroke:#9a9a9a"
+ id="path2046" />
+ <path
+ d="M 485,450 L 484,451 L 485,450 z "
+ style="fill:#9d9d9d;stroke:#9d9d9d"
+ id="path2047" />
+ <path
+ d="M 484,451 L 483,452 L 484,451 z "
+ style="fill:#aaa;stroke:#aaa"
+ id="path2048" />
+ <path
+ d="M 974,451 C 976.134,451.4124 976.431,454.054 975.75,455.75 L 975.25,455.75 L 974,451 z "
+ style="fill:#a9a9a9;stroke:#a9a9a9"
+ id="path2049" />
+ <path
+ d="M 1035,464 L 1044,481 L 1043,483 L 1045.75,502.75 L 1042.25,504.75 L 1041,499 L 1040,492 C 1040.8357,485.271 1036.908,479.84 1033.25,474.75 L 1021.25,451.25 C 1026.198,449.281 1026.628,455.599 1028.75,458.25 L 1034.25,469.75 L 1035.75,469.75 L 1035,464 z "
+ style="fill:#d5d5d5;stroke:#d5d5d5"
+ id="path2050" />
+ <path
+ d="M 965,452 L 966.75,452.25 L 968,457 L 968,459 L 967,474 L 966,475 L 965.25,474.75 L 966,467 L 965,455 L 965,452 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path2051" />
+ <path
+ d="M 959,453 L 960.25,457.75 L 962.75,458.25 L 962.75,469.75 L 962.25,469.75 L 961.75,461.25 L 960.75,460.25 C 957.063,462.008 957.371,454.895 959,453 z "
+ style="fill:#b5b5b5;stroke:#b5b5b5"
+ id="path2052" />
+ <path
+ d="M 1016,453 L 1033.75,485.25 L 1040.75,506.75 L 1033.75,511.75 C 1017.04,518.441 1000.35,525.71 982.25,526.75 C 984.554,517.29 987.857,506.18 985,496 L 986,475 L 984.25,463.25 L 986,468 L 987.25,470.75 L 989.75,464.25 L 995.75,478.25 L 995,489 L 994,503 L 990.25,514.75 L 991.75,515.75 C 998.461,506.607 998.314,494.53 998,483 L 1000,481 L 999.25,471.25 L 1005.75,471.25 L 1007,477 L 1008.75,477.75 L 1008.25,463.25 L 1009.75,462.25 L 1010.75,467.25 L 1012,473 L 1014,471 C 1009.961,467.688 1016.814,459.05 1017.25,466.75 L 1025,499 L 1024,507 L 1026,513 L 1028,509 C 1027.9736,489.19 1021.425,471.26 1016,453 z "
+ style="fill:#dedede;stroke:#dedede"
+ id="path2053" />
+ <path
+ d="M 220,474 L 228,466 L 227.75,465.25 C 223.961,463.084 230.344,456.079 233.25,458.75 L 234.75,458.75 L 238.75,456.25 C 238.0799,459.999 236.729,463.786 233.25,466.25 L 227,473 L 224,472 L 220,474 z "
+ style="fill:#d1d1d1;stroke:#d1d1d1"
+ id="path2054" />
+ <path
+ d="M 670,456 L 674.75,456.25 L 673.75,457.75 L 662.25,459.75 L 663.25,458.25 L 666,458 L 670,456 z "
+ style="fill:#363636;stroke:#363636"
+ id="path2055" />
+ <path
+ d="M 252,458 L 252.75,458.25 L 253,460 C 251.469,462.284 250.741,467.109 247.25,466.75 L 247.75,465.75 L 252,458 z "
+ style="fill:#b2b2b2;stroke:#b2b2b2"
+ id="path2056" />
+ <path
+ d="M 616,459 C 621.149,458.6844 622.674,465.038 619,468 L 626,473 L 629,477 L 630,482 C 630.5185,484.693 625.27,487.383 624,485 L 624.75,480.25 L 613.25,474.75 L 611.25,468.25 L 614.75,464.25 L 613,463 C 591.02,463.6057 569.34,468.156 551.75,481.75 L 550.25,480.75 L 555.25,476.25 L 578,466 L 587,464 C 595.402,459.44 605.23,459.807 615,460 L 616,459 z "
+ style="fill:#242424;stroke:#242424"
+ id="path2057" />
+ <path
+ d="M 1035,464 L 1033.25,460.25 L 1035,464 z "
+ style="fill:#939393;stroke:#939393"
+ id="path2058" />
+ <path
+ d="M 211,461 C 215.237,460.0621 209.863,465.804 209.25,463.25 L 209.75,462.75 L 211,461 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2059" />
+ <path
+ d="M 479,462 C 478.8603,460.115 480.885,462.1397 479,462 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2060" />
+ <path
+ d="M 479,462 L 478,463 L 479,462 z "
+ style="fill:#979797;stroke:#979797"
+ id="path2061" />
+ <path
+ d="M 676,464 C 678.553,464.979 674.771,468.536 673,468 L 670.25,467.75 L 676,464 z "
+ style="fill:#3e3e3e;stroke:#3e3e3e"
+ id="path2062" />
+ <path
+ d="M 679,467 L 681.75,467.25 L 681.75,468.75 L 680.75,469.75 L 669.25,473.75 L 671,472 L 674.25,470.25 L 679,467 z "
+ style="fill:#3c3c3c;stroke:#3c3c3c"
+ id="path2063" />
+ <path
+ d="M 220,474 L 194,495 L 177.75,504.75 L 169,509 C 168.2931,511.682 171.449,510.935 173,511 C 190.7,502.886 207.43,491.62 221.25,478.25 L 224.75,477.25 L 214.75,486.75 L 183.25,512.25 L 183.25,513.75 C 191.564,513.0847 198.13,505.737 204.75,500.75 L 209.75,497.25 L 189,519 L 190.25,521.75 L 194.75,518.75 L 200.75,513.75 L 207.25,507.25 L 224.75,487.75 C 228.413,484.096 233.647,480.451 231,475 L 232.75,473.25 C 232.9568,477.106 237.137,478.104 239,475 L 242.75,470.25 L 241.75,474.75 C 228.95,493.75 212.64,512.15 194.25,526.25 L 194.25,527.75 L 197,529 L 213.75,513.75 L 220.75,506.75 L 224.75,502.75 L 240.75,486.25 C 240.0198,494.496 232.547,500.46 228.25,507.25 L 215.75,521.75 L 203.25,537.75 L 205.75,537.75 C 218.83,524.38 230.45,511.14 241.25,496.25 L 244.75,499.25 L 233.75,512.75 L 217.25,533.25 L 211.25,544.75 C 213.582,545.879 215.749,543.278 216.25,541.25 L 238.25,513.25 L 239.75,513.25 C 234.972,528.05 225.25,541.73 214.75,553.75 C 200.8,544.808 187.17,534.86 174.75,523.25 L 163,509 C 181.94,497.93 202.51,487.55 220,474 z "
+ style="fill:#ddd;stroke:#ddd"
+ id="path2064" />
+ <path
+ d="M 995,472 C 996.613,472.2975 997.595,474.288 996.75,475.75 L 995,472 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path2065" />
+ <path
+ d="M 319,475 C 321.324,476.462 321.332,480.152 320.75,482.75 C 317.638,481.8206 319.506,477.441 319,475 z "
+ style="fill:#959595;stroke:#959595"
+ id="path2066" />
+ <path
+ d="M 679,475 C 681.241,474.2788 682.389,475.9416 682.75,477.75 L 672.25,481.75 L 671.25,480.25 L 672.75,478.75 L 679,475 z "
+ style="fill:#202020;stroke:#202020"
+ id="path2067" />
+ <path
+ d="M 266,477 L 266.75,479.75 L 266,477 z "
+ style="fill:#929292;stroke:#929292"
+ id="path2068" />
+ <path
+ d="M 1010,479 C 1014.226,483.418 1017.573,489.68 1018,496 L 1019.75,509.75 L 1019,509 L 1019,505 L 1012.25,485.25 L 1012,498 L 1009.75,510.75 L 1008.25,511.75 L 1009,504 L 1010,502 L 1010,479 z "
+ style="fill:#1d1d1d;stroke:#1d1d1d"
+ id="path2069" />
+ <path
+ d="M 267,481 C 268.978,481.1194 267.5378,483.952 266.25,483.75 L 267,481 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2070" />
+ <path
+ d="M 287,481 L 288.75,481.25 L 288.75,491.75 L 288.25,491.75 L 287,481 z "
+ style="fill:#969696;stroke:#969696"
+ id="path2071" />
+ <path
+ d="M 949,508 L 946.25,513.75 L 948.75,513.75 C 956.325,504.792 960.81,494.15 964.25,483.25 L 965.75,485.25 L 968,484 L 969.75,481.25 L 960,509 L 963,511 L 961.25,516.75 L 964,515 L 974.25,489.25 L 976.25,491.75 L 979.75,491.25 L 977.75,506.75 C 974.24,511.082 981.357,512.289 980,507 L 981,505 L 982.75,499.25 C 983.796,509.228 982.1059,518.8 978.75,527.75 L 950.25,527.75 L 941.25,513.75 C 940.3126,511.782 938.848,508.884 941.25,507.25 L 943.25,505.25 L 945.75,504.25 L 942,509 C 941.5545,512.137 944.646,512.196 945.75,509.75 L 949,508 z "
+ style="fill:#eaeaea;stroke:#eaeaea"
+ id="path2072" />
+ <path
+ d="M 682,484 L 684.75,484.25 L 683.75,486.75 L 675.25,488.75 L 678.75,485.75 L 682,484 z "
+ style="fill:#2f2f2f;stroke:#2f2f2f"
+ id="path2073" />
+ <path
+ d="M 1002,484 L 1003,485 L 1003.75,491.25 L 1003,493 L 1003.75,496.25 L 1003,498 L 999.75,517.75 L 999.25,517.75 L 999,514 L 1001,503 L 1002,501 L 1002,484 z "
+ style="fill:#1a1a1a;stroke:#1a1a1a"
+ id="path2074" />
+ <path
+ d="M 248,485 C 250.543,485.9216 247.96722,489.481 246.25,489.75 L 248,485 z "
+ style="fill:#a5a5a5;stroke:#a5a5a5"
+ id="path2075" />
+ <path
+ d="M 293,485 L 293.75,485.25 C 296.323,489.755 294.6281,495.121 293.75,499.75 L 293.25,499.75 L 292,491 L 293,485 z "
+ style="fill:#b0b0b0;stroke:#b0b0b0"
+ id="path2076" />
+ <path
+ d="M 952,485 L 952.75,486.75 L 952,485 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path2077" />
+ <path
+ d="M 686,488 C 687.162,487.8355 689.859,487.8197 688.75,489.75 L 680.25,491.75 L 681.25,490.25 L 686,488 z "
+ style="fill:#414141;stroke:#414141"
+ id="path2078" />
+ <path
+ d="M 553,491 L 559.25,501.75 C 561.52,501.3813 563.79,500.33 565,503 L 582,498 C 589.469,494.767 598.52,492.793 606,495 L 609,494 L 611,495 L 628.75,492.25 L 640,513 L 637.25,513.75 L 635.25,511.75 C 632.66,504.828 623.12,507.333 618.25,506.25 L 617.25,507.75 C 634.37,522.41 612.104,547.03 594,543 C 587.909,540.727 581.31,537.664 579,531 C 578.2983,523.682 581.756,517.18 586.75,512.25 L 582,513 C 571.1,515.534 562.3,522.773 553.25,529.25 L 554,534 C 558.544,540.406 564.52,546.39 571.75,549.25 L 573.75,551.75 L 572,553 C 561.32,550.016 553.42,541.47 547.75,532.25 L 544.25,531.75 C 543.9048,528.69 542.213,526.319 545,524 L 552,510 L 544.25,510.75 L 553,491 z "
+ style="fill:#0f0f0f;stroke:#0f0f0f"
+ id="path2079" />
+ <path
+ d="M 969,492 L 969.75,493.75 L 969,492 z "
+ style="fill:#979797;stroke:#979797"
+ id="path2080" />
+ <path
+ d="M 690,496 C 691.162,495.8355 693.859,495.8197 692.75,497.75 C 691.029,498.5771 686.765,499.503 689,502 L 688.75,502.75 L 687.75,504.75 C 685.031,503.8135 683.532,498.091 687.75,497.75 L 689.75,496.75 L 690,496 z "
+ style="fill:#373737;stroke:#373737"
+ id="path2081" />
+ <path
+ d="M 967,498 L 967.75,499.75 L 967,498 z "
+ style="fill:#9e9e9e;stroke:#9e9e9e"
+ id="path2082" />
+ <path
+ d="M 767,503 L 781,505 L 780.75,505.75 C 777.856,506.7468 774.723,508.625 773.75,511.75 L 768.75,519.75 C 764.741,516.832 760.731,512.762 759,508 L 766.75,503.75 L 767,503 z "
+ style="fill:#efefef;stroke:#efefef"
+ id="path2083" />
+ <path
+ d="M 965,503 L 965.75,504.75 L 965,503 z "
+ style="fill:#929292;stroke:#929292"
+ id="path2084" />
+ <path
+ d="M 949,508 C 948.8602,506.115 950.885,508.1397 949,508 z "
+ style="fill:#9a9a9a;stroke:#9a9a9a"
+ id="path2085" />
+ <path
+ d="M 690,511 C 692.612,510.575 695.178,514.948 692.25,516.25 C 692.21832,519.488 688.973,521.887 686.25,522.75 L 686.25,520.25 L 689,517 L 690,511 z "
+ style="fill:#343434;stroke:#343434"
+ id="path2086" />
+ <path
+ d="M 789,514 L 792.75,514.25 L 792.75,519.75 L 788.25,519.75 L 789,514 z "
+ style="fill:#e4e4e4;stroke:#e4e4e4"
+ id="path2087" />
+ <path
+ d="M 601,515 L 606.75,515.25 C 607.4874,517.943 609.084,520.937 605.75,522.75 L 600.25,521.75 L 601,515 z "
+ style="fill:#fbfbfb;stroke:#fbfbfb"
+ id="path2088" />
+ <path
+ d="M 633,525 L 635.75,525.25 L 636,531 L 637,533 C 637.7314,540.799 635.957,549.87 628,553 L 627.25,552.75 L 627.25,550.25 C 630.184,548.504 633.504,545.923 633,542 C 634.607,538.22 634.607,532.78 633,529 L 633,525 z "
+ style="fill:#3d3d3d;stroke:#3d3d3d"
+ id="path2089" />
+ <path
+ d="M 275,530 L 277.75,531.25 L 278,555 L 276,558 L 274,555 L 275,553 L 275,530 z "
+ style="fill:#252525;stroke:#252525"
+ id="path2090" />
+ <path
+ d="M 564,533 L 565.75,534.25 L 566.25,536.75 C 572.538,544.541 581.55,548.28 591,549 L 593,550 L 616,551 L 619,550 L 620.75,551.25 L 617.75,554.75 L 612,555 L 580.25,551.75 L 578.75,549.25 C 572.602,546.797 566.68,542.614 563.25,536.75 L 564,533 z "
+ style="fill:#2f2f2f;stroke:#2f2f2f"
+ id="path2091" />
+ <path
+ d="M 226,546 L 227,547 L 220.75,556.75 L 220.25,556.75 L 219,555 L 226,546 z "
+ style="fill:#a1a1a1;stroke:#a1a1a1"
+ id="path2092" />
+ <path
+ d="M 650,549 L 652.75,549.25 L 652.75,550.75 C 648.377,557.438 644.782,565.12 638,570 L 636.25,568.75 L 649.75,550.75 L 650,549 z "
+ style="fill:#3f3f3f;stroke:#3f3f3f"
+ id="path2093" />
+ <path
+ d="M 788,551 L 799,557 L 814.75,555.25 L 827,601 L 830,606 L 830.75,635.75 L 829.25,634.75 L 821.25,616.75 L 797.75,567.25 L 792.25,560.75 L 788,551 z "
+ style="fill:#f6f6f6;stroke:#f6f6f6"
+ id="path2094" />
+ <path
+ d="M 482,569 L 484,571 L 480,580 L 484,588 C 483.5308,591.82 479.04,595.751 483,599 L 483,601 L 484.75,623.75 L 484,624 L 483.25,623.75 C 482.5443,618.427 481.874,612.99 478,609 L 476.25,602.75 L 474,595 L 474.75,593.25 L 471,594 L 482,569 z "
+ style="fill:#212121;stroke:#212121"
+ id="path2095" />
+ <path
+ d="M 490,619 L 490.75,619.25 L 491,622 L 491,627 C 490.794,630.104 487.951,634.358 484.25,632.75 L 490,619 z "
+ style="fill:#2d2d2d;stroke:#2d2d2d"
+ id="path2096" />
+ <path
+ d="M 683,624 L 698,624 L 702,626 C 718.95,629.103 734.55,638.78 748.25,649.75 C 756.914,654.562 767.1,655.359 773.25,664.75 L 776.75,664.25 L 785.75,670.25 L 800.25,683.75 C 804.222,684.6334 808.661,689.168 809.75,692.75 L 798.25,689.75 L 785.75,677.25 L 779.75,678.75 L 774.75,675.25 L 773,676 L 765.25,669.75 C 763.586,666.853 761.836,667.754 759.25,667.75 C 746.32,650.78 727.69,639.02 708,632 C 694.39,630.774 679.01,629.255 666.25,634.25 C 646.04,644.7 631.35,673.75 646.75,693.25 L 650.25,700.75 C 651.61,701.522 653.182,704.373 651,705 C 646.002,700.478 642.958,693.58 640.75,687.25 L 635,676 C 634.1579,653.87 651.48,632.66 672.75,627.75 L 683,624 z "
+ style="fill:#1b1b1b;stroke:#1b1b1b"
+ id="path2097" />
+ <path
+ d="M 499,631 L 500.75,632.25 L 498.75,637.75 L 493.75,644.75 L 492.25,644.75 L 492,644 L 496.75,636.75 L 499,631 z "
+ style="fill:#3b3b3b;stroke:#3b3b3b"
+ id="path2098" />
+ <path
+ d="M 420,643 L 420.75,645.75 L 416.75,648.75 C 414.897,648.80646 412.925,649.5909 412.75,651.75 L 405.25,657.25 L 404,658 L 402.25,656.75 L 412.25,647.25 C 415.419,649.618 418.361,645.378 420,643 z "
+ style="fill:#d7d7d7;stroke:#d7d7d7"
+ id="path2099" />
+ <path
+ d="M 671,653 C 679.169,652.241 686.85,654.926 692.75,660.25 L 693.25,662.75 L 695,665 L 693.75,666.75 L 690.25,665.75 C 687.332,657.207 676.02,658.037 669.25,661.25 L 659.25,682.25 L 657,689 L 654.25,688.75 L 651,679 L 651,672 C 651.9046,662.489 660.579,654.79 670,654 L 671,653 z "
+ style="fill:#101010;stroke:#101010"
+ id="path2100" />
+ <path
+ d="M 387,658 C 388.3,658.1038 391.862,657.3036 390.75,659.75 L 386,663 C 383.306,661.904 386.318,659.4 387,658 z "
+ style="fill:#2f2f2f;stroke:#2f2f2f"
+ id="path2101" />
+ <path
+ d="M 395,663 C 394.8603,661.115 396.885,663.1397 395,663 z "
+ style="fill:#969696;stroke:#969696"
+ id="path2102" />
+ <path
+ d="M 395,663 L 394,664 L 395,663 z "
+ style="fill:#9d9d9d;stroke:#9d9d9d"
+ id="path2103" />
+ <path
+ d="M 394,664 L 393,665 L 394,664 z "
+ style="fill:#a6a6a6;stroke:#a6a6a6"
+ id="path2104" />
+ <path
+ d="M 393,665 C 393.1397,666.885 391.115,664.8603 393,665 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2105" />
+ <path
+ d="M 386,671 C 385.8603,669.115 387.885,671.1398 386,671 z "
+ style="fill:#9c9c9c;stroke:#9c9c9c"
+ id="path2106" />
+ <path
+ d="M 702,678 C 718.45,676.723 724.42,694.8 729,707 L 730,724 L 725.25,736.75 L 726,731 L 728,726 L 727,712 L 716.25,689.75 L 715.75,687.25 L 710.75,683.25 L 702.25,683.25 C 701.8545,685.612 701.3195,688.765 703.75,690.25 L 716,701 C 714.769,708.195 704.75,706.276 699.75,705.25 C 692.393,703.568 683.2,698.648 683,690 C 684.914,682.201 693.36,678.16 701,679 L 702,678 z "
+ style="fill:#131313;stroke:#131313"
+ id="path2107" />
+ <path
+ d="M 341,681 L 345.75,682.25 L 344.75,683.75 L 337.25,689.25 L 326.75,701.75 L 323,702 L 318.75,698.25 L 316.25,692.25 L 326.75,685.75 C 330.522,682.77 334.981,681.382 340,682 L 341,681 z "
+ style="fill:#efefef;stroke:#efefef"
+ id="path2108" />
+ <path
+ d="M 728,690 C 733.677,691.777 736.806,698.415 738.25,703.75 L 741.75,703.25 L 743,706 L 743,709 L 753,734 L 753,737 L 755,746 L 753.75,747.75 L 752.25,746.75 L 751,742 L 744.75,719.25 L 744,719 L 739.25,723.75 L 731.25,697.75 L 728.25,692.75 L 728,690 z "
+ style="fill:#2b2b2b;stroke:#2b2b2b"
+ id="path2109" />
+ <path
+ d="M 303,696 L 305.75,696.25 C 305.9422,701.83 298.89,702.469 295,704 L 293.25,703.75 L 297.25,699.25 L 302.75,696.75 L 303,696 z "
+ style="fill:#d9d9d9;stroke:#d9d9d9"
+ id="path2110" />
+ <path
+ d="M 563,700 L 573.75,700.25 L 573.75,701.75 C 565.016,700.915 559.2,708.006 553,713 L 551,709 L 553.75,705.75 L 563,700 z "
+ style="fill:#202020;stroke:#202020"
+ id="path2111" />
+ <path
+ d="M 276,709 L 287,709 L 292.75,710.25 L 280,719 L 277,719 L 275.75,719.75 L 274.25,719.25 L 273,720 L 271,719 L 267.25,718.75 L 267.25,717.25 L 275.75,709.75 L 276,709 z "
+ style="fill:#eaeaea;stroke:#eaeaea"
+ id="path2112" />
+ <path
+ d="M 413,721 L 413.75,721.25 L 413,729 C 413.5779,733.69 411.42,739.37 415,743 L 415.75,746.75 C 411.554,746.3154 407.952,742.732 406,739 C 405.323,733.114 408.012,727.97 411.75,723.75 L 413,721 z "
+ style="fill:#eee;stroke:#eee"
+ id="path2113" />
+ <path
+ d="M 883,735 C 882.8602,733.115 884.885,735.1397 883,735 z "
+ style="fill:#b7b7b7;stroke:#b7b7b7"
+ id="path2114" />
+ <path
+ d="M 883,735 L 881,736 L 883,735 z "
+ style="fill:#b7b7b7;stroke:#b7b7b7"
+ id="path2115" />
+ <path
+ d="M 881,736 C 881.1398,737.885 879.115,735.8603 881,736 z "
+ style="fill:#999;stroke:#999"
+ id="path2116" />
+ <path
+ d="M 873,741 C 874.885,740.8602 872.8603,742.885 873,741 z "
+ style="fill:#999;stroke:#999"
+ id="path2117" />
+ <path
+ d="M 869,744 L 869.75,746.75 L 862.25,749.75 L 869,744 z "
+ style="fill:#cdcdcd;stroke:#cdcdcd"
+ id="path2118" />
+ <path
+ d="M 434,765 L 436,767 L 434,775 L 438.75,785.25 C 440.731,789.123 443.616,793.329 447.75,795.25 L 447.75,796.75 L 442,797 L 440,798 L 437,797 C 430.484,796.587 425.67,789.678 424,784 L 430.75,770.75 L 432.75,768.75 L 434,765 z "
+ style="fill:#f1f1f1;stroke:#f1f1f1"
+ id="path2119" />
+ <path
+ d="M 827,766 C 826.8603,764.115 828.885,766.1398 827,766 z "
+ style="fill:#919191;stroke:#919191"
+ id="path2120" />
+ <path
+ d="M 827,766 L 826,767 L 827,766 z "
+ style="fill:#9f9f9f;stroke:#9f9f9f"
+ id="path2121" />
+ <path
+ d="M 460,805 L 465.75,806.25 L 466.25,807.75 L 474.25,815.75 L 475.75,816.25 L 480.25,821.75 L 486.25,824.75 C 489.796,824.9198 492.126,828.526 493.75,831.25 C 495.111,839.066 486.615,841.7 482,846 L 481.25,845.25 C 487.749,834.34 474.137,823.77 464,824 L 457,823 L 454.25,821.75 C 448.968,816.163 455.492,808.53 460,805 z "
+ style="fill:#f4f4f4;stroke:#f4f4f4"
+ id="path2122" />
+ <path
+ d="M 640,887 L 638.75,892.75 L 638,893 C 634.895,891.274 639.335,888.846 640,887 z "
+ style="fill:#313131;stroke:#313131"
+ id="path2123" />
+ <path
+ d="M 736,889 L 739.75,889.25 L 737.75,891.75 L 735.25,892.25 L 732,894 L 711,894 L 709.25,892.25 C 710.357,890.345 712.599,890.961 714,892 L 722,892 C 726.837,891.96973 732.76,892.8617 736,889 z "
+ style="fill:#373737;stroke:#373737"
+ id="path2124" />
+ <path
+ d="M 658,896 L 659.75,896.25 C 662.867,899.447 666.363,902.475 670.75,903.25 L 671,904 L 669,906 L 659.25,907.25 L 653.25,912.75 L 653,911 C 655.505,907.438 659.061,903.586 663.75,903.75 L 663.75,902.25 C 661.31,901.178 658.238,898.801 658,896 z "
+ style="fill:#303030;stroke:#303030"
+ id="path2125" />
+ <path
+ d="M 735,916 L 736.75,917.75 L 733.75,917.75 L 735,916 z "
+ style="fill:#b5b5b5;stroke:#b5b5b5"
+ id="path2126" />
+ <path
+ d="M 771,923 L 772,924 C 769.064,928.82 767.842,934.06 767.75,939.75 L 766.25,938.75 C 764.117,934.543 761.824,930.246 763.25,925.25 L 766.75,927.75 L 771,923 z "
+ style="fill:#dbdbdb;stroke:#dbdbdb"
+ id="path2127" />
+ <path
+ d="M 590,925 L 591.75,926.75 C 577.94,941.45 574.4,966.62 588.75,981.75 C 590.47,969.94 588.8797,955.14 600,947 L 600.75,948.75 C 591.326,957.674 592.132,971.04 591,983 L 624.75,1030.75 C 625.0984,1021.853 620.798,1013.43 615.75,1006.25 L 610.25,997.25 L 611.75,997.25 L 622.75,1013.25 C 625.867,1021.254 629.005,1030.28 627.25,1038.75 L 629.75,1039.75 L 616.25,1052.25 L 610.25,1057.75 L 615.25,1050.25 L 623.75,1041.75 L 622.75,1032.25 C 608.28,1010.99 591.26,990.76 573.25,972.75 L 570.25,966.75 C 566.749,955.51 570.6867,943.91 573.25,933.25 L 574.75,932.25 L 575,937 C 571.3,944.982 573.442,954.94 573.25,963.75 L 576.75,969.75 C 573.532,954.26 577.2103,936.26 590,925 z "
+ style="fill:#2a2a2a;stroke:#2a2a2a"
+ id="path2128" />
+ <path
+ d="M 728,930 C 730.5,931.735 733.536,932.805 735.75,930.25 L 735.75,931.75 L 733.75,935.75 C 728.798,941.591 727.991,949.24 727.75,956.75 C 724.618,954.819 721.521,949.56 724,946 L 724,945 C 723.5238,939.476 725.762,934.74 728,930 z "
+ style="fill:#e7e7e7;stroke:#e7e7e7"
+ id="path2129" />
+ <path
+ d="M 620,947 C 622.199,946.2726 625.76,947.6509 622.75,949.75 C 604.14,961.32 607.96,988.47 624.75,1000.25 C 628.542,1007.557 635.88,1012.15 642.75,1016.25 L 646.75,1020.25 L 652.75,1034.25 L 651.75,1037.75 C 642.925,1046.572 639.32,1058.87 639,1071 C 641.634,1078.447 647.442,1085.41 654.75,1088.75 C 653.346,1076.76 653.534,1063.17 658.25,1052.25 L 658,1061 L 656.25,1068.25 C 651.154,1094.44 674.14,1113.14 694.75,1123.25 L 694.75,1125.75 C 679.88,1118.758 665.38,1107.98 656.75,1093.25 C 639.74,1090.235 620.81,1075.09 625.75,1055.75 L 619.75,1060.75 C 613.395,1068.574 611.911,1078.65 613.25,1088.75 C 617.902,1096.551 627.32,1106.16 637,1103 L 638.75,1104.75 C 627.62,1109.36 615.92,1098.519 611,1089 C 609.451,1075.5 612.651,1062.82 622.75,1053.75 L 628.25,1045.25 C 630.472,1047.15 632.156,1044.203 633.75,1042.75 C 633.3624,1036.916 634.953,1031.79 638,1027 L 634.75,1017.25 C 622.51,1006.04 610.19,993.12 605,977 L 606,973 C 605.087,962.27 613.857,954.71 620,947 z "
+ style="fill:#353535;stroke:#353535"
+ id="path2130" />
+ <path
+ d="M 443,974 L 451.25,978.75 C 458.391,982.383 463.59,990.31 461.75,998.75 L 459.75,1000.75 L 455.25,1000.75 L 456.25,999.25 L 458.75,998.75 C 463.285,988.913 452.008,980.62 444.25,976.75 L 443,974 z "
+ style="fill:#353535;stroke:#353535"
+ id="path2131" />
+ <path
+ d="M 724,1007 L 724.25,1005.25 L 734.25,991.25 L 738.25,985.25 L 739.75,985.25 C 740.261,993.203 733.527,999.95 727.25,1004.25 L 726.75,1005.75 L 724,1007 z "
+ style="fill:#d9d9d9;stroke:#d9d9d9"
+ id="path2132" />
+ <path
+ d="M 724,1007 L 723,1008 L 724,1007 z "
+ style="fill:#bebebe;stroke:#bebebe"
+ id="path2133" />
+ <path
+ d="M 723,1008 C 723.1398,1009.885 721.115,1007.8603 723,1008 z "
+ style="fill:#9b9b9b;stroke:#9b9b9b"
+ id="path2134" />
+ <path
+ d="M 426,1014 L 427.75,1014.25 C 429.571,1017.4 431.5,1022.002 435.75,1022.25 L 438,1023 L 438.75,1023.75 L 437,1025 C 431.818,1024.4138 426.09,1019.58 426,1014 z "
+ style="fill:#3b3b3b;stroke:#3b3b3b"
+ id="path2135" />
+ <path
+ d="M 638,1017 C 636.115,1017.1398 638.1397,1015.115 638,1017 z "
+ style="fill:#d8d8d8;stroke:#d8d8d8"
+ id="path2136" />
+ <path
+ d="M 638,1017 L 643.75,1020.25 L 646,1025 L 645,1029 L 643.25,1033.25 L 637.25,1038.75 L 637,1035 L 640,1026 L 640.75,1024.25 L 638.25,1018.75 L 638,1017 z "
+ style="fill:#f3f3f3;stroke:#f3f3f3"
+ id="path2137" />
+ <path
+ d="M 705,1019 C 708.831,1018.7223 704.7036,1021.624 703.75,1022.75 L 698.25,1024.25 C 689.865,1032.162 678.03,1038.76 675.75,1050.75 L 674,1058 C 670.693,1062.154 671.571,1069.23 673,1074 L 672.75,1085.75 L 665.25,1076.75 C 663.589,1063.02 669.204,1050.81 675.25,1039.25 C 683.647,1031.815 691.68,1023.17 702.75,1020.75 L 705,1019 z "
+ style="fill:#e9e9e9;stroke:#e9e9e9"
+ id="path2138" />
+ <path
+ d="M 699,1028 C 698.8602,1026.115 700.885,1028.1398 699,1028 z "
+ style="fill:#a7a7a7;stroke:#a7a7a7"
+ id="path2139" />
+ <path
+ d="M 699,1028 L 698,1029 L 699,1028 z "
+ style="fill:#949494;stroke:#949494"
+ id="path2140" />
+ <path
+ d="M 698,1029 L 694.75,1034.75 L 686.25,1048.25 L 686,1052 L 684,1056 L 682,1075 L 686.75,1095.25 L 686.75,1096.75 L 681.25,1092.75 L 675.25,1084.75 L 674.25,1063.25 C 676.688,1051.75 681.721,1039.42 692.75,1032.75 L 696.25,1029.25 L 698,1029 z "
+ style="fill:#eee;stroke:#eee"
+ id="path2141" />
+ <path
+ d="M 647,1033 L 648,1035 L 646.75,1038.75 C 641.912,1044.92 639.915,1052.5 637.75,1059.75 L 637.25,1059.75 L 637.25,1043.25 L 643.75,1038.75 L 647,1033 z "
+ style="fill:#f6f6f6;stroke:#f6f6f6"
+ id="path2142" />
+ <path
+ d="M 633,1047 L 634,1048 L 634,1066 L 641.75,1082.25 L 641.75,1083.75 C 634.066,1081.68 630.89,1073.38 628,1067 L 629.25,1050.25 L 633,1047 z "
+ style="fill:#f8f8f8;stroke:#f8f8f8"
+ id="path2143" />
+ </g>
+</svg>
diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/svg/tests/kis_svg_test.cpp
new file mode 100644
index 0000000000..6b47e8aadf
--- /dev/null
+++ b/plugins/impex/svg/tests/kis_svg_test.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "kis_svg_test.h"
+
+
+#include <QTest>
+#include <QCoreApplication>
+
+#include <QTest>
+
+#include "filestest.h"
+
+#ifndef FILES_DATA_DIR
+#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita"
+#endif
+
+
+void KisSvgTest::testFiles()
+{
+ TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList());
+}
+QTEST_MAIN(KisSvgTest)
+
diff --git a/plugins/impex/svg/tests/kis_svg_test.h b/plugins/impex/svg/tests/kis_svg_test.h
new file mode 100644
index 0000000000..66cf670f4d
--- /dev/null
+++ b/plugins/impex/svg/tests/kis_svg_test.h
@@ -0,0 +1,31 @@
+/*
+ * 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 _KIS_SVG_TEST_H_
+#define _KIS_SVG_TEST_H_
+
+#include <QtTest>
+
+class KisSvgTest : public QObject
+{
+ Q_OBJECT
+private Q_SLOTS:
+ void testFiles();
+};
+
+#endif
diff --git a/plugins/impex/tiff/kis_tiff_export.h b/plugins/impex/tiff/kis_tiff_export.h
index 4bd4517bbd..6def27cfac 100644
--- a/plugins/impex/tiff/kis_tiff_export.h
+++ b/plugins/impex/tiff/kis_tiff_export.h
@@ -1,42 +1,42 @@
/*
* Copyright (c) 2005 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_TIFF_EXPORT_H_
#define _KIS_TIFF_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
#include <kis_config_widget.h>
class KisTIFFExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTIFFExport(QObject *parent, const QVariantList &);
virtual ~KisTIFFExport();
-public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
void initializeCapabilities();
};
#endif
diff --git a/plugins/impex/tiff/kis_tiff_import.h b/plugins/impex/tiff/kis_tiff_import.h
index 99e04c4a38..78b85bd6ef 100644
--- a/plugins/impex/tiff/kis_tiff_import.h
+++ b/plugins/impex/tiff/kis_tiff_import.h
@@ -1,36 +1,37 @@
/*
* Copyright (c) 2005 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_TIFF_IMPORT_H_
#define _KIS_TIFF_IMPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisTIFFImport : public KisImportExportFilter
{
Q_OBJECT
public:
KisTIFFImport(QObject *parent, const QVariantList &);
virtual ~KisTIFFImport();
+ virtual bool supportsIO() const { return false; }
public:
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
};
#endif
diff --git a/plugins/impex/video/kis_video_export.h b/plugins/impex/video/kis_video_export.h
index 4c1edf0c4a..6daadd5d97 100644
--- a/plugins/impex/video/kis_video_export.h
+++ b/plugins/impex/video/kis_video_export.h
@@ -1,42 +1,43 @@
/*
* Copyright (c) 2016 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_VIDEO_EXPORT_H_
#define _KIS_VIDEO_EXPORT_H_
#include <QVariant>
#include <KisImportExportFilter.h>
class KisVideoExport : public KisImportExportFilter
{
Q_OBJECT
public:
KisVideoExport(QObject *parent, const QVariantList &);
virtual ~KisVideoExport();
public:
+ virtual bool supportsIO() const { return false; }
virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0);
KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from, const QByteArray& to) const;
KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const;
KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const;
};
#endif
diff --git a/plugins/paintops/experiment/kis_experiment_paintop.cpp b/plugins/paintops/experiment/kis_experiment_paintop.cpp
index d23c735498..3f45baec80 100644
--- a/plugins/paintops/experiment/kis_experiment_paintop.cpp
+++ b/plugins/paintops/experiment/kis_experiment_paintop.cpp
@@ -1,384 +1,336 @@
/*
* Copyright (c) 2010-2011 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2012 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_experiment_paintop.h"
#include "kis_experiment_paintop_settings.h"
#include <cmath>
#include <KoCompositeOpRegistry.h>
#include <kis_debug.h>
#include <kis_paint_device.h>
#include <kis_painter.h>
#include <kis_image.h>
#include <krita_utils.h>
KisExperimentPaintOp::KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image)
: KisPaintOp(painter)
{
Q_UNUSED(image);
Q_UNUSED(node);
m_firstRun = true;
m_experimentOption.readOptionSetting(settings);
m_displaceEnabled = m_experimentOption.isDisplacementEnabled;
m_displaceCoeff = (m_experimentOption.displacement * 0.01 * 14) + 1; // 1..15 [7 default according alchemy]
m_speedEnabled = m_experimentOption.isSpeedEnabled;
m_speedMultiplier = (m_experimentOption.speed * 0.01 * 35); // 0..35 [15 default according alchemy]
m_smoothingEnabled = m_experimentOption.isSmoothingEnabled;
m_smoothingThreshold = m_experimentOption.smoothing;
m_useMirroring = painter->hasMirroring();
m_windingFill = m_experimentOption.windingFill;
m_hardEdge = m_experimentOption.hardEdge;
if (m_useMirroring) {
m_originalDevice = source()->createCompositionSourceDevice();
m_originalPainter = new KisPainter(m_originalDevice);
m_originalPainter->setCompositeOp(COMPOSITE_COPY);
m_originalPainter->setPaintColor(painter->paintColor());
m_originalPainter->setFillStyle(KisPainter::FillStyleForegroundColor);
}
else {
m_originalPainter = 0;
}
}
KisExperimentPaintOp::~KisExperimentPaintOp()
{
delete m_originalPainter;
}
void KisExperimentPaintOp::paintRegion(const QRegion &changedRegion)
{
if (m_windingFill) {
m_path.setFillRule(Qt::WindingFill);
}
if (m_useMirroring) {
m_originalPainter->setAntiAliasPolygonFill(!m_hardEdge);
Q_FOREACH (const QRect & rect, changedRegion.rects()) {
m_originalPainter->fillPainterPath(m_path, rect);
painter()->renderDabWithMirroringNonIncremental(rect, m_originalDevice);
}
}
else {
painter()->setFillStyle(KisPainter::FillStyleForegroundColor);
painter()->setCompositeOp(COMPOSITE_COPY);
painter()->setAntiAliasPolygonFill(!m_hardEdge);
Q_FOREACH (const QRect & rect, changedRegion.rects()) {
painter()->fillPainterPath(m_path, rect);
}
}
}
QPointF KisExperimentPaintOp::speedCorrectedPosition(const KisPaintInformation& pi1,
const KisPaintInformation& pi2)
{
const qreal fadeFactor = 0.6;
QPointF diff = pi2.pos() - pi1.pos();
qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
if (realLength < 0.1) return pi2.pos();
qreal coeff = 0.5 * realLength * m_speedMultiplier;
m_savedSpeedCoeff = fadeFactor * m_savedSpeedCoeff + (1 - fadeFactor) * coeff;
QPointF newPoint = pi1.pos() + diff * m_savedSpeedCoeff / realLength;
m_savedSpeedPoint = fadeFactor * m_savedSpeedPoint + (1 - fadeFactor) * newPoint;
return m_savedSpeedPoint;
}
void KisExperimentPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance)
{
Q_UNUSED(currentDistance);
if (!painter()) return;
if (m_firstRun) {
m_firstRun = false;
m_path.moveTo(pi1.pos());
m_path.lineTo(pi2.pos());
m_center = pi1.pos();
m_savedUpdateDistance = 0;
m_lastPaintTime = 0;
m_savedSpeedCoeff = 0;
m_savedSpeedPoint = m_center;
m_savedSmoothingDistance = 0;
m_savedSmoothingPoint = m_center;
}
else {
const QPointF pos1 = pi1.pos();
QPointF pos2 = pi2.pos();
if (m_speedEnabled) {
pos2 = speedCorrectedPosition(pi1, pi2);
}
int length = (pos2 - pos1).manhattanLength();
m_savedUpdateDistance += length;
if (m_smoothingEnabled) {
m_savedSmoothingDistance += length;
if (m_savedSmoothingDistance > m_smoothingThreshold) {
QPointF pt = (m_savedSmoothingPoint + pos2) * 0.5;
// for updates approximate curve with two lines
m_savedPoints << m_path.currentPosition();
m_savedPoints << m_savedSmoothingPoint;
m_savedPoints << m_savedSmoothingPoint;
m_savedPoints << pt;
m_path.quadTo(m_savedSmoothingPoint, pt);
m_savedSmoothingPoint = pos2;
m_savedSmoothingDistance = 0;
}
}
else {
m_path.lineTo(pos2);
m_savedPoints << pos1;
m_savedPoints << pos2;
}
if (m_displaceEnabled) {
if (m_path.elementCount() % 16 == 0) {
QRectF bounds = m_path.boundingRect();
m_path = applyDisplace(m_path, m_displaceCoeff - length);
bounds |= m_path.boundingRect();
qreal threshold = simplifyThreshold(bounds);
- m_path = trySimplifyPath(m_path, threshold);
+ m_path = KritaUtils::trySimplifyPath(m_path, threshold);
}
else {
m_path = applyDisplace(m_path, m_displaceCoeff - length);
}
}
/**
* Refresh rate at least 25fps
*/
const int timeThreshold = 40;
const int elapsedTime = pi2.currentTime() - m_lastPaintTime;
QRect pathBounds = m_path.boundingRect().toRect();
int distanceMetric = qMax(pathBounds.width(), pathBounds.height());
if (elapsedTime > timeThreshold ||
(!m_displaceEnabled &&
m_savedUpdateDistance > distanceMetric / 8)) {
if (m_displaceEnabled) {
/**
* Rendering the path with diff'ed rects is up to two
* times more efficient for really huge shapes (tested
* on 2000+ px shapes), however for smaller ones doing
* paths arithmetics eats too much time. That's why we
* choose the method on the base of the size of the
* shape.
*/
const int pathSizeThreshold = 128;
QRegion changedRegion;
if (distanceMetric < pathSizeThreshold) {
QRectF changedRect = m_path.boundingRect().toRect() |
m_lastPaintedPath.boundingRect().toRect();
changedRect.adjust(-1, -1, 1, 1);
changedRegion = changedRect.toRect();
}
else {
QPainterPath diff1 = m_path - m_lastPaintedPath;
QPainterPath diff2 = m_lastPaintedPath - m_path;
changedRegion = KritaUtils::splitPath(diff1 | diff2);
}
paintRegion(changedRegion);
m_lastPaintedPath = m_path;
}
else if (!m_savedPoints.isEmpty()) {
QRegion changedRegion = KritaUtils::splitTriangles(m_center, m_savedPoints);
paintRegion(changedRegion);
}
m_savedPoints.clear();
m_savedUpdateDistance = 0;
m_lastPaintTime = pi2.currentTime();
}
}
}
KisSpacingInformation KisExperimentPaintOp::paintAt(const KisPaintInformation& info)
{
Q_UNUSED(info);
return KisSpacingInformation(1.0);
}
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 (distance != 0) {
path.lineTo(startPoint);
}
distance = 0;
return false;
}
distance += length;
if (distance > distanceThreshold) {
path.lineTo(endPoint);
distance = 0;
}
return true;
}
qreal KisExperimentPaintOp::simplifyThreshold(const QRectF &bounds)
{
qreal maxDimension = qMax(bounds.width(), bounds.height());
return qMax(0.01 * maxDimension, 1.0);
}
-QPainterPath KisExperimentPaintOp::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;
-}
-
QPointF KisExperimentPaintOp::getAngle(const QPointF& p1, const QPointF& p2, qreal distance)
{
QPointF diff = p1 - p2;
qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
return realLength > 0.5 ? p1 + diff * distance / realLength : p1;
}
QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int speed)
{
QPointF lastPoint = path.currentPosition();
QPainterPath newPath;
int count = path.elementCount();
int curveElementCounter = 0;
QPointF ctrl1;
QPointF ctrl2;
QPointF endPoint;
for (int i = 0; i < count; i++) {
QPainterPath::Element e = path.elementAt(i);
switch (e.type) {
case QPainterPath::MoveToElement: {
newPath.moveTo(getAngle(QPointF(e.x, e.y), lastPoint, speed));
break;
}
case QPainterPath::LineToElement: {
newPath.lineTo(getAngle(QPointF(e.x, e.y), lastPoint, speed));
break;
}
case QPainterPath::CurveToElement: {
curveElementCounter = 0;
endPoint = getAngle(QPointF(e.x, e.y), lastPoint, speed);
break;
}
case QPainterPath::CurveToDataElement: {
curveElementCounter++;
if (curveElementCounter == 1) {
ctrl1 = getAngle(QPointF(e.x, e.y), lastPoint, speed);
}
else if (curveElementCounter == 2) {
ctrl2 = getAngle(QPointF(e.x, e.y), lastPoint, speed);
newPath.cubicTo(ctrl1, ctrl2, endPoint);
}
break;
}
}
}// for
return newPath;
}
diff --git a/plugins/paintops/experiment/kis_experiment_paintop.h b/plugins/paintops/experiment/kis_experiment_paintop.h
index 5c98e70bc6..0a8dc4f42f 100644
--- a/plugins/paintops/experiment/kis_experiment_paintop.h
+++ b/plugins/paintops/experiment/kis_experiment_paintop.h
@@ -1,87 +1,86 @@
/*
* Copyright (c) 2010-2011 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_EXPERIMENT_PAINTOP_H_
#define KIS_EXPERIMENT_PAINTOP_H_
#include <klocalizedstring.h>
#include <brushengine/kis_paintop.h>
#include <kis_types.h>
#include "kis_experiment_paintop_settings.h"
#include "kis_experimentop_option.h"
class QPointF;
class KisPainter;
class KisExperimentPaintOp : public KisPaintOp
{
public:
KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image);
~KisExperimentPaintOp();
void paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance);
KisSpacingInformation paintAt(const KisPaintInformation& info);
private:
void paintRegion(const QRegion &changedRegion);
QPointF speedCorrectedPosition(const KisPaintInformation& pi1,
const KisPaintInformation& pi2);
static qreal simplifyThreshold(const QRectF &bounds);
- static QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold);
static QPointF getAngle(const QPointF& p1, const QPointF& p2, qreal distance);
static QPainterPath applyDisplace(const QPainterPath& path, int speed);
bool m_displaceEnabled;
int m_displaceCoeff;
QPainterPath m_lastPaintedPath;
bool m_windingFill;
bool m_hardEdge;
bool m_speedEnabled;
int m_speedMultiplier;
qreal m_savedSpeedCoeff;
QPointF m_savedSpeedPoint;
bool m_smoothingEnabled;
int m_smoothingThreshold;
QPointF m_savedSmoothingPoint;
int m_savedSmoothingDistance;
int m_savedUpdateDistance;
QVector<QPointF> m_savedPoints;
int m_lastPaintTime;
bool m_firstRun;
QPointF m_center;
QPainterPath m_path;
ExperimentOption m_experimentOption;
bool m_useMirroring;
KisPainter *m_originalPainter;
KisPaintDeviceSP m_originalDevice;
};
#endif // KIS_EXPERIMENT_PAINTOP_H_
diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp
index 2d378181f5..fb4e3c612e 100644
--- a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp
+++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp
@@ -1,340 +1,342 @@
/*
* 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.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"
struct BrushReader {
BrushReader(const KisBrushBasedPaintOpSettings *parent)
: m_parent(parent)
{
m_option.readOptionSetting(m_parent);
}
KisBrushSP brush() {
return m_option.brush();
}
const KisBrushBasedPaintOpSettings *m_parent;
KisBrushOption 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;
KisBrushOption m_option;
};
KisBrushBasedPaintOpSettings::KisBrushBasedPaintOpSettings()
: KisOutlineGenerationPolicy<KisPaintOpSettings>(KisCurrentOutlineFetcher::SIZE_OPTION |
KisCurrentOutlineFetcher::ROTATION_OPTION |
KisCurrentOutlineFetcher::MIRROR_OPTION)
{
}
bool KisBrushBasedPaintOpSettings::paintIncremental()
{
if (hasProperty("PaintOpAction")) {
return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP;
}
return true;
}
bool KisBrushBasedPaintOpSettings::isAirbrushing() const
{
return getBool(AIRBRUSH_ENABLED);
}
int KisBrushBasedPaintOpSettings::rate() const
{
return getInt(AIRBRUSH_RATE);
}
KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const
{
KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy<KisPaintOpSettings>::clone();
KisBrushBasedPaintOpSettingsSP settings = dynamic_cast<KisBrushBasedPaintOpSettings*>(_settings.data());
settings->m_savedBrush = this->brush();
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,
OutlineMode mode,
qreal additionalScale,
bool forceOutline)
{
QPainterPath path;
if (forceOutline || mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) {
KisBrushSP brush = this->brush();
if (!brush) return path;
qreal finalScale = brush->scale() * additionalScale;
QPainterPath realOutline = brush->outline();
if (mode == CursorIsCircleOutline || mode == CursorTiltOutline ||
(forceOutline && mode == CursorNoOutline)) {
QPainterPath ellipse;
ellipse.addEllipse(realOutline.boundingRect());
realOutline = ellipse;
}
path = outlineFetcher()->fetchOutline(info, this, realOutline, finalScale, brush->angle());
if (mode == CursorTiltOutline) {
QPainterPath tiltLine = makeTiltIndicator(info,
realOutline.boundingRect().center(),
realOutline.boundingRect().width() * 0.5,
3.0);
path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, finalScale, 0.0, true, realOutline.boundingRect().center().x(), realOutline.boundingRect().center().y()));
}
}
return path;
}
QPainterPath KisBrushBasedPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode)
{
return brushOutlineImpl(info, mode, 1.0);
}
bool KisBrushBasedPaintOpSettings::isValid() const
{
QString filename = getString("requiredBrushFile", QString());
if (!filename.isEmpty()) {
KisBrushSP brush = KisBrushServer::instance()->brushServer()->resourceByFilename(filename);
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());
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->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_spacing_selection_widget.cpp b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
index 3d486c6cf7..4723ea69aa 100644
--- a/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
+++ b/plugins/paintops/libpaintop/kis_spacing_selection_widget.cpp
@@ -1,129 +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)
{
}
KisSpacingSelectionWidget *q;
KisDoubleSliderSpinBox *slider;
QCheckBox *autoButton;
qreal oldSliderValue;
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.0, 10.0, 2);
+ m_d->slider->setRange(0.01, 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/spray/kis_spray_paintop_settings.cpp b/plugins/paintops/spray/kis_spray_paintop_settings.cpp
index 6138ede053..154562824b 100644
--- a/plugins/paintops/spray/kis_spray_paintop_settings.cpp
+++ b/plugins/paintops/spray/kis_spray_paintop_settings.cpp
@@ -1,238 +1,239 @@
/*
* Copyright (c) 2008,2009,2010 Lukáš Tvrdý <lukast.dev@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <cmath>
#include <kis_paint_action_type_option.h>
#include <kis_color_option.h>
#include "kis_spray_paintop_settings.h"
#include "kis_sprayop_option.h"
#include "kis_spray_shape_option.h"
#include <kis_airbrush_option.h>
struct KisSprayPaintOpSettings::Private
{
QList<KisUniformPaintOpPropertyWSP> uniformProperties;
};
KisSprayPaintOpSettings::KisSprayPaintOpSettings()
: KisOutlineGenerationPolicy<KisPaintOpSettings>(KisCurrentOutlineFetcher::SIZE_OPTION |
KisCurrentOutlineFetcher::ROTATION_OPTION),
m_d(new Private)
{
}
KisSprayPaintOpSettings::~KisSprayPaintOpSettings()
{
}
void KisSprayPaintOpSettings::setPaintOpSize(qreal value)
{
KisSprayProperties option;
option.readOptionSetting(this);
option.diameter = value;
option.writeOptionSetting(this);
}
qreal KisSprayPaintOpSettings::paintOpSize() const
{
KisSprayProperties option;
option.readOptionSetting(this);
return option.diameter;
}
bool KisSprayPaintOpSettings::paintIncremental()
{
return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP;
}
bool KisSprayPaintOpSettings::isAirbrushing() const
{
return getBool(AIRBRUSH_ENABLED);
}
int KisSprayPaintOpSettings::rate() const
{
return getInt(AIRBRUSH_RATE);
}
QPainterPath KisSprayPaintOpSettings::brushOutline(const KisPaintInformation &info, OutlineMode mode)
{
QPainterPath path;
if (mode == CursorIsOutline || mode == CursorIsCircleOutline || mode == CursorTiltOutline) {
qreal width = getInt(SPRAY_DIAMETER);
qreal height = getInt(SPRAY_DIAMETER) * getDouble(SPRAY_ASPECT);
path = ellipseOutline(width, height, getDouble(SPRAY_SCALE), getDouble(SPRAY_ROTATION));
QPainterPath tiltLine;
QLineF tiltAngle(QPointF(0.0,0.0), QPointF(0.0,width));
tiltAngle.setLength(qMax(width*0.5, 50.0) * (1 - info.tiltElevation(info, 60.0, 60.0, true)));
tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))-3.0);
tiltLine.moveTo(tiltAngle.p1());
tiltLine.lineTo(tiltAngle.p2());
tiltAngle.setAngle((360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0))+3.0);
tiltLine.lineTo(tiltAngle.p2());
tiltLine.lineTo(tiltAngle.p1());
path = outlineFetcher()->fetchOutline(info, this, path);
if (mode == CursorTiltOutline) {
QPainterPath tiltLine =
makeTiltIndicator(info, QPointF(0.0, 0.0), width * 0.5, 3.0);
path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, 1.0, 0.0, true, 0, 0));
}
}
return path;
}
#include <brushengine/kis_slider_based_paintop_property.h>
#include "kis_paintop_preset.h"
#include "kis_paintop_settings_update_proxy.h"
#include "kis_standard_uniform_properties_factory.h"
QList<KisUniformPaintOpPropertySP> KisSprayPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings)
{
QList<KisUniformPaintOpPropertySP> props =
listWeakToStrong(m_d->uniformProperties);
if (props.isEmpty()) {
{
KisDoubleSliderBasedPaintOpPropertyCallback *prop =
new KisDoubleSliderBasedPaintOpPropertyCallback(
KisDoubleSliderBasedPaintOpPropertyCallback::Double,
"spacing",
i18n("Spacing"),
settings, 0);
prop->setRange(0.01, 10);
prop->setSingleStep(0.01);
+ prop->setExponentRatio(3.0);
prop->setReadCallback(
[](KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
prop->setValue(option.spacing);
});
prop->setWriteCallback(
[](KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
option.spacing = prop->value().toReal();
option.writeOptionSetting(prop->settings().data());
});
QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue()));
prop->requestReadValue();
props << toQShared(prop);
}
{
KisIntSliderBasedPaintOpPropertyCallback *prop =
new KisIntSliderBasedPaintOpPropertyCallback(
KisIntSliderBasedPaintOpPropertyCallback::Int,
"spray_particlecount",
i18n("Particle Count"),
settings, 0);
prop->setRange(0, 1000);
prop->setExponentRatio(3);
prop->setReadCallback(
[](KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
prop->setValue(int(option.particleCount));
});
prop->setWriteCallback(
[](KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
option.particleCount = prop->value().toInt();
option.writeOptionSetting(prop->settings().data());
});
prop->setIsVisibleCallback(
[](const KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
return !option.useDensity;
});
QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue()));
prop->requestReadValue();
props << toQShared(prop);
}
{
KisDoubleSliderBasedPaintOpPropertyCallback *prop =
new KisDoubleSliderBasedPaintOpPropertyCallback(
KisDoubleSliderBasedPaintOpPropertyCallback::Double,
"spray_density",
i18n("Density"),
settings, 0);
prop->setRange(0.1, 100);
prop->setSingleStep(0.01);
prop->setDecimals(2);
prop->setExponentRatio(3);
prop->setSuffix(i18n("%"));
prop->setReadCallback(
[](KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
prop->setValue(option.coverage);
});
prop->setWriteCallback(
[](KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
option.coverage = prop->value().toReal();
option.writeOptionSetting(prop->settings().data());
});
prop->setIsVisibleCallback(
[](const KisUniformPaintOpProperty *prop) {
KisSprayProperties option;
option.readOptionSetting(prop->settings().data());
return option.useDensity;
});
QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue()));
prop->requestReadValue();
props << toQShared(prop);
}
}
{
using namespace KisStandardUniformPropertiesFactory;
Q_FOREACH (KisUniformPaintOpPropertySP prop, KisPaintOpSettings::uniformProperties(settings)) {
if (prop->id() == opacity.id() ||
prop->id() == size.id()) {
props.prepend(prop);
}
}
}
return props;
}
diff --git a/plugins/tools/basictools/kis_tool_ellipse.cc b/plugins/tools/basictools/kis_tool_ellipse.cc
index 2a6061fe18..91410c1392 100644
--- a/plugins/tools/basictools/kis_tool_ellipse.cc
+++ b/plugins/tools/basictools/kis_tool_ellipse.cc
@@ -1,87 +1,87 @@
/*
* kis_tool_ellipse.cc - part of Krayon
*
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
* Copyright (c) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_ellipse.h"
#include <KoCanvasBase.h>
#include <KoShapeStroke.h>
#include <kis_shape_tool_helper.h>
#include "kis_figure_painting_tool_helper.h"
#include <brushengine/kis_paintop_preset.h>
#include <kis_system_locker.h>
#include <recorder/kis_action_recorder.h>
#include <recorder/kis_recorded_shape_paint_action.h>
#include <recorder/kis_node_query_path.h>
KisToolEllipse::KisToolEllipse(KoCanvasBase * canvas)
: KisToolEllipseBase(canvas, KisToolEllipseBase::PAINT, KisCursor::load("tool_ellipse_cursor.png", 6, 6))
{
setObjectName("tool_ellipse");
setSupportOutline(true);
}
KisToolEllipse::~KisToolEllipse()
{
}
void KisToolEllipse::resetCursorStyle()
{
KisToolEllipseBase::resetCursorStyle();
overrideCursorIfNotEditable();
}
void KisToolEllipse::finishRect(const QRectF& rect)
{
if (rect.isEmpty())
return;
if (image()) {
KisRecordedShapePaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset(), KisRecordedShapePaintAction::Ellipse, rect);
setupPaintAction(&linePaintAction);
image()->actionRecorder()->addAction(linePaintAction);
}
if (!currentNode()->inherits("KisShapeLayer")) {
KisSystemLocker locker(currentNode());
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Ellipse"),
image(),
currentNode(),
canvas()->resourceManager(),
strokeStyle(),
fillStyle());
helper.paintEllipse(rect);
} else {
QRectF r = convertToPt(rect);
KoShape* shape = KisShapeToolHelper::createEllipseShape(r);
- KoShapeStroke* border = new KoShapeStroke(1.0, currentFgColor().toQColor());
+ KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor()));
shape->setStroke(border);
addShape(shape);
}
notifyModified();
}
diff --git a/plugins/tools/basictools/kis_tool_line.cc b/plugins/tools/basictools/kis_tool_line.cc
index 34d4dc53dc..bcb1607db8 100644
--- a/plugins/tools/basictools/kis_tool_line.cc
+++ b/plugins/tools/basictools/kis_tool_line.cc
@@ -1,374 +1,374 @@
/*
* kis_tool_line.cc - part of Krayon
*
* Copyright (c) 2000 John Califf <jwcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2003 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2007,2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_line.h"
#include <QPushButton>
#include <ksharedconfig.h>
#include <KoCanvasBase.h>
#include <KoPointerEvent.h>
#include <KoPathShape.h>
#include <KoShapeController.h>
#include <KoShapeStroke.h>
#include <kis_debug.h>
#include <kis_cursor.h>
#include <brushengine/kis_paintop_registry.h>
#include "kis_figure_painting_tool_helper.h"
#include "kis_canvas2.h"
#include <recorder/kis_action_recorder.h>
#include <recorder/kis_recorded_path_paint_action.h>
#include <recorder/kis_node_query_path.h>
#include "kis_painting_information_builder.h"
#include "kis_tool_line_helper.h"
#define ENABLE_RECORDING
const KisCoordinatesConverter* getCoordinatesConverter(KoCanvasBase * canvas)
{
KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
return kritaCanvas->coordinatesConverter();
}
KisToolLine::KisToolLine(KoCanvasBase * canvas)
- : KisToolPaint(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)),
+ : KisToolShape(canvas, KisCursor::load("tool_line_cursor.png", 6, 6)),
m_showGuideline(true),
m_strokeIsRunning(false),
m_infoBuilder(new KisConverterPaintingInformationBuilder(getCoordinatesConverter(canvas))),
m_helper(new KisToolLineHelper(m_infoBuilder.data(), kundo2_i18n("Draw Line"))),
m_strokeUpdateCompressor(500, KisSignalCompressor::POSTPONE),
m_longStrokeUpdateCompressor(1000, KisSignalCompressor::FIRST_INACTIVE)
{
setObjectName("tool_line");
setSupportOutline(true);
connect(&m_strokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke()));
connect(&m_longStrokeUpdateCompressor, SIGNAL(timeout()), SLOT(updateStroke()));
}
KisToolLine::~KisToolLine()
{
}
int KisToolLine::flags() const
{
return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET;
}
void KisToolLine::resetCursorStyle()
{
KisToolPaint::resetCursorStyle();
overrideCursorIfNotEditable();
}
void KisToolLine::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
KisToolPaint::activate(activation, shapes);
configGroup = KSharedConfig::openConfig()->group(toolId());
}
void KisToolLine::deactivate()
{
KisToolPaint::deactivate();
cancelStroke();
}
QWidget* KisToolLine::createOptionWidget()
{
QWidget* widget = KisToolPaint::createOptionWidget();
m_chkUseSensors = new QCheckBox(i18n("Use sensors"));
addOptionWidgetOption(m_chkUseSensors);
m_chkShowPreview = new QCheckBox(i18n("Show Preview"));
addOptionWidgetOption(m_chkShowPreview);
m_chkShowGuideline = new QCheckBox(i18n("Show Guideline"));
addOptionWidgetOption(m_chkShowGuideline);
// hook up connections for value changing
connect(m_chkUseSensors, SIGNAL(clicked(bool)), this, SLOT(setUseSensors(bool)) );
connect(m_chkShowPreview, SIGNAL(clicked(bool)), this, SLOT(setShowPreview(bool)) );
connect(m_chkShowGuideline, SIGNAL(clicked(bool)), this, SLOT(setShowGuideline(bool)) );
// read values in from configuration
m_chkUseSensors->setChecked(configGroup.readEntry("useSensors", true));
m_chkShowPreview->setChecked(configGroup.readEntry("showPreview", true));
m_chkShowGuideline->setChecked(configGroup.readEntry("showGuideline", true));
return widget;
}
void KisToolLine::setUseSensors(bool value)
{
configGroup.writeEntry("useSensors", value);
}
void KisToolLine::setShowGuideline(bool value)
{
m_showGuideline = value;
configGroup.writeEntry("showGuideline", value);
}
void KisToolLine::setShowPreview(bool value)
{
configGroup.writeEntry("showPreview", value);
}
void KisToolLine::requestStrokeCancellation()
{
cancelStroke();
}
void KisToolLine::requestStrokeEnd()
{
// Terminate any in-progress strokes
if (nodePaintAbility() == PAINT && m_helper->isRunning()) {
endStroke();
}
}
void KisToolLine::updatePreviewTimer(bool showGuideline)
{
// If the user disables the guideline, we will want to try to draw some
// preview lines even if they're slow, so set the timer to FIRST_ACTIVE.
if (showGuideline) {
m_strokeUpdateCompressor.setMode(KisSignalCompressor::POSTPONE);
} else {
m_strokeUpdateCompressor.setMode(KisSignalCompressor::FIRST_ACTIVE);
}
}
void KisToolLine::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if(mode() == KisTool::PAINT_MODE) {
paintLine(gc,QRect());
}
KisToolPaint::paint(gc,converter);
}
void KisToolLine::beginPrimaryAction(KoPointerEvent *event)
{
NodePaintAbility nodeAbility = nodePaintAbility();
if (nodeAbility == NONE || !nodeEditable()) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
// Always show guideline on vector layers
m_showGuideline = m_chkShowGuideline->isChecked() || nodeAbility != PAINT;
updatePreviewTimer(m_showGuideline);
m_helper->setEnabled(nodeAbility == PAINT);
m_helper->setUseSensors(m_chkUseSensors->isChecked());
m_helper->start(event, canvas()->resourceManager());
m_startPoint = convertToPixelCoordAndSnap(event);
m_endPoint = m_startPoint;
m_lastUpdatedPoint = m_startPoint;
m_strokeIsRunning = true;
}
void KisToolLine::updateStroke()
{
if (!m_strokeIsRunning) return;
m_helper->repaintLine(canvas()->resourceManager(),
image(),
currentNode(),
image().data());
}
void KisToolLine::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
if (!m_strokeIsRunning) return;
// First ensure the old guideline is deleted
updateGuideline();
QPointF pos = convertToPixelCoordAndSnap(event);
if (event->modifiers() == Qt::AltModifier) {
QPointF trans = pos - m_endPoint;
m_helper->translatePoints(trans);
m_startPoint += trans;
m_endPoint += trans;
} else if (event->modifiers() == Qt::ShiftModifier) {
pos = straightLine(pos);
m_helper->addPoint(event, pos);
} else {
m_helper->addPoint(event, pos);
}
m_endPoint = pos;
// Draw preview if requested
if (m_chkShowPreview->isChecked()) {
// If the cursor has moved a significant amount, immediately clear the
// current preview and redraw. Otherwise, do slow redraws periodically.
auto updateDistance = (pixelToView(m_lastUpdatedPoint) - pixelToView(pos)).manhattanLength();
if (updateDistance > 10) {
m_helper->clearPaint();
m_longStrokeUpdateCompressor.stop();
m_strokeUpdateCompressor.start();
m_lastUpdatedPoint = pos;
} else if (updateDistance > 1) {
m_longStrokeUpdateCompressor.start();
}
}
updateGuideline();
KisToolPaint::requestUpdateOutline(event->point, event);
}
void KisToolLine::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
setMode(KisTool::HOVER_MODE);
updateGuideline();
endStroke();
}
void KisToolLine::endStroke()
{
NodePaintAbility nodeAbility = nodePaintAbility();
if (!m_strokeIsRunning || m_startPoint == m_endPoint || nodeAbility == NONE) {
return;
}
if (nodeAbility == PAINT) {
updateStroke();
m_helper->end();
}
else {
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
QTransform resolutionMatrix;
resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
path->moveTo(resolutionMatrix.map(m_startPoint));
path->lineTo(resolutionMatrix.map(m_endPoint));
path->normalize();
- KoShapeStroke* border = new KoShapeStroke(1.0, currentFgColor().toQColor());
+ KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor()));
path->setStroke(border);
KUndo2Command * cmd = canvas()->shapeController()->addShape(path);
canvas()->addCommand(cmd);
}
m_strokeIsRunning = false;
m_endPoint = m_startPoint;
}
void KisToolLine::cancelStroke()
{
if (!m_strokeIsRunning) return;
if (m_startPoint == m_endPoint) return;
/**
* The actual stroke is run by the timer so it is a legal
* situation when m_strokeIsRunning is true, but the actual redraw
* stroke is not running.
*/
if (m_helper->isRunning()) {
m_helper->cancel();
}
m_strokeIsRunning = false;
m_endPoint = m_startPoint;
}
QPointF KisToolLine::straightLine(QPointF point)
{
const QPointF lineVector = point - m_startPoint;
qreal lineAngle = std::atan2(lineVector.y(), lineVector.x());
if (lineAngle < 0) {
lineAngle += 2 * M_PI;
}
const qreal ANGLE_BETWEEN_CONSTRAINED_LINES = (2 * M_PI) / 24;
const quint32 constrainedLineIndex = static_cast<quint32>((lineAngle / ANGLE_BETWEEN_CONSTRAINED_LINES) + 0.5);
const qreal constrainedLineAngle = constrainedLineIndex * ANGLE_BETWEEN_CONSTRAINED_LINES;
const qreal lineLength = std::sqrt((lineVector.x() * lineVector.x()) + (lineVector.y() * lineVector.y()));
const QPointF constrainedLineVector(lineLength * std::cos(constrainedLineAngle), lineLength * std::sin(constrainedLineAngle));
const QPointF result = m_startPoint + constrainedLineVector;
return result;
}
void KisToolLine::updateGuideline()
{
if (canvas()) {
QRectF bound(m_startPoint, m_endPoint);
canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3)));
}
}
void KisToolLine::paintLine(QPainter& gc, const QRect&)
{
QPointF viewStartPos = pixelToView(m_startPoint);
QPointF viewStartEnd = pixelToView(m_endPoint);
if (m_showGuideline && canvas()) {
QPainterPath path;
path.moveTo(viewStartPos);
path.lineTo(viewStartEnd);
paintToolOutline(&gc, path);
}
}
QString KisToolLine::quickHelp() const
{
return i18n("Alt+Drag will move the origin of the currently displayed line around, Shift+Drag will force you to draw straight lines");
}
diff --git a/plugins/tools/basictools/kis_tool_line.h b/plugins/tools/basictools/kis_tool_line.h
index b99bf260b3..a4c8f4ae06 100644
--- a/plugins/tools/basictools/kis_tool_line.h
+++ b/plugins/tools/basictools/kis_tool_line.h
@@ -1,137 +1,137 @@
/*
* kis_tool_line.h - part of Krayon
*
* Copyright (c) 2000 John Califf <jcaliff@comuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_LINE_H_
#define KIS_TOOL_LINE_H_
-#include "kis_tool_paint.h"
+#include "kis_tool_shape.h"
#include <kconfig.h>
#include <kconfiggroup.h>
#include <QScopedPointer>
#include "kis_global.h"
#include "kis_types.h"
#include "KoToolFactoryBase.h"
#include "flake/kis_node_shape.h"
#include "kis_signal_compressor.h"
#include <kis_icon.h>
#include <KoIcon.h>
class QPoint;
class KoCanvasBase;
class QCheckBox;
class KisPaintingInformationBuilder;
class KisToolLineHelper;
-class KisToolLine : public KisToolPaint
+class KisToolLine : public KisToolShape
{
Q_OBJECT
public:
KisToolLine(KoCanvasBase * canvas);
virtual ~KisToolLine();
void requestStrokeCancellation();
void requestStrokeEnd();
void beginPrimaryAction(KoPointerEvent *event);
void continuePrimaryAction(KoPointerEvent *event);
void endPrimaryAction(KoPointerEvent *event);
void activate(ToolActivation activation, const QSet<KoShape*> &shapes);
void deactivate();
virtual int flags() const;
virtual void paint(QPainter& gc, const KoViewConverter &converter);
virtual QString quickHelp() const;
protected Q_SLOTS:
virtual void resetCursorStyle();
private Q_SLOTS:
void updateStroke();
void setUseSensors(bool value);
void setShowPreview(bool value);
void setShowGuideline(bool value);
private:
void paintLine(QPainter& gc, const QRect& rc);
QPointF straightLine(QPointF point);
void updateGuideline();
void updatePreviewTimer(bool showGuide);
virtual QWidget* createOptionWidget();
void endStroke();
void cancelStroke();
private:
bool m_showGuideline;
QPointF m_startPoint;
QPointF m_endPoint;
QPointF m_lastUpdatedPoint;
bool m_strokeIsRunning;
QCheckBox *m_chkUseSensors;
QCheckBox *m_chkShowPreview;
QCheckBox *m_chkShowGuideline;
QScopedPointer<KisPaintingInformationBuilder> m_infoBuilder;
QScopedPointer<KisToolLineHelper> m_helper;
KisSignalCompressor m_strokeUpdateCompressor;
KisSignalCompressor m_longStrokeUpdateCompressor;
KConfigGroup configGroup;
};
class KisToolLineFactory : public KoToolFactoryBase
{
public:
KisToolLineFactory()
: KoToolFactoryBase("KritaShape/KisToolLine") {
setToolTip(i18n("Line Tool"));
// Temporarily
setSection(TOOL_TYPE_SHAPE);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
setPriority(1);
setIconName(koIconNameCStr("krita_tool_line"));
}
virtual ~KisToolLineFactory() {}
virtual KoToolBase * createTool(KoCanvasBase *canvas) {
return new KisToolLine(canvas);
}
};
#endif //KIS_TOOL_LINE_H_
diff --git a/plugins/tools/basictools/kis_tool_pencil.cc b/plugins/tools/basictools/kis_tool_pencil.cc
index 7b7516af63..92f49472fa 100644
--- a/plugins/tools/basictools/kis_tool_pencil.cc
+++ b/plugins/tools/basictools/kis_tool_pencil.cc
@@ -1,68 +1,93 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_pencil.h"
#include <KoPathShape.h>
#include <KoCanvasBase.h>
#include <KoPointerEvent.h>
#include <KoShapeStroke.h>
#include <kis_cursor.h>
KisToolPencil::KisToolPencil(KoCanvasBase * canvas)
: DelegatedPencilTool(canvas, Qt::ArrowCursor,
new __KisToolPencilLocalTool(canvas, this))
{
}
void KisToolPencil::resetCursorStyle()
{
DelegatedPencilTool::resetCursorStyle();
overrideCursorIfNotEditable();
}
+void KisToolPencil::updatePencilCursor(bool value)
+{
+ setCursor(value ? Qt::ArrowCursor : Qt::ForbiddenCursor);
+ resetCursorStyle();
+}
+
void KisToolPencil::mousePressEvent(KoPointerEvent *event)
{
if (!nodeEditable()) return;
DelegatedPencilTool::mousePressEvent(event);
}
+QList<QPointer<QWidget> > KisToolPencil::createOptionWidgets()
+{
+ QList<QPointer<QWidget> > widgetsList =
+ DelegatedPencilTool::createOptionWidgets();
+
+ QList<QPointer<QWidget> > filteredWidgets;
+ Q_FOREACH (QWidget* widget, widgetsList) {
+ if (widget->objectName() != "Stroke widget") {
+ filteredWidgets.push_back(widget);
+ }
+ }
+ return filteredWidgets;
+}
__KisToolPencilLocalTool::__KisToolPencilLocalTool(KoCanvasBase * canvas, KisToolPencil* parentTool)
: KoPencilTool(canvas), m_parentTool(parentTool) {}
void __KisToolPencilLocalTool::paintPath(KoPathShape &pathShape, QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(converter);
QTransform matrix;
matrix.scale(m_parentTool->image()->xRes(), m_parentTool->image()->yRes());
matrix.translate(pathShape.position().x(), pathShape.position().y());
m_parentTool->paintToolOutline(&painter, m_parentTool->pixelToView(matrix.map(pathShape.outline())));
}
void __KisToolPencilLocalTool::addPathShape(KoPathShape* pathShape, bool closePath)
{
if (closePath) {
pathShape->close();
pathShape->normalize();
}
m_parentTool->addPathShape(pathShape, kundo2_i18n("Draw Freehand Path"));
}
+
+void __KisToolPencilLocalTool::slotUpdatePencilCursor()
+{
+ KoShapeStrokeSP stroke = this->createStroke();
+ m_parentTool->updatePencilCursor(stroke && stroke->isVisible());
+}
diff --git a/plugins/tools/basictools/kis_tool_pencil.h b/plugins/tools/basictools/kis_tool_pencil.h
index 3b3aed4a42..9dffcfe30f 100644
--- a/plugins/tools/basictools/kis_tool_pencil.h
+++ b/plugins/tools/basictools/kis_tool_pencil.h
@@ -1,88 +1,96 @@
/*
* Copyright (c) 2012 Sven Langkamp <sven.langkamp@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_PENCIL_H_
#define KIS_TOOL_PENCIL_H_
#include <KoPencilTool.h>
#include <KoToolFactoryBase.h>
#include "flake/kis_node_shape.h"
#include "kis_tool_shape.h"
#include "kis_delegated_tool.h"
#include <kis_icon.h>
class KoCanvasBase;
class KisToolPencil;
class __KisToolPencilLocalTool : public KoPencilTool {
public:
__KisToolPencilLocalTool(KoCanvasBase * canvas, KisToolPencil* parentTool);
virtual void paintPath(KoPathShape &path, QPainter &painter, const KoViewConverter &converter);
virtual void addPathShape(KoPathShape* pathShape, bool closePath);
using KoPencilTool::createOptionWidgets;
+protected:
+ void slotUpdatePencilCursor() override;
+
private:
KisToolPencil* const m_parentTool;
};
typedef KisDelegatedTool<KisToolShape,
- __KisToolPencilLocalTool,
- DeselectShapesActivationPolicy> DelegatedPencilTool;
+__KisToolPencilLocalTool,
+DeselectShapesActivationPolicy> DelegatedPencilTool;
class KisToolPencil : public DelegatedPencilTool
{
Q_OBJECT
public:
KisToolPencil(KoCanvasBase * canvas);
void mousePressEvent(KoPointerEvent *event);
+ virtual QList<QPointer<QWidget> > createOptionWidgets();
+
protected Q_SLOTS:
- virtual void resetCursorStyle();
+ void resetCursorStyle() override;
+
+private:
+ void updatePencilCursor(bool value);
private:
friend class __KisToolPencilLocalTool;
};
class KisToolPencilFactory : public KoToolFactoryBase
{
public:
KisToolPencilFactory()
- : KoToolFactoryBase("KisToolPencil") {
+ : KoToolFactoryBase("KisToolPencil") {
setToolTip(i18n("Freehand Path Tool"));
setSection(TOOL_TYPE_SHAPE);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
setIconName(koIconNameCStr("krita_tool_freehandvector"));
setPriority(9);
}
virtual ~KisToolPencilFactory() {}
virtual KoToolBase * createTool(KoCanvasBase *canvas) {
return new KisToolPencil(canvas);
}
};
#endif // KIS_TOOL_PENCIL_H_
diff --git a/plugins/tools/basictools/kis_tool_rectangle.cc b/plugins/tools/basictools/kis_tool_rectangle.cc
index 2e2d0ebdd4..40ebd89028 100644
--- a/plugins/tools/basictools/kis_tool_rectangle.cc
+++ b/plugins/tools/basictools/kis_tool_rectangle.cc
@@ -1,91 +1,91 @@
/*
* kis_tool_rectangle.cc - part of Krita
*
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2004 Clarence Dang <dang@k.org>
* Copyright (c) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_rectangle.h"
#include <kis_debug.h>
#include <brushengine/kis_paintop_registry.h>
#include "KoCanvasBase.h"
#include "kis_shape_tool_helper.h"
#include "kis_figure_painting_tool_helper.h"
#include "kis_system_locker.h"
#include <recorder/kis_action_recorder.h>
#include <recorder/kis_recorded_shape_paint_action.h>
#include <recorder/kis_node_query_path.h>
#include <KoCanvasController.h>
#include <KoShapeStroke.h>
KisToolRectangle::KisToolRectangle(KoCanvasBase * canvas)
: KisToolRectangleBase(canvas, KisToolRectangleBase::PAINT, KisCursor::load("tool_rectangle_cursor.png", 6, 6))
{
setSupportOutline(true);
setObjectName("tool_rectangle");
}
KisToolRectangle::~KisToolRectangle()
{
}
void KisToolRectangle::resetCursorStyle()
{
KisToolRectangleBase::resetCursorStyle();
overrideCursorIfNotEditable();
}
void KisToolRectangle::finishRect(const QRectF &rect)
{
if (rect.isNull())
return;
if (image()) {
KisRecordedShapePaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset(), KisRecordedShapePaintAction::Rectangle, rect);
setupPaintAction(&linePaintAction);
image()->actionRecorder()->addAction(linePaintAction);
}
if (!currentNode()->inherits("KisShapeLayer")) {
KisSystemLocker locker(currentNode());
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Rectangle"),
image(),
currentNode(),
canvas()->resourceManager(),
strokeStyle(),
fillStyle());
helper.paintRect(rect);
} else {
QRectF r = convertToPt(rect);
KoShape* shape = KisShapeToolHelper::createRectangleShape(r);
- KoShapeStroke* border = new KoShapeStroke(1.0, currentFgColor().toQColor());
+ KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor()));
shape->setStroke(border);
addShape(shape);
}
notifyModified();
}
diff --git a/plugins/tools/defaulttool/CMakeLists.txt b/plugins/tools/defaulttool/CMakeLists.txt
index 4417ac60bd..a836be09ba 100644
--- a/plugins/tools/defaulttool/CMakeLists.txt
+++ b/plugins/tools/defaulttool/CMakeLists.txt
@@ -1,37 +1,36 @@
project( defaulttools )
set ( defaulttools_SRCS
Plugin.cpp
defaulttool/DefaultTool.cpp
defaulttool/DefaultToolFactory.cpp
- defaulttool/DefaultToolWidget.cpp
- defaulttool/DefaultToolArrangeWidget.cpp
- defaulttool/DefaultToolTransformWidget.cpp
+ defaulttool/DefaultToolTabbedWidget.cpp
+ defaulttool/DefaultToolGeometryWidget.cpp
defaulttool/ShapeMoveStrategy.cpp
defaulttool/ShapeResizeStrategy.cpp
defaulttool/ShapeRotateStrategy.cpp
defaulttool/ShapeShearStrategy.cpp
+ defaulttool/ShapeGradientEditStrategy.cpp
defaulttool/SelectionDecorator.cpp
- defaulttool/SelectionTransformCommand.cpp
+
+ defaulttool/KoShapeGradientHandles.cpp
connectionTool/ConnectionTool.cpp
connectionTool/ConnectionToolFactory.cpp
connectionTool/AddConnectionPointCommand.cpp
connectionTool/RemoveConnectionPointCommand.cpp
connectionTool/ChangeConnectionPointCommand.cpp
connectionTool/MoveConnectionPointStrategy.cpp
connectionTool/ConnectionPointWidget.cpp
)
ki18n_wrap_ui(defaulttools_SRCS
- defaulttool/DefaultToolWidget.ui
- defaulttool/DefaultToolArrangeWidget.ui
- defaulttool/DefaultToolTransformWidget.ui
+ defaulttool/DefaultToolGeometryWidget.ui
connectionTool/ConnectionPointWidget.ui
)
qt5_add_resources(defaulttools_SRCS defaulttools.qrc)
add_library(krita_flaketools MODULE ${defaulttools_SRCS})
target_link_libraries(krita_flaketools kritaflake kritawidgets kritaui)
install(TARGETS krita_flaketools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
index 5260c43996..fa347dc9a3 100644
--- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
+++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.cpp
@@ -1,979 +1,987 @@
/* 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 "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);
// 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);
+ 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) {
- painter.save();
- painter.setPen(Qt::blue);
- painter.setBrush(i == m_activeHandle ? Qt::red : Qt::white);
- painter.setTransform(connectionShape->absoluteTransformation(&converter) * painter.transform());
- connectionShape->paintHandle(painter, converter, i, radius);
- painter.restore();
+ KisHandlePainterHelper helper = KoShape::createHandlePainterHelper(&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(true) && !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()))) {
+ 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);
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, const QSet<KoShape *> &)
+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()) {
qSort(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()) {
qSort(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(0);
+
+ 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"));
- strokeWidget->setCanvas(canvas());
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/connectionTool/ConnectionTool.h b/plugins/tools/defaulttool/connectionTool/ConnectionTool.h
index 29ae08fe5d..2638700131 100644
--- a/plugins/tools/defaulttool/connectionTool/ConnectionTool.h
+++ b/plugins/tools/defaulttool/connectionTool/ConnectionTool.h
@@ -1,171 +1,171 @@
/* This file is part of the KDE project
*
* 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.
*/
#ifndef KO_CONNECTION_TOOL_H
#define KO_CONNECTION_TOOL_H
#define ConnectionTool_ID "ConnectionTool"
#include <KoToolBase.h>
#include <KoCanvasBase.h>
#include <KoSnapGuide.h>
#include <KoConnectionShape.h>
#include <QCursor>
class QAction;
class QActionGroup;
class KoShapeConfigWidgetBase;
class KoInteractionStrategy;
class ConnectionTool : public KoToolBase
{
Q_OBJECT
public:
/**
* @brief Constructor
*/
explicit ConnectionTool(KoCanvasBase *canvas);
/**
* @brief Destructor
*/
~ConnectionTool();
/// reimplemented from superclass
virtual void paint(QPainter &painter, const KoViewConverter &converter);
/// reimplemented from superclass
virtual void repaintDecorations();
/// reimplemented from superclass
virtual void mousePressEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseMoveEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseReleaseEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void mouseDoubleClickEvent(KoPointerEvent *event);
/// reimplemented from superclass
virtual void keyPressEvent(QKeyEvent *event);
/// reimplemented from superclass
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape *> &shapes);
/// reimplemented from superclass
virtual void deactivate();
/// reimplemented from superclass
virtual void deleteSelection();
Q_SIGNALS:
void connectionPointEnabled(bool enabled);
void sendConnectionType(int type);
void sendConnectionPointEditState(bool enabled);
public Q_SLOTS:
void toggleConnectionPointEditMode(int state);
private Q_SLOTS:
void horizontalAlignChanged();
void verticalAlignChanged();
void relativeAlignChanged();
void escapeDirectionChanged();
void connectionChanged();
void getConnectionType(int type);
private:
/// reimplemented from superclass
virtual QList<QPointer<QWidget> > createOptionWidgets();
/**
* @brief Return the square of the absolute distance between p1 and p2
*
* @param p1 The first point
* @param p2 The second point
* @return The float which is the square of the distance
*/
qreal squareDistance(const QPointF &p1, const QPointF &p2) const;
/// Returns nearest connection handle or nearest connection point id of shape
int handleAtPoint(KoShape *shape, const QPointF &mousePoint) const;
enum EditMode {
Idle, ///< in idle mode we can only start a connector creation, manipulation to existing connectors and connection points not allowed
CreateConnection, ///< we are creating a new connection
EditConnection, ///< we are editing a connection
EditConnectionPoint ///< we are editing connection points
};
/// Sets the edit mode, current shape and active handle
void setEditMode(EditMode mode, KoShape *currentShape, int handle);
/// Resets the current edit mode to Idle, standard connector type
void resetEditMode();
/// Returns the nearest connection shape within handle grab sensitiviy distance
KoConnectionShape *nearestConnectionShape(const QList<KoShape *> &shapes, const QPointF &mousePos) const;
/// Updates status text depending on edit mode
void updateStatusText();
/// Updates current shape and edit mode dependent on position
KoShape *findShapeAtPosition(const QPointF &position) const;
/// Updates current shape and edit mode dependent on position excluding connection shapes
KoShape *findNonConnectionShapeAtPosition(const QPointF &position) const;
/// Updates actions
void updateActions();
/// Updates currently selected connection point
void updateConnectionPoint();
EditMode m_editMode; ///< the current edit mode
KoConnectionShape::Type m_connectionType;
KoShape *m_currentShape; ///< the current shape we are working on
int m_activeHandle; ///< the currently active connection point/connection handle
KoInteractionStrategy *m_currentStrategy; ///< the current editing strategy
KoSnapGuide::Strategies m_oldSnapStrategies; ///< the previously enables snap strategies
bool m_resetPaint; ///< whether in initial paint mode
QCursor m_connectCursor;
QActionGroup *m_alignVertical;
QActionGroup *m_alignHorizontal;
QActionGroup *m_alignRelative;
QActionGroup *m_escapeDirections;
QAction *m_editConnectionPoint;
QAction *m_alignPercent;
QAction *m_alignLeft;
QAction *m_alignCenterH;
QAction *m_alignRight;
QAction *m_alignTop;
QAction *m_alignCenterV;
QAction *m_alignBottom;
QAction *m_escapeAll;
QAction *m_escapeHorizontal;
QAction *m_escapeVertical;
QAction *m_escapeUp;
QAction *m_escapeLeft;
QAction *m_escapeDown;
QAction *m_escapeRight;
QList<KoShapeConfigWidgetBase *> m_connectionShapeWidgets;
};
#endif // KO_CONNECTION_TOOL_H
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
index 02e5e2f2cf..8ab9b97f32 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.cpp
@@ -1,1242 +1,1349 @@
/* 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 "DefaultToolWidget.h"
-#include "DefaultToolArrangeWidget.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 <KoShapePaste.h>
#include <KoShapeOdfSaveHelper.h>
#include <KoDrag.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceManager.h>
#include <KoShapeRubberSelectStrategy.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeDeleteCommand.h>
#include <commands/KoShapeCreateCommand.h>
#include <commands/KoShapeGroupCommand.h>
#include <commands/KoShapeUngroupCommand.h>
+#include <commands/KoShapeDistributeCommand.h>
#include <KoSnapGuide.h>
#include <KoStrokeConfigWidget.h>
-#include <KoFillConfigWidget.h>
-#include <KoShadowConfigWidget.h>
#include "kis_action_registry.h"
+#include <KoInteractionStrategyFactory.h>
+
+#include "kis_document_aware_spin_box_unit_manager.h"
#include <KoIcon.h>
#include <QPointer>
#include <QAction>
#include <QKeyEvent>
-#include <QClipboard>
+#include <QSignalMapper>
#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";
+}
+
+QPolygonF selectionPolygon(KoSelection *selection)
+{
+ QPolygonF result;
+
+ QList<KoShape*> selectedShapes = selection->selectedShapes();
+
+ if (!selectedShapes.size()) {
+ return result;
+ }
+
+ if (selectedShapes.size() > 1) {
+ QTransform matrix = selection->absoluteTransformation(0);
+ result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selection->size())));
+ } else {
+ KoShape *selectedShape = selectedShapes.first();
+ QTransform matrix = selectedShape->absoluteTransformation(0);
+ result = matrix.map(QPolygonF(QRectF(QPointF(0, 0), selectedShape->size())));
+ }
+
+ return result;
+}
+
+
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) {
+ 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) {
+ KoShapeRubberSelectStrategy::paint(painter, converter);
+ }
+};
+#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() {
+ if (m_currentHandle.type != KoShapeGradientHandles::Handle::None) {
+ q->useCursor(Qt::OpenHandCursor);
+ }
+
+ return m_currentHandle.type != KoShapeGradientHandles::Handle::None;
+ }
+
+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)
: KoInteractionTool(canvas)
, m_lastHandle(KoFlake::NoHandle)
- , m_hotPosition(KoFlake::TopLeftCorner)
+ , m_hotPosition(KoFlake::TopLeft)
, m_mouseWasInsideHandles(false)
- , m_moveCommand(0)
, m_selectionHandler(new SelectionHandler(this))
, m_customEventStrategy(0)
+ , 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;
- KoShapeManager *manager = canvas->shapeManager();
- connect(manager, SIGNAL(selectionChanged()), this, SLOT(updateActions()));
+ 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(QSignalMapper *mapper, const QString &actionId, int commandType)
+{
+ KisActionRegistry *actionRegistry = KisActionRegistry::instance();
+
+ QAction *action = actionRegistry->makeQAction(actionId, this);
+ addAction(actionId, action);
+ connect(action, SIGNAL(triggered()), mapper, SLOT(map()));
+ mapper->setMapping(action, commandType);
+}
+
void DefaultTool::setupActions()
{
KisActionRegistry *actionRegistry = KisActionRegistry::instance();
QAction *actionBringToFront = actionRegistry->makeQAction("object_order_front", this);
addAction("object_order_front", actionBringToFront);
connect(actionBringToFront, SIGNAL(triggered()), this, SLOT(selectionBringToFront()));
QAction *actionRaise = actionRegistry->makeQAction("object_order_raise", this);
addAction("object_order_raise", actionRaise);
connect(actionRaise, SIGNAL(triggered()), this, SLOT(selectionMoveUp()));
QAction *actionLower = actionRegistry->makeQAction("object_order_lower", this);
addAction("object_order_lower", actionLower);
connect(actionLower, SIGNAL(triggered()), this, SLOT(selectionMoveDown()));
QAction *actionSendToBack = actionRegistry->makeQAction("object_order_back", this);
addAction("object_order_back", actionSendToBack);
connect(actionSendToBack, SIGNAL(triggered()), this, SLOT(selectionSendToBack()));
- QAction *actionAlignLeft = actionRegistry->makeQAction("object_align_horizontal_left", this);
- addAction("object_align_horizontal_left", actionAlignLeft);
- connect(actionAlignLeft, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalLeft()));
- QAction *actionAlignCenter = actionRegistry->makeQAction("object_align_horizontal_center", this);
- addAction("object_align_horizontal_center", actionAlignCenter);
- connect(actionAlignCenter, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalCenter()));
+ QSignalMapper *alignSignalsMapper = new QSignalMapper(this);
+ connect(alignSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionAlign(int)));
- QAction *actionAlignRight = actionRegistry->makeQAction("object_align_horizontal_right", this);
- addAction("object_align_horizontal_right", actionAlignRight);
- connect(actionAlignRight, SIGNAL(triggered()), this, SLOT(selectionAlignHorizontalRight()));
+ addMappedAction(alignSignalsMapper, "object_align_horizontal_left", KoShapeAlignCommand::HorizontalLeftAlignment);
+ addMappedAction(alignSignalsMapper, "object_align_horizontal_center", KoShapeAlignCommand::HorizontalCenterAlignment);
+ addMappedAction(alignSignalsMapper, "object_align_horizontal_right", KoShapeAlignCommand::HorizontalRightAlignment);
+ addMappedAction(alignSignalsMapper, "object_align_vertical_top", KoShapeAlignCommand::VerticalTopAlignment);
+ addMappedAction(alignSignalsMapper, "object_align_vertical_center", KoShapeAlignCommand::VerticalCenterAlignment);
+ addMappedAction(alignSignalsMapper, "object_align_vertical_bottom", KoShapeAlignCommand::VerticalBottomAlignment);
- QAction *actionAlignTop = actionRegistry->makeQAction("object_align_vertical_top", this);
- addAction("object_align_vertical_top", actionAlignTop);
- connect(actionAlignTop, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalTop()));
+ QSignalMapper *distributeSignalsMapper = new QSignalMapper(this);
+ connect(distributeSignalsMapper, SIGNAL(mapped(int)), SLOT(selectionDistribute(int)));
- QAction *actionAlignMiddle = actionRegistry->makeQAction("object_align_vertical_center", this);
- addAction("object_align_vertical_center", actionAlignMiddle);
- connect(actionAlignMiddle, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalCenter()));
+ addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_left", KoShapeDistributeCommand::HorizontalLeftDistribution);
+ addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_center", KoShapeDistributeCommand::HorizontalCenterDistribution);
+ addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_right", KoShapeDistributeCommand::HorizontalRightDistribution);
+ addMappedAction(distributeSignalsMapper, "object_distribute_horizontal_gaps", KoShapeDistributeCommand::HorizontalGapsDistribution);
- QAction *actionAlignBottom = actionRegistry->makeQAction("object_align_vertical_bottom", this);
- addAction("object_align_vertical_bottom", actionAlignBottom);
- connect(actionAlignBottom, SIGNAL(triggered()), this, SLOT(selectionAlignVerticalBottom()));
+ addMappedAction(distributeSignalsMapper, "object_distribute_vertical_top", KoShapeDistributeCommand::VerticalTopDistribution);
+ addMappedAction(distributeSignalsMapper, "object_distribute_vertical_center", KoShapeDistributeCommand::VerticalCenterDistribution);
+ addMappedAction(distributeSignalsMapper, "object_distribute_vertical_bottom", KoShapeDistributeCommand::VerticalBottomDistribution);
+ addMappedAction(distributeSignalsMapper, "object_distribute_vertical_gaps", KoShapeDistributeCommand::VerticalGapsDistribution);
QAction *actionGroupBottom = actionRegistry->makeQAction("object_group", this);
addAction("object_group", actionGroupBottom);
connect(actionGroupBottom, SIGNAL(triggered()), this, SLOT(selectionGroup()));
QAction *actionUngroupBottom = actionRegistry->makeQAction("object_ungroup", this);
addAction("object_ungroup", actionUngroupBottom);
connect(actionUngroupBottom, SIGNAL(triggered()), this, SLOT(selectionUngroup()));
+
+ 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::TopRightCorner)
- - koSelection()->absolutePosition(KoFlake::TopLeftCorner);
+ direction = koSelection()->absolutePosition(KoFlake::TopRight)
+ - koSelection()->absolutePosition(KoFlake::TopLeft);
} else {
- QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner);
- handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::TopRightCorner) - handlePosition);
+ 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::TopRightCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopRightCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized()).toPointF();
+ 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::BottomRightCorner)
- - koSelection()->absolutePosition(KoFlake::TopRightCorner);
+ direction = koSelection()->absolutePosition(KoFlake::BottomRight)
+ - koSelection()->absolutePosition(KoFlake::TopRight);
} else {
- QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopRightCorner);
- handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition);
+ 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::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomRightCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized()).toPointF();
+ 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::BottomLeftCorner)
- - koSelection()->absolutePosition(KoFlake::BottomRightCorner);
+ direction = koSelection()->absolutePosition(KoFlake::BottomLeft)
+ - koSelection()->absolutePosition(KoFlake::BottomRight);
} else {
- QPointF handlePosition = koSelection()->absolutePosition(KoFlake::BottomLeftCorner);
- handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomRightCorner) - handlePosition);
+ 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::BottomLeftCorner) - selectionCenter;
- direction = (QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - koSelection()->absolutePosition(KoFlake::TopLeftCorner)).normalized()).toPointF();
+ 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::TopLeftCorner)
- - koSelection()->absolutePosition(KoFlake::BottomLeftCorner);
+ direction = koSelection()->absolutePosition(KoFlake::TopLeft)
+ - koSelection()->absolutePosition(KoFlake::BottomLeft);
} else {
- QPointF handlePosition = koSelection()->absolutePosition(KoFlake::TopLeftCorner);
- handlePosition += 0.5 * (koSelection()->absolutePosition(KoFlake::BottomLeftCorner) - handlePosition);
+ 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::TopLeftCorner) - selectionCenter;
- direction = (QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::TopRightCorner)).normalized() + QVector2D(koSelection()->absolutePosition(KoFlake::TopLeftCorner) - koSelection()->absolutePosition(KoFlake::BottomLeftCorner)).normalized()).toPointF();
+ 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;
case KoFlake::NoHandle:
break;
}
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 = editableShapesCount(koSelection()->selectedShapes(KoFlake::StrippedSelection));
+
+ if (koSelection()->count() > 0) { // has a selection
+ bool editable = !koSelection()->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)
{
+ SelectionDecorator decorator(canvas()->resourceManager());
+ decorator.setSelection(koSelection());
+ decorator.setHandleRadius(handleRadius());
+ decorator.setShowFillGradientHandles(hasInteractioFactory(EditFillGradientFactoryId));
+ decorator.setShowStrokeFillGradientHandles(hasInteractioFactory(EditStrokeGradientFactoryId));
+ decorator.paint(painter, converter);
+
KoInteractionTool::paint(painter, converter);
- if (currentStrategy() == 0 && koSelection() && koSelection()->count() > 0) {
- SelectionDecorator decorator(m_mouseWasInsideHandles ? m_lastHandle : KoFlake::NoHandle,
- true, true);
- decorator.setSelection(koSelection());
- decorator.setHandleRadius(handleRadius());
- decorator.setHotPosition(m_hotPosition);
- decorator.paint(painter, converter);
- }
+
painter.save();
KoShape::applyConversion(painter, converter);
canvas()->snapGuide()->paint(painter, converter);
painter.restore();
}
void DefaultTool::mousePressEvent(KoPointerEvent *event)
{
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()
{
- QRectF bound = koSelection()->boundingRect();
+ KoSelection *selection = koSelection();
+ if (!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();
}
void DefaultTool::mouseDoubleClickEvent(KoPointerEvent *event)
{
- QList<KoShape *> shapes;
- Q_FOREACH (KoShape *shape, koSelection()->selectedShapes()) {
- if (shape->boundingRect().contains(event->point) && // first 'cheap' check
- shape->outline().contains(event->point)) { // this is more expensive but weeds out the almost hits
- shapes.append(shape);
- }
- }
- if (shapes.count() == 0) { // nothing in the selection was clicked on.
- KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop);
- if (shape) {
- shapes.append(shape);
- } // there used to be guides... :'''(
- }
+ KoSelection *selection = canvas()->selectedShapesProxy()->selection();
- QList<KoShape *> shapes2;
- foreach (KoShape *shape, shapes) {
- QSet<KoShape *> delegates = shape->toolDelegates();
- if (delegates.isEmpty()) {
- shapes2.append(shape);
- } else {
- foreach (KoShape *delegatedShape, delegates) {
- shapes2.append(delegatedShape);
- }
+ KoShape *shape = canvas()->shapeManager()->shapeAt(event->point, KoFlake::ShapeOnTop);
+ if (shape && !selection->isSelected(shape)) {
+
+ if (!(event->modifiers() & Qt::ShiftModifier)) {
+ selection->deselectAll();
}
+
+ selection->select(shape);
}
- KoToolManager::instance()->switchToolRequested(
- KoToolManager::instance()->preferredToolForSelection(shapes2));
+ 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<QPointF> prevPos;
- QList<QPointF> newPos;
- QList<KoShape *> shapes;
- Q_FOREACH (KoShape *shape, koSelection()->selectedShapes(KoFlake::TopLevelSelection)) {
- if (shape->isGeometryProtected()) {
- continue;
- }
- shapes.append(shape);
- QPointF p = shape->position();
- prevPos.append(p);
- p.setX(p.x() + x);
- p.setY(p.y() + y);
- newPos.append(p);
- }
- if (shapes.count() > 0) {
- // use a timeout to make sure we don't reuse a command possibly deleted by the commandHistory
- if (m_lastUsedMoveCommand.msecsTo(QTime::currentTime()) > 5000) {
- m_moveCommand = 0;
- }
- if (m_moveCommand) { // alter previous instead of creating new one.
- m_moveCommand->setNewPositions(newPos);
- m_moveCommand->redo();
- } else {
- m_moveCommand = new KoShapeMoveCommand(shapes, prevPos, newPos);
- canvas()->addCommand(m_moveCommand);
- }
- m_lastUsedMoveCommand = QTime::currentTime();
- return true;
+ QList<KoShape *> shapes = koSelection()->selectedEditableShapes();
+
+ if (!shapes.isEmpty()) {
+ canvas()->addCommand(new KoShapeMoveCommand(shapes, QPointF(x, y)));
+ result = true;
}
}
- return false;
+
+ 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::customMoveEvent(KoPointerEvent *event)
-{
- if (koSelection() && koSelection()->count() <= 0) {
- event->ignore();
- return;
- }
-
- int move = qMax(qAbs(event->x()), qAbs(event->y()));
- int zoom = qAbs(event->z());
- int rotate = qAbs(event->rotationZ());
- const int threshold = 2;
-
- if (move < threshold && zoom < threshold && rotate < threshold) {
- if (m_customEventStrategy) {
- m_customEventStrategy->finishInteraction(event->modifiers());
- KUndo2Command *command = m_customEventStrategy->createCommand();
- if (command) {
- canvas()->addCommand(command);
- }
- delete m_customEventStrategy;
- m_customEventStrategy = 0;
- repaintDecorations();
- }
- event->accept();
- return;
- }
-
- // check if the z-movement is dominant
- if (zoom > move && zoom > rotate) {
- // zoom
- if (!m_customEventStrategy) {
- m_customEventStrategy = new ShapeResizeStrategy(this, event->point, KoFlake::TopLeftHandle);
- }
- } else if (move > zoom && move > rotate) { // check if x-/y-movement is dominant
- // move
- if (!m_customEventStrategy) {
- m_customEventStrategy = new ShapeMoveStrategy(this, event->point);
- }
- } else if (rotate > zoom && rotate > move) { // rotation is dominant
- // rotate
- if (!m_customEventStrategy) {
- m_customEventStrategy = new ShapeRotateStrategy(this, event->point, event->buttons());
- }
- }
-
- if (m_customEventStrategy) {
- m_customEventStrategy->handleCustomEvent(event);
- }
-
- event->accept();
-}
-
void DefaultTool::repaintDecorations()
{
if (koSelection() && koSelection()->count() > 0) {
canvas()->updateCanvas(handlesSize());
}
}
void DefaultTool::copy() const
{
- QList<KoShape *> shapes = canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection);
- if (!shapes.empty()) {
- KoShapeOdfSaveHelper saveHelper(shapes);
+ // all the selected shapes, not only editable!
+ QList<KoShape *> shapes = canvas()->selectedShapesProxy()->selection()->selectedShapes();
+
+ if (!shapes.isEmpty()) {
KoDrag drag;
- drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper);
+ drag.setSvg(shapes);
drag.addToClipboard();
}
}
void DefaultTool::deleteSelection()
{
QList<KoShape *> shapes;
- foreach (KoShape *s, canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection)) {
+ foreach (KoShape *s, canvas()->selectedShapesProxy()->selection()->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;
}
-QStringList DefaultTool::supportedPasteMimeTypes() const
-{
- QStringList list;
- list << KoOdf::mimeType(KoOdf::Text);
- return list;
-}
-
KoSelection *DefaultTool::koSelection()
{
Q_ASSERT(canvas());
- Q_ASSERT(canvas()->shapeManager());
- return canvas()->shapeManager()->selection();
+ 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
};
- if (koSelection() && koSelection()->count() == 0) {
- return KoFlake::NoHandle;
- }
-
- recalcSelectionBox();
const KoViewConverter *converter = canvas()->viewConverter();
- if (!converter) {
+ KoSelection *selection = koSelection();
+
+ if (!selection->count() || !converter) {
return KoFlake::NoHandle;
}
- if (innerHandleMeaning != 0) {
+ 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];
- QPointF pt = converter->documentToView(point) - converter->documentToView(m_selectionBox[handle]);
+
+ const QPointF handlePoint = converter->documentToView(m_selectionBox[handle]);
+ const qreal distanceSq = kisSquareDistance(viewPoint, handlePoint);
// if just inside the outline
- if (qAbs(pt.x()) < HANDLE_DISTANCE &&
- qAbs(pt.y()) < HANDLE_DISTANCE) {
- if (innerHandleMeaning != 0) {
- if (qAbs(pt.x()) < 4 && qAbs(pt.y()) < 4) {
+ if (distanceSq < HANDLE_DISTANCE_SQ) {
+
+ if (innerHandleMeaning) {
+ if (distanceSq < INNER_HANDLE_DISTANCE_SQ) {
*innerHandleMeaning = true;
}
}
+
return handle;
}
}
return KoFlake::NoHandle;
}
-void DefaultTool::recalcSelectionBox()
+void DefaultTool::recalcSelectionBox(KoSelection *selection)
{
- if (!koSelection()) {
- return;
- }
+ KIS_ASSERT_RECOVER_RETURN(selection->count());
- if (koSelection()->count() == 0) {
- return;
- }
+ QTransform matrix = selection->absoluteTransformation(0);
+ m_selectionOutline = matrix.map(QPolygonF(selection->outlineRect()));
+ m_angle = 0.0;
- if (koSelection()->count() > 1) {
- QTransform matrix = koSelection()->absoluteTransformation(0);
- m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->size())));
- m_angle = 0.0; //koSelection()->rotation();
- } else {
- QTransform matrix = koSelection()->firstSelectedShape()->absoluteTransformation(0);
- m_selectionOutline = matrix.map(QPolygonF(QRectF(QPointF(0, 0), koSelection()->firstSelectedShape()->size())));
- m_angle = 0.0; //koSelection()->firstSelectedShape()->rotation();
- }
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 (koSelection()->count() == 1) {
+ if (selection->count() == 1) {
#if 0 // TODO detect mirroring
KoShape *s = koSelection()->firstSelectedShape();
if (s->scaleX() < 0) { // vertically mirrored: swap left / right
qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::TopRightHandle]);
qSwap(m_selectionBox[KoFlake::LeftMiddleHandle], m_selectionBox[KoFlake::RightMiddleHandle]);
qSwap(m_selectionBox[KoFlake::BottomLeftHandle], m_selectionBox[KoFlake::BottomRightHandle]);
}
if (s->scaleY() < 0) { // vertically mirrored: swap top / bottom
qSwap(m_selectionBox[KoFlake::TopLeftHandle], m_selectionBox[KoFlake::BottomLeftHandle]);
qSwap(m_selectionBox[KoFlake::TopMiddleHandle], m_selectionBox[KoFlake::BottomMiddleHandle]);
qSwap(m_selectionBox[KoFlake::TopRightHandle], m_selectionBox[KoFlake::BottomRightHandle]);
}
#endif
}
}
-void DefaultTool::activate(ToolActivation, const QSet<KoShape *> &)
+void DefaultTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
+ KoToolBase::activate(activation, shapes);
+
m_mouseWasInsideHandles = false;
m_lastHandle = KoFlake::NoHandle;
useCursor(Qt::ArrowCursor);
repaintDecorations();
updateActions();
-}
-
-void DefaultTool::selectionAlignHorizontalLeft()
-{
- selectionAlign(KoShapeAlignCommand::HorizontalLeftAlignment);
-}
-void DefaultTool::selectionAlignHorizontalCenter()
-{
- selectionAlign(KoShapeAlignCommand::HorizontalCenterAlignment);
-}
-
-void DefaultTool::selectionAlignHorizontalRight()
-{
- selectionAlign(KoShapeAlignCommand::HorizontalRightAlignment);
-}
-
-void DefaultTool::selectionAlignVerticalTop()
-{
- selectionAlign(KoShapeAlignCommand::VerticalTopAlignment);
+ if (m_tabbedOptionWidget) {
+ m_tabbedOptionWidget->activate();
+ }
}
-void DefaultTool::selectionAlignVerticalCenter()
+void DefaultTool::deactivate()
{
- selectionAlign(KoShapeAlignCommand::VerticalCenterAlignment);
-}
+ KoToolBase::deactivate();
-void DefaultTool::selectionAlignVerticalBottom()
-{
- selectionAlign(KoShapeAlignCommand::VerticalBottomAlignment);
+ if (m_tabbedOptionWidget) {
+ m_tabbedOptionWidget->deactivate();
+ }
}
void DefaultTool::selectionGroup()
{
KoSelection *selection = koSelection();
- if (!selection) {
- return;
- }
+ if (!selection) return;
- QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
- QList<KoShape *> groupedShapes;
+ QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
+ qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
- // only group shapes with an unselected parent
- foreach (KoShape *shape, selectedShapes) {
- if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) {
- groupedShapes << shape;
- }
- }
KoShapeGroup *group = new KoShapeGroup();
// TODO what if only one shape is left?
KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Group shapes"));
canvas()->shapeController()->addShapeDirect(group, cmd);
- KoShapeGroupCommand::createCommand(group, groupedShapes, cmd);
+ new KoShapeGroupCommand(group, selectedShapes, false, true, true, cmd);
canvas()->addCommand(cmd);
// update selection so we can ungroup immediately again
selection->deselectAll();
selection->select(group);
}
void DefaultTool::selectionUngroup()
{
- KoSelection *selection = canvas()->shapeManager()->selection();
- if (!selection) {
- return;
- }
-
- QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
- QList<KoShape *> containerSet;
+ KoSelection *selection = koSelection();
+ if (!selection) return;
- // only ungroup shape groups with an unselected parent
- foreach (KoShape *shape, selectedShapes) {
- if (!selectedShapes.contains(shape->parent()) && shape->isEditable()) {
- containerSet << shape;
- }
- }
+ QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
+ qSort(selectedShapes.begin(), selectedShapes.end(), KoShape::compareShapeZIndex);
KUndo2Command *cmd = 0;
// add a ungroup command for each found shape container to the macro command
- Q_FOREACH (KoShape *shape, containerSet) {
+ Q_FOREACH (KoShape *shape, selectedShapes) {
KoShapeGroup *group = dynamic_cast<KoShapeGroup *>(shape);
if (group) {
cmd = cmd ? cmd : new KUndo2Command(kundo2_i18n("Ungroup shapes"));
new KoShapeUngroupCommand(group, group->shapes(),
group->parent() ? QList<KoShape *>() : canvas()->shapeManager()->topLevelShapes(),
cmd);
canvas()->shapeController()->removeShape(group, cmd);
}
}
if (cmd) {
canvas()->addCommand(cmd);
}
}
-void DefaultTool::selectionAlign(KoShapeAlignCommand::Align align)
+void DefaultTool::selectionAlign(int _align)
{
- KoSelection *selection = canvas()->shapeManager()->selection();
- if (!selection) {
- return;
- }
+ KoShapeAlignCommand::Align align =
+ static_cast<KoShapeAlignCommand::Align>(_align);
+
+ KoSelection *selection = koSelection();
+ if (!selection) return;
- QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
- if (selectedShapes.count() < 1) {
+ QList<KoShape *> editableShapes = selection->selectedEditableShapes();
+ if (editableShapes.isEmpty()) {
return;
}
- QList<KoShape *> editableShapes = filterEditableShapes(selectedShapes);
-
// 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(KoCanvasResourceManager::PageSize)) {
return;
}
bb = QRectF(QPointF(0, 0), canvas()->resourceManager()->sizeResource(KoCanvasResourceManager::PageSize));
} else {
- Q_FOREACH (KoShape *shape, editableShapes) {
- bb |= shape->boundingRect();
- }
+ 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);
- selection->updateSizeAndPosition();
}
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 = canvas()->shapeManager()->selection();
+ KoSelection *selection = canvas()->selectedShapesProxy()->selection();
if (!selection) {
return;
}
- QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
- if (selectedShapes.count() < 1) {
- return;
- }
-
- QList<KoShape *> editableShapes = filterEditableShapes(selectedShapes);
- if (editableShapes.count() < 1) {
+ QList<KoShape *> selectedShapes = selection->selectedEditableShapes();
+ if (selectedShapes.isEmpty()) {
return;
}
- KUndo2Command *cmd = KoShapeReorderCommand::createCommand(editableShapes, canvas()->shapeManager(), order);
+ KUndo2Command *cmd = KoShapeReorderCommand::createCommand(selectedShapes, canvas()->shapeManager(), order);
if (cmd) {
canvas()->addCommand(cmd);
}
}
QList<QPointer<QWidget> > DefaultTool::createOptionWidgets()
{
QList<QPointer<QWidget> > widgets;
- DefaultToolArrangeWidget *defaultArrange = new DefaultToolArrangeWidget(this);
- defaultArrange->setWindowTitle(i18n("Arrange"));
- widgets.append(defaultArrange);
- DefaultToolWidget *defaultTool = new DefaultToolWidget(this);
- defaultTool->setWindowTitle(i18n("Geometry"));
- widgets.append(defaultTool);
- KoStrokeConfigWidget *strokeWidget = new KoStrokeConfigWidget(0);
- strokeWidget->setWindowTitle(i18n("Line"));
- strokeWidget->setCanvas(canvas());
- widgets.append(strokeWidget);
-
- KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0);
- fillWidget->setWindowTitle(i18n("Fill"));
- fillWidget->setCanvas(canvas());
- widgets.append(fillWidget);
-
- KoShadowConfigWidget *shadowWidget = new KoShadowConfigWidget(0);
- shadowWidget->setWindowTitle(i18n("Shadow"));
- shadowWidget->setCanvas(canvas());
- widgets.append(shadowWidget);
+
+ 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 = static_cast<KoFlake::Position>(res.toInt());
+ m_hotPosition = KoFlake::AnchorPosition(res.toInt());
repaintDecorations();
}
}
KoInteractionStrategy *DefaultTool::createStrategy(KoPointerEvent *event)
{
- // reset the move by keys when a new strategy is created otherwise we might change the
- // command after a new command was added. This happend when you where faster than the timer.
- m_moveCommand = 0;
-
KoShapeManager *shapeManager = canvas()->shapeManager();
- KoSelection *select = shapeManager->selection();
- bool insideSelection;
+ KoSelection *selection = koSelection();
+
+ bool insideSelection = false;
KoFlake::SelectionHandle handle = handleAt(event->point, &insideSelection);
- bool editableShape = editableShapesCount(select->selectedShapes());
+ bool editableShape = !selection->selectedEditableShapes().isEmpty();
- if (event->buttons() & Qt::MidButton) {
+ 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::Position newHotPosition = m_hotPosition;
+ KoFlake::AnchorPosition newHotPosition = m_hotPosition;
switch (handle) {
- case KoFlake::TopLeftHandle:
- newHotPosition = KoFlake::TopLeftCorner;
+ case KoFlake::TopMiddleHandle:
+ newHotPosition = KoFlake::Top;
break;
case KoFlake::TopRightHandle:
- newHotPosition = KoFlake::TopRightCorner;
+ newHotPosition = KoFlake::TopRight;
break;
- case KoFlake::BottomLeftHandle:
- newHotPosition = KoFlake::BottomLeftCorner;
+ case KoFlake::RightMiddleHandle:
+ newHotPosition = KoFlake::Right;
break;
case KoFlake::BottomRightHandle:
- newHotPosition = KoFlake::BottomRightCorner;
+ 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;
- default: {
+ case KoFlake::NoHandle:
+ default:
// check if we had hit the center point
const KoViewConverter *converter = canvas()->viewConverter();
- QPointF pt = converter->documentToView(event->point - select->absolutePosition());
- if (qAbs(pt.x()) < HANDLE_DISTANCE && qAbs(pt.y()) < HANDLE_DISTANCE) {
- newHotPosition = KoFlake::CenteredPosition;
+ 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);
}
- return 0;
}
- bool selectMultiple = event->modifiers() & Qt::ControlModifier;
- bool selectNextInStack = event->modifiers() & Qt::ShiftModifier;
-
- if (editableShape) {
+ if (!avoidSelection && editableShape) {
// manipulation of selected shapes goes first
if (handle != KoFlake::NoHandle) {
- if (event->buttons() == Qt::LeftButton) {
- // resizing or shearing only with left mouse button
- if (insideSelection) {
- return new ShapeResizeStrategy(this, event->point, handle);
- }
- if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
- handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) {
- return new ShapeShearStrategy(this, event->point, handle);
- }
+ // resizing or shearing only with left mouse button
+ if (insideSelection) {
+ return new ShapeResizeStrategy(this, event->point, handle);
}
+
+ if (handle == KoFlake::TopMiddleHandle || handle == KoFlake::RightMiddleHandle ||
+ handle == KoFlake::BottomMiddleHandle || handle == KoFlake::LeftMiddleHandle) {
+
+ return new ShapeShearStrategy(this, event->point, handle);
+ }
+
// rotating is allowed for rigth mouse button too
if (handle == KoFlake::TopLeftHandle || handle == KoFlake::TopRightHandle ||
handle == KoFlake::BottomLeftHandle || handle == KoFlake::BottomRightHandle) {
+
return new ShapeRotateStrategy(this, event->point, event->buttons());
}
}
- if (!(selectMultiple || selectNextInStack) && event->buttons() == Qt::LeftButton) {
- const QPainterPath outlinePath = select->transformation().map(select->outline());
- if (outlinePath.contains(event->point) || outlinePath.intersects(handlePaintRect(event->point))) {
+
+ if (!selectMultiple && !selectNextInStack) {
+ if (insideSelection) {
return new ShapeMoveStrategy(this, event->point);
}
}
}
- if ((event->buttons() & Qt::LeftButton) == 0) {
- return 0; // Nothing to do for middle/right mouse button
- }
-
KoShape *shape = shapeManager->shapeAt(event->point, selectNextInStack ? KoFlake::NextUnselected : KoFlake::ShapeOnTop);
- if (!shape && handle == KoFlake::NoHandle) {
+ if (avoidSelection || (!shape && handle == KoFlake::NoHandle)) {
if (!selectMultiple) {
repaintDecorations();
- select->deselectAll();
+ selection->deselectAll();
}
- return new KoShapeRubberSelectStrategy(this, event->point);
+ return new SelectionInteractionStrategy(this, event->point, false);
}
- if (select->isSelected(shape)) {
+ if (selection->isSelected(shape)) {
if (selectMultiple) {
repaintDecorations();
- select->deselect(shape);
+ selection->deselect(shape);
}
} else if (handle == KoFlake::NoHandle) { // clicked on shape which is not selected
repaintDecorations();
if (!selectMultiple) {
shapeManager->selection()->deselectAll();
}
- select->select(shape, selectNextInStack ? false : true);
+ 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, event->point);
}
return 0;
}
void DefaultTool::updateActions()
{
- KoSelection *selection(koSelection());
- if (!selection) {
- action("object_order_front")->setEnabled(false);
- action("object_order_raise")->setEnabled(false);
- action("object_order_lower")->setEnabled(false);
- action("object_order_back")->setEnabled(false);
- action("object_align_horizontal_left")->setEnabled(false);
- action("object_align_horizontal_center")->setEnabled(false);
- action("object_align_horizontal_right")->setEnabled(false);
- action("object_align_vertical_top")->setEnabled(false);
- action("object_align_vertical_center")->setEnabled(false);
- action("object_align_vertical_bottom")->setEnabled(false);
- action("object_group")->setEnabled(false);
- action("object_ungroup")->setEnabled(false);
- return;
+ QList<KoShape*> editableShapes;
+
+ if (koSelection()) {
+ editableShapes = koSelection()->selectedEditableShapes();
}
- QList<KoShape *> editableShapes = filterEditableShapes(selection->selectedShapes(KoFlake::TopLevelSelection));
- bool enable = editableShapes.count() > 0;
- action("object_order_front")->setEnabled(enable);
- action("object_order_raise")->setEnabled(enable);
- action("object_order_lower")->setEnabled(enable);
- action("object_order_back")->setEnabled(enable);
- enable = (editableShapes.count() > 1) || (enable && canvas()->resourceManager()->hasResource(KoCanvasResourceManager::PageSize));
- action("object_align_horizontal_left")->setEnabled(enable);
- action("object_align_horizontal_center")->setEnabled(enable);
- action("object_align_horizontal_right")->setEnabled(enable);
- action("object_align_vertical_top")->setEnabled(enable);
- action("object_align_vertical_center")->setEnabled(enable);
- action("object_align_vertical_bottom")->setEnabled(enable);
-
- action("object_group")->setEnabled(editableShapes.count() > 1);
- bool groupShape = false;
+ const bool orderingEnabled = !editableShapes.isEmpty();
+
+ action("object_order_front")->setEnabled(orderingEnabled);
+ action("object_order_raise")->setEnabled(orderingEnabled);
+ action("object_order_lower")->setEnabled(orderingEnabled);
+ action("object_order_back")->setEnabled(orderingEnabled);
+
+ const bool alignmentEnabled =
+ editableShapes.size() > 1 ||
+ (!editableShapes.isEmpty() &&
+ canvas()->resourceManager()->hasResource(KoCanvasResourceManager::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);
+
+ action("object_group")->setEnabled(editableShapes.size() > 1);
+
+ 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);
+
+
+ bool hasGroupShape = false;
foreach (KoShape *shape, editableShapes) {
if (dynamic_cast<KoShapeGroup *>(shape)) {
- groupShape = true;
+ hasGroupShape = true;
break;
}
}
- action("object_ungroup")->setEnabled(groupShape);
+ action("object_ungroup")->setEnabled(hasGroupShape);
- emit selectionChanged(selection->count());
+ emit selectionChanged(editableShapes.size());
}
KoToolSelection *DefaultTool::selection()
{
return m_selectionHandler;
}
-QList<KoShape *> DefaultTool::filterEditableShapes(const QList<KoShape *> &shapes)
+QMenu* DefaultTool::popupActionsMenu()
{
- QList<KoShape *> editableShapes;
- Q_FOREACH (KoShape *shape, shapes) {
- if (shape->isEditable()) {
- editableShapes.append(shape);
+ if (m_contextMenu) {
+ m_contextMenu->clear();
+
+ KActionCollection *collection = this->canvas()->canvasController()->actionCollection();
+
+ m_contextMenu->addAction(collection->action("edit_cut"));
+ m_contextMenu->addAction(collection->action("edit_copy"));
+ m_contextMenu->addAction(collection->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 editableShapes;
+ return m_contextMenu.data();
}
-uint DefaultTool::editableShapesCount(const QList<KoShape *> &shapes)
+void DefaultTool::explicitUserStrokeEndRequest()
{
- uint count = 0;
- Q_FOREACH (KoShape *shape, shapes) {
- if (shape->isEditable()) {
- count++;
- }
- }
-
- return count;
+ QList<KoShape *> shapes = koSelection()->selectedEditableShapesAndDelegates();
+ emit activateTemporary(KoToolManager::instance()->preferredToolForSelection(shapes));
}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultTool.h b/plugins/tools/defaulttool/defaulttool/DefaultTool.h
index 35d9a4de51..8311cb28c6 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultTool.h
+++ b/plugins/tools/defaulttool/defaulttool/DefaultTool.h
@@ -1,173 +1,173 @@
/* This file is part of the KDE project
Copyright (C) 2006-2008 Thorsten Zachmann <zachmann@kde.org>
Copyright (C) 2006-2008 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 DEFAULTTOOL_H
#define DEFAULTTOOL_H
#include <KoInteractionTool.h>
#include <KoFlake.h>
#include <commands/KoShapeAlignCommand.h>
#include <commands/KoShapeReorderCommand.h>
#include <QPolygonF>
#include <QTime>
+class QSignalMapper;
class KoInteractionStrategy;
class KoShapeMoveCommand;
class KoSelection;
+class DefaultToolTabbedWidget;
/**
* The default tool (associated with the arrow icon) implements the default
* interactions you have with flake objects.<br>
* The tool provides scaling, moving, selecting, rotation and soon skewing of
* any number of shapes.
* <p>Note that the implementation of those different strategies are delegated
* to the InteractionStrategy class and its subclasses.
*/
class DefaultTool : public KoInteractionTool
{
Q_OBJECT
public:
/**
* Constructor for basic interaction tool where user actions are translated
* and handled by interaction strategies of type KoInteractionStrategy.
* @param canvas the canvas this tool will be working for.
*/
explicit DefaultTool(KoCanvasBase *canvas);
virtual ~DefaultTool();
enum CanvasResource {
HotPosition = 1410100299
};
public:
virtual bool wantsAutoScroll() const;
virtual void paint(QPainter &painter, const KoViewConverter &converter);
virtual void repaintDecorations();
///reimplemented
virtual void copy() const;
///reimplemented
virtual void deleteSelection();
///reimplemented
virtual bool paste();
///reimplemented
- virtual QStringList supportedPasteMimeTypes() const;
- ///reimplemented
virtual KoToolSelection *selection();
+ QMenu* popupActionsMenu() override;
+
/**
* Returns which selection handle is at params point (or NoHandle if none).
* @return which selection handle is at params point (or NoHandle if none).
* @param point the location (in pt) where we should look for a handle
* @param innerHandleMeaning this boolean is altered to true if the point
* is inside the selection rectangle and false if it is just outside.
* The value of innerHandleMeaning is undefined if the handle location is NoHandle
*/
KoFlake::SelectionHandle handleAt(const QPointF &point, bool *innerHandleMeaning = 0);
public Q_SLOTS:
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ void activate(ToolActivation activation, const QSet<KoShape *> &shapes) override;
+ void deactivate() override;
private Q_SLOTS:
- void selectionAlignHorizontalLeft();
- void selectionAlignHorizontalCenter();
- void selectionAlignHorizontalRight();
- void selectionAlignVerticalTop();
- void selectionAlignVerticalCenter();
- void selectionAlignVerticalBottom();
+ void selectionAlign(int _align);
+ void selectionDistribute(int _distribute);
void selectionBringToFront();
void selectionSendToBack();
void selectionMoveUp();
void selectionMoveDown();
void selectionGroup();
void selectionUngroup();
+ void slotActivateEditFillGradient(bool value);
+ void slotActivateEditStrokeGradient(bool value);
+
/// Update actions on selection change
void updateActions();
public: // Events
- virtual void mousePressEvent(KoPointerEvent *event);
- virtual void mouseMoveEvent(KoPointerEvent *event);
- virtual void mouseReleaseEvent(KoPointerEvent *event);
- virtual void mouseDoubleClickEvent(KoPointerEvent *event);
+ void mousePressEvent(KoPointerEvent *event) override;
+ void mouseMoveEvent(KoPointerEvent *event) override;
+ void mouseReleaseEvent(KoPointerEvent *event) override;
+ void mouseDoubleClickEvent(KoPointerEvent *event) override;
- virtual void keyPressEvent(QKeyEvent *event);
-
- virtual void customMoveEvent(KoPointerEvent *event);
+ void keyPressEvent(QKeyEvent *event) override;
+ void explicitUserStrokeEndRequest() override;
protected:
QList<QPointer<QWidget> > createOptionWidgets();
- virtual KoInteractionStrategy *createStrategy(KoPointerEvent *event);
+ KoInteractionStrategy *createStrategy(KoPointerEvent *event) override;
+
+private:
+ class MoveGradientHandleInteractionFactory;
private:
void setupActions();
- void recalcSelectionBox();
+ void recalcSelectionBox(KoSelection *selection);
void updateCursor();
/// Returns rotation angle of given handle of the current selection
qreal rotationOfHandle(KoFlake::SelectionHandle handle, bool useEdgeRotation);
- void selectionAlign(KoShapeAlignCommand::Align align);
+ void addMappedAction(QSignalMapper *mapper, const QString &actionId, int type);
+
void selectionReorder(KoShapeReorderCommand::MoveShapeType order);
bool moveSelection(int direction, Qt::KeyboardModifiers modifiers);
/// Returns selection rectangle adjusted by handle proximity threshold
QRectF handlesSize();
// convenience method;
KoSelection *koSelection();
void canvasResourceChanged(int key, const QVariant &res);
- /// Returns list of editable shapes from the given list of shapes
- QList<KoShape *> filterEditableShapes(const QList<KoShape *> &shapes);
-
- /// Returns the number of editable shapes from the given list of shapes
- uint editableShapesCount(const QList<KoShape *> &shapes);
-
KoFlake::SelectionHandle m_lastHandle;
- KoFlake::Position m_hotPosition;
+ KoFlake::AnchorPosition m_hotPosition;
bool m_mouseWasInsideHandles;
QPointF m_selectionBox[8];
QPolygonF m_selectionOutline;
QPointF m_lastPoint;
- KoShapeMoveCommand *m_moveCommand;
- QTime m_lastUsedMoveCommand;
// TODO alter these 3 arrays to be static const instead
QCursor m_sizeCursors[8];
QCursor m_rotateCursors[8];
QCursor m_shearCursors[8];
qreal m_angle;
KoToolSelection *m_selectionHandler;
friend class SelectionHandler;
KoInteractionStrategy *m_customEventStrategy;
+ QScopedPointer<QMenu> m_contextMenu;
+
+ DefaultToolTabbedWidget *m_tabbedOptionWidget;
};
#endif
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.cpp
deleted file mode 100644
index 6e15b42979..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007 Martin Pfeiffer <hubipete@gmx.net>
- * 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 "DefaultToolArrangeWidget.h"
-
-#include <KoInteractionTool.h>
-#include <QAction>
-
-DefaultToolArrangeWidget::DefaultToolArrangeWidget(KoInteractionTool *tool, QWidget *parent)
- : QWidget(parent)
-{
- m_tool = tool;
-
- setupUi(this);
-
- bringToFront->setDefaultAction(m_tool->action("object_order_front"));
- raiseLevel->setDefaultAction(m_tool->action("object_order_raise"));
- lowerLevel->setDefaultAction(m_tool->action("object_order_lower"));
- sendBack->setDefaultAction(m_tool->action("object_order_back"));
-
- leftAlign->setDefaultAction(m_tool->action("object_align_horizontal_left"));
- hCenterAlign->setDefaultAction(m_tool->action("object_align_horizontal_center"));
- rightAlign->setDefaultAction(m_tool->action("object_align_horizontal_right"));
- topAlign->setDefaultAction(m_tool->action("object_align_vertical_top"));
- vCenterAlign->setDefaultAction(m_tool->action("object_align_vertical_center"));
- bottomAlign->setDefaultAction(m_tool->action("object_align_vertical_bottom"));
-
- group->setDefaultAction(m_tool->action("object_group"));
- ungroup->setDefaultAction(m_tool->action("object_ungroup"));
-}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.h b/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.h
deleted file mode 100644
index 75c8b7e0cd..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007 Martin Pfeiffer <hubipete@gmx.net>
- * 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 DEFAULTTOOLARRANGEWIDGET_H
-#define DEFAULTTOOLARRANGEWIDGET_H
-
-#include <ui_DefaultToolArrangeWidget.h>
-#include <KoFlake.h>
-
-#include <QWidget>
-
-class KoInteractionTool;
-
-class DefaultToolArrangeWidget : public QWidget, Ui::DefaultToolArrangeWidget
-{
- Q_OBJECT
-public:
- explicit DefaultToolArrangeWidget(KoInteractionTool *tool, QWidget *parent = 0);
-
-private:
- KoInteractionTool *m_tool;
-};
-
-#endif
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.ui b/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.ui
deleted file mode 100644
index 78c62f56fb..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolArrangeWidget.ui
+++ /dev/null
@@ -1,172 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>DefaultToolArrangeWidget</class>
- <widget class="QWidget" name="DefaultToolArrangeWidget">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>208</width>
- <height>70</height>
- </rect>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <property name="margin">
- <number>0</number>
- </property>
- <item row="0" column="0">
- <layout class="QGridLayout" name="gridLayout">
- <property name="horizontalSpacing">
- <number>2</number>
- </property>
- <item row="0" column="0">
- <widget class="QToolButton" name="leftAlign">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QToolButton" name="hCenterAlign">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QToolButton" name="rightAlign">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QToolButton" name="topAlign">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QToolButton" name="vCenterAlign">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QToolButton" name="bottomAlign">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <widget class="QFrame" name="frame">
- <property name="frameShape">
- <enum>QFrame::VLine</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <layout class="QGridLayout" name="gridLayout_2">
- <property name="horizontalSpacing">
- <number>2</number>
- </property>
- <item row="0" column="0">
- <widget class="QToolButton" name="bringToFront">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QToolButton" name="raiseLevel">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QToolButton" name="sendBack">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QToolButton" name="lowerLevel">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="3">
- <widget class="QFrame" name="frame_2">
- <property name="frameShape">
- <enum>QFrame::VLine</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Raised</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="4">
- <layout class="QGridLayout" name="gridLayout_3">
- <property name="horizontalSpacing">
- <number>2</number>
- </property>
- <item row="1" column="0">
- <widget class="QToolButton" name="ungroup">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QToolButton" name="group">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="5">
- <spacer name="horizontalSpacer">
- <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>0</height>
- </size>
- </property>
- </spacer>
- </item>
- <item row="1" column="0">
- <widget class="QWidget" name="SpecialSpacer">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolFactory.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolFactory.cpp
index 4e34a2ca97..cabe7e0cd2 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolFactory.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolFactory.cpp
@@ -1,44 +1,44 @@
/* This file is part of the KDE project
*
* 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 "DefaultToolFactory.h"
#include "DefaultTool.h"
#include <KoIcon.h>
#include <klocalizedstring.h>
DefaultToolFactory::DefaultToolFactory()
: KoToolFactoryBase(KoInteractionTool_ID)
{
- setToolTip(i18n("Shape Manipulation Tool"));
+ setToolTip(i18n("Select Shapes Tool"));
setSection(mainToolType());
setPriority(0);
setIconName(koIconNameCStr("select"));
setActivationShapeId("flake/always");
}
DefaultToolFactory::~DefaultToolFactory()
{
}
KoToolBase *DefaultToolFactory::createTool(KoCanvasBase *canvas)
{
return new DefaultTool(canvas);
}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp
new file mode 100644
index 0000000000..f92c919dfd
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.cpp
@@ -0,0 +1,483 @@
+/* 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 <KoCanvasResourceManager.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()));
+
+ // TODO: use valueChanged() instead!
+ connect(widthSpinBox, SIGNAL(valueChangedPt(qreal)), this, SLOT(slotResizeShapes()));
+ connect(heightSpinBox, SIGNAL(valueChangedPt(qreal)), this, SLOT(slotResizeShapes()));
+
+ 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(slotUpdateSizeBoxes()));
+ connect(selectedShapesProxy, SIGNAL(selectionChanged()), this, SLOT(slotUpdateOpacitySlider()));
+
+ connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdatePositionBoxes()));
+ connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateSizeBoxes()));
+ connect(selectedShapesProxy, SIGNAL(selectionContentChanged()), this, SLOT(slotUpdateOpacitySlider()));
+
+ connect(chkGlobalCoordinates, SIGNAL(toggled(bool)), SLOT(slotUpdateSizeBoxes()));
+
+ 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()));
+
+
+ // Connect and initialize anchor point resource
+ KoCanvasResourceManager *resourceManager = m_tool->canvas()->resourceManager();
+ connect(resourceManager,
+ SIGNAL(canvasResourceChanged(int,QVariant)),
+ SLOT(resourceChanged(int,QVariant)));
+ resourceManager->setResource(DefaultTool::HotPosition, int(KoFlake::Center));
+
+ // 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);
+
+ {
+ KisSignalCompressor *opacityCompressor =
+ new KisSignalCompressor(100, KisSignalCompressor::FIRST_ACTIVE, this);
+ connect(dblOpacity, SIGNAL(valueChanged(qreal)), opacityCompressor, SLOT(start()));
+ connect(opacityCompressor, SIGNAL(timeout()), SLOT(slotOpacitySliderChanged()));
+
+ // 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);
+ } 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);
+
+ 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();
+
+ QList<bool> oldKeepAspectRatio;
+ QList<bool> newKeepAspectRatio;
+
+ Q_FOREACH (KoShape *shape, shapes) {
+ oldKeepAspectRatio << shape->keepAspectRatio();
+ newKeepAspectRatio << aspectButton->keepAspectRatio();
+ }
+
+ KUndo2Command *cmd =
+ new KoShapeKeepAspectRatioCommand(shapes, oldKeepAspectRatio, newKeepAspectRatio);
+
+ 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()
+{
+ static const qreal eps = 1e-3;
+
+ KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
+ QList<KoShape*> shapes = selection->selectedEditableShapes();
+ if (shapes.isEmpty()) return;
+
+ const qreal newTransparency = 1.0 - dblOpacity->value();
+ const qreal commonTransparency = calculateCommonShapeTransparency(shapes);
+ if (qAbs(commonTransparency - newTransparency) < eps) {
+ return;
+ }
+
+ KUndo2Command *cmd =
+ new KoShapeTransparencyCommand(shapes, newTransparency);
+
+ m_tool->canvas()->addCommand(cmd);
+}
+
+void DefaultToolGeometryWidget::slotUpdateOpacitySlider()
+{
+ if (!isVisible()) return;
+
+ const QString opacityNormalPrefix = i18n("Opacity: ");
+ const QString opacityVariesPrefix = i18n("Opacity [*varies*]: ");
+
+ KoSelection *selection = m_tool->canvas()->selectedShapesProxy()->selection();
+ QList<KoShape*> shapes = selection->selectedEditableShapes();
+
+ if (shapes.isEmpty()) {
+ KisSignalsBlocker b(dblOpacity);
+ dblOpacity->setEnabled(false);
+ dblOpacity->setValue(1.0);
+ dblOpacity->setPrefix(opacityNormalPrefix);
+ } else {
+ const qreal commonTransparency = calculateCommonShapeTransparency(shapes);
+
+ dblOpacity->setEnabled(true);
+
+ if (commonTransparency >= 0.0) {
+ KisSignalsBlocker b(dblOpacity);
+ dblOpacity->setValue(1.0 - commonTransparency);
+ dblOpacity->setPrefix(opacityNormalPrefix);
+ } else {
+ KisSignalsBlocker b(dblOpacity);
+ dblOpacity->setValue(1.0);
+ dblOpacity->setPrefix(opacityVariesPrefix);
+ }
+ }
+}
+
+void DefaultToolGeometryWidget::slotUpdateSizeBoxes()
+{
+ 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());
+ m_sizeAspectLocker->updateAspect();
+ }
+}
+
+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();
+}
+
+void DefaultToolGeometryWidget::showEvent(QShowEvent *event)
+{
+ QWidget::showEvent(event);
+
+ slotUpdatePositionBoxes();
+ slotUpdateSizeBoxes();
+ slotUpdateOpacitySlider();
+ slotUpdateAspectButton();
+ slotUpdateCheckboxes();
+ slotAnchorPointChanged();
+}
+
+void DefaultToolGeometryWidget::resourceChanged(int key, const QVariant &res)
+{
+ if (key == KoCanvasResourceManager::Unit) {
+ setUnit(res.value<KoUnit>());
+ } else if (key == DefaultTool::HotPosition) {
+ positionSelector->setValue(KoFlake::AnchorPosition(res.toInt()));
+ }
+}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.h b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.h
new file mode 100644
index 0000000000..bf5c393d5c
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.h
@@ -0,0 +1,70 @@
+/* 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) 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 DEFAULTTOOLGEOMETRYWIDGET_H
+#define DEFAULTTOOLGEOMETRYWIDGET_H
+
+#include <ui_DefaultToolGeometryWidget.h>
+#include <KoFlake.h>
+
+#include <QWidget>
+
+
+class KoInteractionTool;
+class KisAspectRatioLocker;
+
+class DefaultToolGeometryWidget : public QWidget, Ui::DefaultToolGeometryWidget
+{
+ Q_OBJECT
+public:
+ explicit DefaultToolGeometryWidget(KoInteractionTool *tool, QWidget *parent = 0);
+ ~DefaultToolGeometryWidget();
+
+ /// Sets the unit used by the unit aware child widgets
+ void setUnit(const KoUnit &unit);
+
+protected:
+ void showEvent(QShowEvent *event);
+
+private Q_SLOTS:
+ void slotAnchorPointChanged();
+ void resourceChanged(int key, const QVariant &res);
+
+ void slotUpdatePositionBoxes();
+ void slotRepositionShapes();
+
+ void slotUpdateSizeBoxes();
+ void slotResizeShapes();
+
+ void slotUpdateCheckboxes();
+
+ void slotAspectButtonToggled();
+ void slotUpdateAspectButton();
+
+ void slotOpacitySliderChanged();
+ void slotUpdateOpacitySlider();
+
+private:
+ KoInteractionTool *m_tool;
+ QScopedPointer<KisAspectRatioLocker> m_sizeAspectLocker;
+ bool m_savedUniformScaling;
+};
+
+#endif
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui
new file mode 100644
index 0000000000..e359fb7f5e
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolGeometryWidget.ui
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>DefaultToolGeometryWidget</class>
+ <widget class="QWidget" name="DefaultToolGeometryWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>305</width>
+ <height>303</height>
+ </rect>
+ </property>
+ <property name="focusPolicy">
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>X:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="KisDoubleParseUnitSpinBox" name="positionXSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <double>-10000.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="KisDoubleParseUnitSpinBox" name="widthSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <double>-10000.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" rowspan="2">
+ <widget class="KoAspectButton" name="aspectButton" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text" stdset="0">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Y:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="KisDoubleParseUnitSpinBox" name="positionYSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <double>-10000.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="KisDoubleParseUnitSpinBox" name="heightSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimum">
+ <double>-10000.000000000000000</double>
+ </property>
+ <property name="maximum">
+ <double>10000.000000000000000</double>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,1">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="KoAnchorSelectionWidget" name="positionSelector" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::MinimumExpanding</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="spacing">
+ <number>1</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="chkAnchorLock">
+ <property name="text">
+ <string>Anchor Lock</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkUniformScaling">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When &amp;quot;Uniform Scaling&amp;quot; is &lt;span style=&quot; font-weight:600;&quot;&gt;enabled&lt;/span&gt;, the shape's stroke is scaled with the shape itself. &lt;/p&gt;&lt;p&gt;In &lt;span style=&quot; font-weight:600;&quot;&gt;disabled&lt;/span&gt; state, the shape is only resized, keeping the stroke width and style intact.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Uniform Scaling</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chkGlobalCoordinates">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In &amp;quot;Global Coordinates&amp;quot; mode Width and Height fields show the size of the shape's bounding box in image-aligned coordinates, even when the shape is rotated or has any other transform. &lt;/p&gt;&lt;p&gt;If &amp;quot;Global Coordinates&amp;quot; mode is disabled, Width and Height fields show the shape's &amp;quot;local&amp;quot; size, before application of any transformations.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Global Coordinates</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="Line" name="line_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="KisDoubleSliderSpinBox" name="dblOpacity" native="true"/>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>KoAspectButton</class>
+ <extends>QWidget</extends>
+ <header>KoAspectButton.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisDoubleParseUnitSpinBox</class>
+ <extends>QDoubleSpinBox</extends>
+ <header>kis_double_parse_unit_spin_box.h</header>
+ </customwidget>
+ <customwidget>
+ <class>KoAnchorSelectionWidget</class>
+ <extends>QWidget</extends>
+ <header>KoAnchorSelectionWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
+ <class>KisDoubleSliderSpinBox</class>
+ <extends>QWidget</extends>
+ <header location="global">kis_slider_spin_box.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>positionXSpinBox</tabstop>
+ <tabstop>positionYSpinBox</tabstop>
+ <tabstop>widthSpinBox</tabstop>
+ <tabstop>heightSpinBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp
new file mode 100644
index 0000000000..3851b5b840
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "DefaultToolTabbedWidget.h"
+
+#include <QLabel>
+#include "kis_icon_utils.h"
+#include "DefaultToolGeometryWidget.h"
+#include "KoStrokeConfigWidget.h"
+#include "KoFillConfigWidget.h"
+#include <KoInteractionTool.h>
+
+#include <kis_document_aware_spin_box_unit_manager.h>
+
+
+DefaultToolTabbedWidget::DefaultToolTabbedWidget(KoInteractionTool *tool, QWidget *parent)
+ : KoTitledTabWidget(parent)
+{
+ setObjectName("default-tool-tabbed-widget");
+
+ DefaultToolGeometryWidget *geometryWidget = new DefaultToolGeometryWidget(tool, this);
+ geometryWidget->setWindowTitle(i18n("Geometry"));
+ addTab(geometryWidget, KisIconUtils::loadIcon("geometry"), QString());
+
+ m_strokeWidget = new KoStrokeConfigWidget(tool->canvas(), this);
+ m_strokeWidget->setWindowTitle(i18n("Stroke"));
+
+ KisDocumentAwareSpinBoxUnitManager* managerLineWidth = new KisDocumentAwareSpinBoxUnitManager(m_strokeWidget);
+ KisDocumentAwareSpinBoxUnitManager* managerMitterLimit = new KisDocumentAwareSpinBoxUnitManager(m_strokeWidget);
+ managerLineWidth->setApparentUnitFromSymbol("px");
+ managerMitterLimit->setApparentUnitFromSymbol("px"); //set unit to px by default
+ m_strokeWidget->setUnitManagers(managerLineWidth, managerMitterLimit);
+
+ addTab(m_strokeWidget, KisIconUtils::loadIcon("krita_tool_line"), QString());
+
+ m_fillWidget = new KoFillConfigWidget(tool->canvas(), KoFlake::Fill, this);
+ m_fillWidget->setWindowTitle(i18n("Fill"));
+ addTab(m_fillWidget, KisIconUtils::loadIcon("krita_tool_color_fill"), QString());
+
+ connect(this, SIGNAL(currentChanged(int)), SLOT(slotCurrentIndexChanged(int)));
+ m_oldTabIndex = currentIndex();
+}
+
+DefaultToolTabbedWidget::~DefaultToolTabbedWidget()
+{
+}
+
+void DefaultToolTabbedWidget::activate()
+{
+ m_fillWidget->activate();
+ m_strokeWidget->activate();
+}
+
+void DefaultToolTabbedWidget::deactivate()
+{
+ m_fillWidget->deactivate();
+ m_strokeWidget->deactivate();
+}
+
+void DefaultToolTabbedWidget::slotCurrentIndexChanged(int current)
+{
+ if (m_oldTabIndex == FillTab) {
+ emit sigSwitchModeEditFillGradient(false);
+ } else if (m_oldTabIndex == StrokeTab) {
+ emit sigSwitchModeEditStrokeGradient(false);
+ }
+
+ m_oldTabIndex = current;
+
+ if (current == FillTab) {
+ emit sigSwitchModeEditFillGradient(true);
+ } else if (m_oldTabIndex == StrokeTab) {
+ emit sigSwitchModeEditStrokeGradient(true);
+ }
+}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.h b/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.h
new file mode 100644
index 0000000000..31eb961756
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolTabbedWidget.h
@@ -0,0 +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.
+ */
+
+#ifndef DEFAULTTOOLTABBEDWIDGET_H
+#define DEFAULTTOOLTABBEDWIDGET_H
+
+#include <KoTitledTabWidget.h>
+
+class KoInteractionTool;
+class KoFillConfigWidget;
+class KoStrokeConfigWidget;
+
+class DefaultToolTabbedWidget : public KoTitledTabWidget
+{
+ Q_OBJECT
+
+public:
+ explicit DefaultToolTabbedWidget(KoInteractionTool *tool, QWidget *parent = 0);
+ ~DefaultToolTabbedWidget();
+
+ enum TabType {
+ GeometryTab,
+ StrokeTab,
+ FillTab
+ };
+
+ void activate();
+ void deactivate();
+
+Q_SIGNALS:
+ void sigSwitchModeEditFillGradient(bool value);
+ void sigSwitchModeEditStrokeGradient(bool value);
+
+private Q_SLOTS:
+ void slotCurrentIndexChanged(int current);
+
+private:
+ int m_oldTabIndex;
+
+ KoFillConfigWidget *m_fillWidget;
+ KoStrokeConfigWidget *m_strokeWidget;
+};
+
+#endif // DEFAULTTOOLTABBEDWIDGET_H
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp
index 93b5c7db6f..37461e944e 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp
@@ -1,284 +1,286 @@
/* 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) 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 "DefaultToolTransformWidget.h"
#include <KoInteractionTool.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceManager.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoUnit.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeSizeCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include "SelectionDecorator.h"
#include <QSize>
#include <QRadioButton>
#include <QLabel>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QList>
#include <QTransform>
+#include "kis_document_aware_spin_box_unit_manager.h"
+
DefaultToolTransformWidget::DefaultToolTransformWidget(KoInteractionTool *tool, QWidget *parent)
: QMenu(parent)
{
m_tool = tool;
setupUi(this);
setUnit(m_tool->canvas()->unit());
connect(m_tool->canvas()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
this, SLOT(resourceChanged(int,QVariant)));
connect(rotateButton, SIGNAL(clicked()), this, SLOT(rotationChanged()));
connect(shearXButton, SIGNAL(clicked()), this, SLOT(shearXChanged()));
connect(shearYButton, SIGNAL(clicked()), this, SLOT(shearYChanged()));
connect(scaleXButton, SIGNAL(clicked()), this, SLOT(scaleXChanged()));
connect(scaleYButton, SIGNAL(clicked()), this, SLOT(scaleYChanged()));
connect(scaleAspectCheckBox, SIGNAL(toggled(bool)), scaleYSpinBox, SLOT(setDisabled(bool)));
connect(scaleAspectCheckBox, SIGNAL(toggled(bool)), scaleYButton, SLOT(setDisabled(bool)));
connect(resetButton, SIGNAL(clicked()), this, SLOT(resetTransformations()));
}
void DefaultToolTransformWidget::setUnit(const KoUnit &unit)
{
shearXSpinBox->setUnit(unit);
shearYSpinBox->setUnit(unit);
}
void DefaultToolTransformWidget::resourceChanged(int key, const QVariant &res)
{
if (key == KoCanvasResourceManager::Unit) {
setUnit(res.value<KoUnit>());
}
}
void DefaultToolTransformWidget::rotationChanged()
{
QList<KoShape *> selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection);
QList<QTransform> oldTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldTransforms << shape->transformation();
}
qreal angle = rotateSpinBox->value();
QPointF rotationCenter = m_tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition());
QTransform matrix;
matrix.translate(rotationCenter.x(), rotationCenter.y());
matrix.rotate(angle);
matrix.translate(-rotationCenter.x(), -rotationCenter.y());
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(matrix);
shape->update();
}
m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix);
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Rotate"));
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolTransformWidget::shearXChanged()
{
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
QList<QTransform> oldTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldTransforms << shape->transformation();
}
qreal shearX = shearXSpinBox->value() / selection->size().height();
QPointF basePoint = selection->absolutePosition(SelectionDecorator::hotPosition());
QTransform matrix;
matrix.translate(basePoint.x(), basePoint.y());
matrix.shear(shearX, 0.0);
matrix.translate(-basePoint.x(), -basePoint.y());
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(matrix);
shape->update();
}
selection->applyAbsoluteTransformation(matrix);
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Shear X"));
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolTransformWidget::shearYChanged()
{
KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
QList<KoShape *> selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection);
QList<QTransform> oldTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldTransforms << shape->transformation();
}
qreal shearY = shearYSpinBox->value() / selection->size().width();
QPointF basePoint = selection->absolutePosition(SelectionDecorator::hotPosition());
QTransform matrix;
matrix.translate(basePoint.x(), basePoint.y());
matrix.shear(0.0, shearY);
matrix.translate(-basePoint.x(), -basePoint.y());
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(matrix);
shape->update();
}
selection->applyAbsoluteTransformation(matrix);
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Shear Y"));
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolTransformWidget::scaleXChanged()
{
QList<KoShape *> selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection);
QList<QTransform> oldTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldTransforms << shape->transformation();
}
qreal scale = scaleXSpinBox->value() * 0.01; // Input is in per cent
QPointF basePoint = m_tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition());
QTransform matrix;
matrix.translate(basePoint.x(), basePoint.y());
if (scaleAspectCheckBox->isChecked()) {
matrix.scale(scale, scale);
} else {
matrix.scale(scale, 1.0);
}
matrix.translate(-basePoint.x(), -basePoint.y());
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(matrix);
shape->update();
}
m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix);
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Scale"));
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolTransformWidget::scaleYChanged()
{
QList<KoShape *> selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection);
QList<QTransform> oldTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldTransforms << shape->transformation();
}
qreal scale = scaleYSpinBox->value() * 0.01; // Input is in per cent
QPointF basePoint = m_tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition());
QTransform matrix;
matrix.translate(basePoint.x(), basePoint.y());
matrix.scale(1.0, scale);
matrix.translate(-basePoint.x(), -basePoint.y());
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(matrix);
shape->update();
}
m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix);
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Scale"));
m_tool->canvas()->addCommand(cmd);
}
void DefaultToolTransformWidget::resetTransformations()
{
QList<KoShape *> selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection);
QList<QTransform> oldTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
oldTransforms << shape->transformation();
}
QTransform matrix;
Q_FOREACH (KoShape *shape, selectedShapes) {
shape->update();
shape->setTransformation(matrix);
shape->update();
}
m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix);
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Reset Transformations"));
m_tool->canvas()->addCommand(cmd);
}
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.h b/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.h
deleted file mode 100644
index d1457fb8bd..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007 Martin Pfeiffer <hubipete@gmx.net>
- * 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 DEFAULTTOOLTRANSFORMWIDGET_H
-#define DEFAULTTOOLTRANSFORMWIDGET_H
-
-#include <ui_DefaultToolTransformWidget.h>
-#include <KoFlake.h>
-
-#include <QMenu>
-
-class KoInteractionTool;
-
-class DefaultToolTransformWidget : public QMenu, Ui::DefaultToolTransformWidget
-{
- Q_OBJECT
-public:
- explicit DefaultToolTransformWidget(KoInteractionTool *tool, QWidget *parent = 0);
-
- /// Sets the unit used by the unit aware child widgets
- void setUnit(const KoUnit &unit);
-
-private Q_SLOTS:
- void resourceChanged(int key, const QVariant &res);
- void rotationChanged();
- void shearXChanged();
- void shearYChanged();
- void scaleXChanged();
- void scaleYChanged();
- void resetTransformations();
-
-private:
- KoInteractionTool *m_tool;
-};
-
-#endif
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.ui b/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.ui
deleted file mode 100644
index 9a35520290..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.ui
+++ /dev/null
@@ -1,211 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>DefaultToolTransformWidget</class>
- <widget class="QMenu" name="DefaultToolTransformWidget">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>222</width>
- <height>293</height>
- </rect>
- </property>
- <property name="title">
- <string/>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="KisDoubleParseSpinBox" name="rotateSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="suffix">
- <string>°</string>
- </property>
- <property name="minimum">
- <double>-360.000000000000000</double>
- </property>
- <property name="maximum">
- <double>360.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QPushButton" name="rotateButton">
- <property name="text">
- <string>Rotate</string>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="KisDoubleParseUnitSpinBox" name="shearXSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <double>-100.000000000000000</double>
- </property>
- <property name="maximum">
- <double>100.000000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.100000000000000</double>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QPushButton" name="shearXButton">
- <property name="text">
- <string>Shear X</string>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="KisDoubleParseUnitSpinBox" name="shearYSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <double>-100.000000000000000</double>
- </property>
- <property name="maximum">
- <double>100.000000000000000</double>
- </property>
- <property name="singleStep">
- <double>0.100000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QPushButton" name="shearYButton">
- <property name="text">
- <string>Shear Y</string>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="KisDoubleParseSpinBox" name="scaleXSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="suffix">
- <string>%</string>
- </property>
- <property name="minimum">
- <double>-1000.000000000000000</double>
- </property>
- <property name="maximum">
- <double>1000.000000000000000</double>
- </property>
- <property name="value">
- <double>100.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QPushButton" name="scaleXButton">
- <property name="text">
- <string>Scale X</string>
- </property>
- </widget>
- </item>
- <item row="4" column="0">
- <widget class="KisDoubleParseSpinBox" name="scaleYSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>2</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- <property name="suffix">
- <string>%</string>
- </property>
- <property name="minimum">
- <double>-1000.000000000000000</double>
- </property>
- <property name="maximum">
- <double>1000.000000000000000</double>
- </property>
- <property name="value">
- <double>100.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
- <widget class="QPushButton" name="scaleYButton">
- <property name="text">
- <string>Scale Y</string>
- </property>
- </widget>
- </item>
- <item row="5" column="0" colspan="2">
- <widget class="QCheckBox" name="scaleAspectCheckBox">
- <property name="text">
- <string>Keep aspect ratio</string>
- </property>
- </widget>
- </item>
- <item row="6" column="0" colspan="2">
- <widget class="QPushButton" name="resetButton">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>Reset Transformations</string>
- </property>
- </widget>
- </item>
- <item row="7" column="0" colspan="2">
- <spacer name="spacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>191</width>
- <height>58</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>KisDoubleParseSpinBox</class>
- <extends>QDoubleSpinBox</extends>
- <header>kis_double_parse_spin_box.h</header>
- </customwidget>
- <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/tools/defaulttool/defaulttool/DefaultToolWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp
index 5c5b0e7b91..408daa2358 100644
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp
+++ b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp
@@ -1,280 +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 <KoCanvasResourceManager.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);
// 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 == KoCanvasResourceManager::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/DefaultToolWidget.h b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.h
deleted file mode 100644
index 1712b1ab3b..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/* 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) 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 DEFAULTTOOLWIDGET_H
-#define DEFAULTTOOLWIDGET_H
-
-#include <ui_DefaultToolWidget.h>
-#include <KoFlake.h>
-
-#include <QWidget>
-
-class KoInteractionTool;
-
-class DefaultToolWidget : public QWidget, Ui::DefaultToolWidget
-{
- Q_OBJECT
-public:
- explicit DefaultToolWidget(KoInteractionTool *tool, QWidget *parent = 0);
-
- /// Sets the unit used by the unit aware child widgets
- void setUnit(const KoUnit &unit);
-
-private Q_SLOTS:
- void positionSelected(KoFlake::Position position);
- void updatePosition();
- void positionHasChanged();
- void updateSize();
- void sizeHasChanged();
- void resourceChanged(int key, const QVariant &res);
- void aspectButtonToggled(bool keepAspect);
-
-private:
- KoInteractionTool *m_tool;
- bool m_blockSignals;
-};
-
-#endif
diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.ui b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.ui
deleted file mode 100644
index c5efcee49c..0000000000
--- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.ui
+++ /dev/null
@@ -1,175 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>DefaultToolWidget</class>
- <widget class="QWidget" name="DefaultToolWidget">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>256</width>
- <height>76</height>
- </rect>
- </property>
- <property name="focusPolicy">
- <enum>Qt::NoFocus</enum>
- </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="1">
- <widget class="QLabel" name="label">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string>X:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="KisDoubleParseUnitSpinBox" name="positionXSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <double>-10000.000000000000000</double>
- </property>
- <property name="maximum">
- <double>10000.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="0" column="3">
- <widget class="KisDoubleParseUnitSpinBox" name="widthSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <double>-10000.000000000000000</double>
- </property>
- <property name="maximum">
- <double>10000.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="0" column="4" rowspan="2">
- <widget class="KoAspectButton" name="aspectButton" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text" stdset="0">
- <string/>
- </property>
- </widget>
- </item>
- <item row="0" column="0" rowspan="2">
- <widget class="KoPositionSelector" name="positionSelector" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Y:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="KisDoubleParseUnitSpinBox" name="positionYSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <double>-10000.000000000000000</double>
- </property>
- <property name="maximum">
- <double>10000.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="1" column="3">
- <widget class="KisDoubleParseUnitSpinBox" name="heightSpinBox">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimum">
- <double>-10000.000000000000000</double>
- </property>
- <property name="maximum">
- <double>10000.000000000000000</double>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QWidget" name="SpecialSpacer" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>KoAspectButton</class>
- <extends>QWidget</extends>
- <header>KoAspectButton.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>KisDoubleParseUnitSpinBox</class>
- <extends>QDoubleSpinBox</extends>
- <header>kis_double_parse_unit_spin_box.h</header>
- </customwidget>
- <customwidget>
- <class>KoPositionSelector</class>
- <extends>QWidget</extends>
- <header>KoPositionSelector.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
- <tabstops>
- <tabstop>positionXSpinBox</tabstop>
- <tabstop>positionYSpinBox</tabstop>
- <tabstop>widthSpinBox</tabstop>
- <tabstop>heightSpinBox</tabstop>
- </tabstops>
- <resources/>
- <connections/>
-</ui>
diff --git a/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp
new file mode 100644
index 0000000000..b7e5dd1e63
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.cpp
@@ -0,0 +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<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);
+
+ 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);
+
+ 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
new file mode 100644
index 0000000000..d88fab5da5
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/KoShapeGradientHandles.h
@@ -0,0 +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;
+ 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 ab23f8e653..39f1925dd7 100644
--- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp
+++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.cpp
@@ -1,163 +1,170 @@
/* 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"
-#define HANDLE_DISTANCE 10
+#include "kis_debug.h"
+#include <KisHandlePainterHelper.h>
+#include <KoCanvasResourceManager.h>
+#include <KisQPainterStateSaver.h>
+#include "KoShapeGradientHandles.h"
+
+#include "kis_painting_tweaks.h"
-KoFlake::Position SelectionDecorator::m_hotPosition = KoFlake::TopLeftCorner;
+#define HANDLE_DISTANCE 10
-SelectionDecorator::SelectionDecorator(KoFlake::SelectionHandle arrows, bool rotationHandles, bool shearHandles)
- : m_rotationHandles(rotationHandles)
- , m_shearHandles(shearHandles)
- , m_arrows(arrows)
- , m_handleRadius(3)
- , m_lineWidth(1)
+SelectionDecorator::SelectionDecorator(KoCanvasResourceManager *resourceManager)
+ : m_hotPosition(KoFlake::Center)
+ , m_handleRadius(7)
+ , m_lineWidth(2)
+ , m_showFillGradientHandles(false)
+ , m_showStrokeFillGradientHandles(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::setHotPosition(KoFlake::Position hotPosition)
+void SelectionDecorator::setShowFillGradientHandles(bool value)
{
- m_hotPosition = hotPosition;
+ m_showFillGradientHandles = value;
}
-KoFlake::Position SelectionDecorator::hotPosition()
+void SelectionDecorator::setShowStrokeFillGradientHandles(bool value)
{
- return m_hotPosition;
+ m_showStrokeFillGradientHandles = value;
}
void SelectionDecorator::paint(QPainter &painter, const KoViewConverter &converter)
{
- QRectF handleArea;
- painter.save();
-
- // save the original painter transformation
- QTransform painterMatrix = painter.worldTransform();
-
- QPen pen;
- //Use the #00adf5 color with 50% opacity
- pen.setColor(QColor(0, 173, 245, 127));
- pen.setWidth(m_lineWidth);
- pen.setJoinStyle(Qt::RoundJoin);
- painter.setPen(pen);
+ const bool haveOnlyOneEditableShape = m_selection->selectedEditableShapes().size() == 1;
+
bool editable = false;
- foreach (KoShape *shape, m_selection->selectedShapes(KoFlake::StrippedSelection)) {
- // apply the shape transformation on top of the old painter transformation
- painter.setWorldTransform(shape->absoluteTransformation(&converter) * painterMatrix);
- // apply the zoom factor
- KoShape::applyConversion(painter, converter);
- // draw the shape bounding rect
- painter.drawRect(QRectF(QPointF(), shape->size()));
+
+ QList<KoShape*> selectedShapes = m_selection->selectedShapes();
+ if (selectedShapes.isEmpty()) return;
+
+ foreach (KoShape *shape, KoShape::linearizeSubtree(selectedShapes)) {
+ if (!haveOnlyOneEditableShape || !m_showStrokeFillGradientHandles) {
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&painter, shape, converter, m_handleRadius);
+
+ helper.setHandleStyle(KisHandleStyle::secondarySelection());
+ helper.drawRubberLine(shape->outlineRect());
+ }
if (!shape->isGeometryProtected()) {
editable = true;
}
}
- if (m_selection) {
- if (m_selection->count() > 1) {
- // more than one shape selected, so we need to draw the selection bounding rect
- painter.setPen(Qt::blue);
- // apply the selection transformation on top of the old painter transformation
- painter.setWorldTransform(m_selection->absoluteTransformation(&converter) * painterMatrix);
- // apply the zoom factor
- KoShape::applyConversion(painter, converter);
- // draw the selection bounding rect
- painter.drawRect(QRectF(QPointF(), m_selection->size()));
- // save the selection bounding rect for later drawing the selection handles
- handleArea = QRectF(QPointF(), m_selection->size());
- }
- else if (m_selection->firstSelectedShape()) {
- // only one shape selected, so we compose the correct painter matrix
- painter.setWorldTransform(m_selection->firstSelectedShape()->absoluteTransformation(&converter) * painterMatrix);
- KoShape::applyConversion(painter, converter);
- // save the only selected shapes bounding rect for later drawing the handles
- handleArea = QRectF(QPointF(), m_selection->firstSelectedShape()->size());
+ const QRectF handleArea = m_selection->outlineRect();
+
+ // draw extra rubber line around all the shapes
+ if (selectedShapes.size() > 1) {
+ KisHandlePainterHelper helper =
+ KoShape::createHandlePainterHelper(&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);
+ 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);
}
}
- painterMatrix = painter.worldTransform();
- painter.restore();
- // if we have no editable shape selected there is no need drawing the selection handles
- if (!editable) {
- return;
+ 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);
+
+ const QTransform t = shape->absoluteTransformation(0).inverted();
- painter.save();
-
- painter.setTransform(QTransform());
- painter.setRenderHint(QPainter::Antialiasing, false);
-
- painter.setPen(pen);
- painter.setBrush(pen.color());
-
- QPolygonF outline = painterMatrix.map(handleArea);
-
- // the 8 move rects
- QRectF rect(QPointF(0.5, 0.5), QSizeF(2 * m_handleRadius, 2 * m_handleRadius));
- rect.moveCenter(outline.value(0));
- painter.drawRect(rect);
- rect.moveCenter(outline.value(1));
- painter.drawRect(rect);
- rect.moveCenter(outline.value(2));
- painter.drawRect(rect);
- rect.moveCenter(outline.value(3));
- painter.drawRect(rect);
- rect.moveCenter((outline.value(0) + outline.value(1)) / 2);
- painter.drawRect(rect);
- rect.moveCenter((outline.value(1) + outline.value(2)) / 2);
- painter.drawRect(rect);
- rect.moveCenter((outline.value(2) + outline.value(3)) / 2);
- painter.drawRect(rect);
- rect.moveCenter((outline.value(3) + outline.value(0)) / 2);
- painter.drawRect(rect);
-
- // draw the hot position
- painter.setBrush(Qt::red);
- QPointF pos;
- switch (m_hotPosition) {
- case KoFlake::TopLeftCorner: pos = handleArea.topLeft(); break;
- case KoFlake::TopRightCorner: pos = handleArea.topRight(); break;
- case KoFlake::BottomLeftCorner: pos = handleArea.bottomLeft(); break;
- case KoFlake::BottomRightCorner: pos = handleArea.bottomRight(); break;
- case KoFlake::CenteredPosition: pos = handleArea.center(); break;
+ 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);
+ }
}
- rect.moveCenter(painterMatrix.map(pos));
- painter.drawRect(rect);
- painter.restore();
-}
+ 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);
+ }
+ }
+}
diff --git a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h
index 910fb89b61..8b679749f2 100644
--- a/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h
+++ b/plugins/tools/defaulttool/defaulttool/SelectionDecorator.h
@@ -1,82 +1,92 @@
/* 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.
*/
#ifndef SELECTIONDECORATOR_H
#define SELECTIONDECORATOR_H
#include <KoViewConverter.h>
#include <KoFlake.h>
#include <QPainter>
#include <QPointer>
class KoSelection;
+class KoCanvasResourceManager;
/**
* The SelectionDecorator is used to paint extra user-interface items on top of a selection.
*/
class SelectionDecorator
{
public:
/**
* Constructor.
* @param arrows the direction that needs highlighting. (currently unused)
* @param rotationHandles if true; the rotation handles will be drawn
* @param shearHandles if true; the shearhandles will be drawn
*/
- SelectionDecorator(KoFlake::SelectionHandle arrows, bool rotationHandles, bool shearHandles);
+ SelectionDecorator(KoCanvasResourceManager *resourceManager);
~SelectionDecorator() {}
/**
* paint the decortations.
* @param painter the painter to paint to.
* @param converter to convert between internal and view coordinates.
*/
void paint(QPainter &painter, const KoViewConverter &converter);
/**
* set the selection that is to be painted.
* @param selection the current selection.
*/
void setSelection(KoSelection *selection);
/**
* set the radius of the selection handles
* @param radius the new handle radius
*/
void setHandleRadius(int radius);
- /// Sets the hot position to highlight
- static void setHotPosition(KoFlake::Position hotPosition);
+ /**
+ * Set true if you want to render gradient handles on the canvas.
+ * Default value: false
+ */
+ void setShowFillGradientHandles(bool value);
+
+ /**
+ * Set true if you want to render gradient handles on the canvas.
+ * Default value: false
+ */
+ void setShowStrokeFillGradientHandles(bool value);
- /// Returns the hot position
- static KoFlake::Position hotPosition();
+private:
+ void paintGradientHandles(KoShape *shape, KoFlake::FillVariant fillVariant, QPainter &painter, const KoViewConverter &converter);
private:
- bool m_rotationHandles, m_shearHandles;
- KoFlake::SelectionHandle m_arrows;
- static KoFlake::Position m_hotPosition;
- QPointer<KoSelection> m_selection;
+ KoFlake::AnchorPosition m_hotPosition;
+ KoSelection *m_selection;
int m_handleRadius;
int m_lineWidth;
+ bool m_showFillGradientHandles;
+ bool m_showStrokeFillGradientHandles;
};
#endif
diff --git a/plugins/tools/defaulttool/defaulttool/SelectionTransformCommand.cpp b/plugins/tools/defaulttool/defaulttool/SelectionTransformCommand.cpp
deleted file mode 100644
index 64f587fd3b..0000000000
--- a/plugins/tools/defaulttool/defaulttool/SelectionTransformCommand.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/* 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 "SelectionTransformCommand.h"
-#include <KoSelection.h>
-
-SelectionTransformCommand::SelectionTransformCommand(KoSelection *selection, const QTransform &oldTransformation, const QTransform &newTransformation, KUndo2Command *parent)
- : KUndo2Command(parent)
- , m_selection(selection)
- , m_oldTransformation(oldTransformation)
- , m_newTransformation(newTransformation)
-{
- Q_ASSERT(m_selection);
- m_selectedShapes = m_selection->selectedShapes();
-}
-
-void SelectionTransformCommand::redo()
-{
- KUndo2Command::redo();
-
- m_selection->blockSignals(true);
-
- m_selection->deselectAll();
- Q_FOREACH (KoShape *shape, m_selectedShapes) {
- m_selection->select(shape, false);
- }
-
- m_selection->setTransformation(m_newTransformation);
-
- m_selection->blockSignals(false);
-}
-
-void SelectionTransformCommand::undo()
-{
- m_selection->blockSignals(true);
-
- m_selection->deselectAll();
- Q_FOREACH (KoShape *shape, m_selectedShapes) {
- m_selection->select(shape, false);
- }
-
- m_selection->setTransformation(m_oldTransformation);
-
- m_selection->blockSignals(false);
-
- KUndo2Command::undo();
-}
diff --git a/plugins/tools/defaulttool/defaulttool/SelectionTransformCommand.h b/plugins/tools/defaulttool/defaulttool/SelectionTransformCommand.h
deleted file mode 100644
index 15931743f0..0000000000
--- a/plugins/tools/defaulttool/defaulttool/SelectionTransformCommand.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* 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 SELECTION_TRANSFORM_COMMAND_H
-#define SELECTION_TRANSFORM_COMMAND_H
-
-#include <kundo2command.h>
-#include <QTransform>
-
-class KoSelection;
-class KoShape;
-
-class SelectionTransformCommand : public KUndo2Command
-{
-public:
- SelectionTransformCommand(KoSelection *selection, const QTransform &oldTransformation, const QTransform &newTransformation, KUndo2Command *parent = 0);
-
- /// reimplemented from KUndo2Command
- virtual void redo();
- /// reimplemented from KUndo2Command
- virtual void undo();
-private:
- KoSelection *m_selection;
- QList<KoShape *> m_selectedShapes;
- QTransform m_oldTransformation;
- QTransform m_newTransformation;
-};
-
-#endif // SELECTION_TRANSFORM_COMMAND_H
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp
new file mode 100644
index 0000000000..7373fa74cd
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.cpp
@@ -0,0 +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 <KoCanvasResourceManager.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;
+ 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/ShapeGradientEditStrategy.h b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.h
new file mode 100644
index 0000000000..329e3f6ed9
--- /dev/null
+++ b/plugins/tools/defaulttool/defaulttool/ShapeGradientEditStrategy.h
@@ -0,0 +1,46 @@
+/*
+ * 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 SHAPEGRADIENTEDITSTRATEGY_H
+#define SHAPEGRADIENTEDITSTRATEGY_H
+
+#include <QScopedPointer>
+#include <KoInteractionStrategy.h>
+#include "KoShapeGradientHandles.h"
+
+class ShapeGradientEditStrategy : public KoInteractionStrategy
+{
+public:
+ ShapeGradientEditStrategy(KoToolBase *tool,
+ KoFlake::FillVariant fillVariant,
+ KoShape *shape,
+ KoShapeGradientHandles::Handle::Type startHandleType,
+ const QPointF &clicked);
+ ~ShapeGradientEditStrategy();
+
+ void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) override;
+ KUndo2Command *createCommand() override;
+ void finishInteraction(Qt::KeyboardModifiers modifiers) override;
+ void paint(QPainter &painter, const KoViewConverter &converter) override;
+
+private:
+ struct Private;
+ QScopedPointer<Private> m_d;
+};
+
+#endif // SHAPEGRADIENTEDITSTRATEGY_H
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.cpp
index 225be47a11..86b8f826e7 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.cpp
@@ -1,140 +1,127 @@
/* 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 "ShapeMoveStrategy.h"
#include "SelectionDecorator.h"
#include <KoCanvasBase.h>
#include <KoShapeManager.h>
#include <KoShapeContainer.h>
#include <KoShapeContainerModel.h>
+#include <KoCanvasResourceManager.h>
#include <commands/KoShapeMoveCommand.h>
#include <KoSnapGuide.h>
#include <KoPointerEvent.h>
#include <KoToolBase.h>
#include <KoSelection.h>
#include <klocalizedstring.h>
#include <kis_global.h>
+#include "kis_debug.h"
+
ShapeMoveStrategy::ShapeMoveStrategy(KoToolBase *tool, const QPointF &clicked)
: KoInteractionStrategy(tool)
, m_start(clicked)
, m_canvas(tool->canvas())
{
- QList<KoShape *> selectedShapes = m_canvas->shapeManager()->selection()->selectedShapes(KoFlake::StrippedSelection);
+ QList<KoShape *> selectedShapes = m_canvas->shapeManager()->selection()->selectedEditableShapes();
+
QRectF boundingRect;
Q_FOREACH (KoShape *shape, selectedShapes) {
- if (!shape->isEditable()) {
- continue;
- }
m_selectedShapes << shape;
- m_previousPositions << shape->position();
- m_newPositions << shape->position();
+ m_previousPositions << shape->absolutePosition(KoFlake::Center);
+ m_newPositions << shape->absolutePosition(KoFlake::Center);
boundingRect = boundingRect.united(shape->boundingRect());
}
+
+ KoFlake::AnchorPosition anchor =
+ KoFlake::AnchorPosition(
+ m_canvas->resourceManager()->resource(KoFlake::HotPosition).toInt());
+
KoSelection *selection = m_canvas->shapeManager()->selection();
- m_initialOffset = selection->absolutePosition(SelectionDecorator::hotPosition()) - m_start;
- m_initialSelectionPosition = selection->position();
- m_canvas->snapGuide()->setIgnoredShapes(selection->selectedShapes(KoFlake::FullSelection));
+ m_initialOffset = selection->absolutePosition(anchor) - m_start;
+ m_canvas->snapGuide()->setIgnoredShapes(KoShape::linearizeSubtree(m_selectedShapes));
- tool->setStatusText(i18n("Press ALT to hold x- or y-position."));
+ tool->setStatusText(i18n("Press Shift to hold x- or y-position."));
}
void ShapeMoveStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers)
{
if (m_selectedShapes.isEmpty()) {
return;
}
QPointF diff = point - m_start;
if (modifiers & Qt::ShiftModifier) {
// Limit change to one direction only
diff = snapToClosestAxis(diff);
} else {
QPointF positionToSnap = point + m_initialOffset;
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
QPointF snappedPosition = tool()->canvas()->snapGuide()->snap(positionToSnap, modifiers);
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
diff = snappedPosition - m_initialOffset - m_start;
}
- m_diff = diff;
-
- moveSelection();
-}
-
-void ShapeMoveStrategy::handleCustomEvent(KoPointerEvent *event)
-{
- QPointF diff = tool()->canvas()->viewConverter()->viewToDocument(event->pos());
-
- if (event->modifiers() & Qt::ShiftModifier) {
- // Limit change to one direction only
- diff = snapToClosestAxis(diff);
- }
-
- m_diff += 0.1 * diff;
-
- moveSelection();
+ moveSelection(diff);
+ m_finalMove = diff;
}
-void ShapeMoveStrategy::moveSelection()
+void ShapeMoveStrategy::moveSelection(const QPointF &diff)
{
Q_ASSERT(m_newPositions.count());
int i = 0;
Q_FOREACH (KoShape *shape, m_selectedShapes) {
- QPointF delta = m_previousPositions.at(i) + m_diff - shape->position();
+ QPointF delta = m_previousPositions.at(i) + diff - shape->absolutePosition(KoFlake::Center);
if (shape->parent()) {
shape->parent()->model()->proposeMove(shape, delta);
}
tool()->canvas()->clipToDocument(shape, delta);
- QPointF newPos(shape->position() + delta);
+ QPointF newPos(shape->absolutePosition(KoFlake::Center) + delta);
m_newPositions[i] = newPos;
shape->update();
- shape->setPosition(newPos);
+ shape->setAbsolutePosition(newPos, KoFlake::Center);
shape->update();
i++;
}
- tool()->canvas()->shapeManager()->selection()->setPosition(m_initialSelectionPosition + m_diff);
}
KUndo2Command *ShapeMoveStrategy::createCommand()
{
tool()->canvas()->snapGuide()->reset();
- if (m_diff.x() == 0 && m_diff.y() == 0) {
+ if (m_finalMove.isNull()) {
return 0;
}
return new KoShapeMoveCommand(m_selectedShapes, m_previousPositions, m_newPositions);
}
void ShapeMoveStrategy::finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
tool()->canvas()->updateCanvas(tool()->canvas()->snapGuide()->boundingRect());
}
void ShapeMoveStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
- SelectionDecorator decorator(KoFlake::NoHandle, false, false);
- decorator.setSelection(tool()->canvas()->shapeManager()->selection());
- decorator.setHandleRadius(handleRadius());
- decorator.paint(painter, converter);
+ Q_UNUSED(painter);
+ Q_UNUSED(converter);
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.h b/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.h
index 4e6678682c..bddd0a05eb 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.h
+++ b/plugins/tools/defaulttool/defaulttool/ShapeMoveStrategy.h
@@ -1,65 +1,64 @@
/* 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.
*/
#ifndef SHAPEMOVESTRATEGY_H
#define SHAPEMOVESTRATEGY_H
#include <KoInteractionStrategy.h>
#include <QPointer>
#include <QPointF>
#include <QList>
#include <KoCanvasBase.h>
class KoToolBase;
class KoShape;
/**
* Implements the Move action on an object or selected objects.
*/
class ShapeMoveStrategy : public KoInteractionStrategy
{
public:
/**
* Constructor that starts to move the objects.
* @param tool the parent tool which controls this strategy
* @param canvas the canvas interface which will supply things like a selection object
* @param clicked the initial point that the user depressed (in pt).
*/
ShapeMoveStrategy(KoToolBase *tool, const QPointF &clicked);
virtual ~ShapeMoveStrategy() {}
- void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers);
- KUndo2Command *createCommand();
- void finishInteraction(Qt::KeyboardModifiers modifiers);
- virtual void paint(QPainter &painter, const KoViewConverter &converter);
- virtual void handleCustomEvent(KoPointerEvent *event);
+ void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) override;
+ KUndo2Command *createCommand() override;
+ void finishInteraction(Qt::KeyboardModifiers modifiers) override;
+ virtual void paint(QPainter &painter, const KoViewConverter &converter) override;
private:
- void moveSelection();
+ void moveSelection(const QPointF &diff);
QList<QPointF> m_previousPositions;
QList<QPointF> m_newPositions;
- QPointF m_start, m_diff, m_initialSelectionPosition, m_initialOffset;
+ QPointF m_start, m_finalMove, m_initialOffset;
QList<KoShape *> m_selectedShapes;
QPointer<KoCanvasBase> m_canvas;
};
#endif
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp
index 2921be94b1..385ca872fe 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp
@@ -1,305 +1,245 @@
/* 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/KoShapeSizeCommand.h>
-#include <commands/KoShapeTransformCommand.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, const QPointF &clicked, KoFlake::SelectionHandle direction)
: KoInteractionStrategy(tool)
- , m_lastScale(1.0, 1.0)
{
Q_ASSERT(tool->canvas()->shapeManager()->selection());
Q_ASSERT(tool->canvas()->shapeManager()->selection()->count() > 0);
-
- QList<KoShape *> selectedShapes = tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::StrippedSelection);
- Q_FOREACH (KoShape *shape, selectedShapes) {
- if (!shape->isEditable()) {
- continue;
- }
- m_selectedShapes << shape;
- m_startPositions << shape->position();
- m_oldTransforms << shape->transformation();
- m_transformations << QTransform();
- m_startSizes << shape->size();
- }
+ m_selectedShapes = tool->canvas()->shapeManager()->selection()->selectedEditableShapes();
m_start = clicked;
KoShape *shape = 0;
- if (tool->canvas()->shapeManager()->selection()->count() > 1) {
+ if (m_selectedShapes.size() > 1) {
shape = tool->canvas()->shapeManager()->selection();
- }
- if (tool->canvas()->shapeManager()->selection()->count() == 1) {
- shape = tool->canvas()->shapeManager()->selection()->firstSelectedShape();
+ } else if (m_selectedShapes.size() == 1) {
+ shape = m_selectedShapes.first();
}
if (shape) {
- m_windMatrix = shape->absoluteTransformation(0);
- m_unwindMatrix = m_windMatrix.inverted();
- m_initialSize = shape->size();
- m_initialPosition = m_windMatrix.map(QPointF());
+ const qreal w = shape->size().width();
+ const qreal h = shape->size().height();
switch (direction) {
case KoFlake::TopMiddleHandle:
- m_start = 0.5 * (shape->absolutePosition(KoFlake::TopLeftCorner) + shape->absolutePosition(KoFlake::TopRightCorner));
- m_top = true; m_bottom = false; m_left = false; m_right = false; break;
+ 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::TopRightCorner);
- m_top = true; m_bottom = false; m_left = false; m_right = true; break;
+ 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::TopRightCorner) + shape->absolutePosition(KoFlake::BottomRightCorner));
- m_top = false; m_bottom = false; m_left = false; m_right = true; break;
+ 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::BottomRightCorner);
- m_top = false; m_bottom = true; m_left = false; m_right = true; break;
+ 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::BottomRightCorner) + shape->absolutePosition(KoFlake::BottomLeftCorner));
- m_top = false; m_bottom = true; m_left = false; m_right = false; break;
+ 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::BottomLeftCorner);
- m_top = false; m_bottom = true; m_left = true; m_right = false; break;
+ 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::BottomLeftCorner) + shape->absolutePosition(KoFlake::TopLeftCorner));
- m_top = false; m_bottom = false; m_left = true; m_right = false; break;
+ 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::TopLeftCorner);
- m_top = true; m_bottom = false; m_left = true; m_right = false; break;
+ 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_globalCenterPoint = shape->absolutePosition(KoFlake::Center);
+
+ m_unwindMatrix = shape->absoluteTransformation(0).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_initialSize.width();
+ qreal startWidth = m_initialSelectionSize.width();
if (startWidth < std::numeric_limits<qreal>::epsilon()) {
startWidth = std::numeric_limits<qreal>::epsilon();
}
- qreal startHeight = m_initialSize.height();
+ 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_initialSize.width() < std::numeric_limits<qreal>::epsilon()) {
+ 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_initialSize.height() < std::numeric_limits<qreal>::epsilon()) {
+ 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 && startWidth < startHeight) || m_left || m_right) {
zoomY = zoomX;
} else {
zoomX = zoomY;
}
}
- QPointF move;
-
- if (scaleFromCenter) {
- move = QPointF(startWidth / 2.0, startHeight / 2.0);
- } else {
- move = QPointF(m_left ? startWidth : 0, m_top ? startHeight : 0);
- }
-
- resizeBy(move, zoomX, zoomY);
+ resizeBy(scaleFromCenter ? m_globalCenterPoint : m_globalStillPoint, zoomX, zoomY);
}
-void ShapeResizeStrategy::handleCustomEvent(KoPointerEvent *event)
+void ShapeResizeStrategy::resizeBy(const QPointF &stillPoint, qreal zoomX, qreal zoomY)
{
- QPointF center = 0.5 * QPointF(m_initialSize.width(), m_initialSize.height());
- qreal zoom = pow(1.01, -0.1 * event->z());
- m_lastScale *= zoom;
- resizeBy(center, m_lastScale.x(), m_lastScale.y());
-}
-
-void ShapeResizeStrategy::resizeBy(const QPointF &center, qreal zoomX, qreal zoomY)
-{
- QTransform matrix;
- matrix.translate(center.x(), center.y()); // translate to
- matrix.scale(zoomX, zoomY);
- matrix.translate(-center.x(), -center.y()); // and back
-
- // that is the transformation we want to apply to the shapes
- matrix = m_unwindMatrix * matrix * m_windMatrix;
-
- // the resizing transformation without the mirroring part
- QTransform resizeMatrix;
- resizeMatrix.translate(center.x(), center.y()); // translate to
- resizeMatrix.scale(qAbs(zoomX), qAbs(zoomY));
- resizeMatrix.translate(-center.x(), -center.y()); // and back
-
- // the mirroring part of the resizing transformation
- QTransform mirrorMatrix;
- mirrorMatrix.translate(center.x(), center.y()); // translate to
- mirrorMatrix.scale(zoomX < 0 ? -1 : 1, zoomY < 0 ? -1 : 1);
- mirrorMatrix.translate(-center.x(), -center.y()); // and back
-
- int i = 0;
- Q_FOREACH (KoShape *shape, m_selectedShapes) {
- shape->update();
-
- // this uses resize for the zooming part
- shape->applyAbsoluteTransformation(m_unwindMatrix);
-
- /*
- normally we would just apply the resizeMatrix now and be done with it, but
- we want to resize instead of scale, so we have to separate the scaling part
- of that transformation which can then be used to resize
- */
-
- // undo the last resize transformation
- shape->applyAbsoluteTransformation(m_transformations[i].inverted());
-
- // save the shapes transformation matrix
- QTransform shapeMatrix = shape->absoluteTransformation(0);
-
- // 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
- qreal scaleX = localMatrix.m11();
- qreal scaleY = 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
- QSizeF size(scaleX * m_startSizes[i].width(), scaleY * m_startSizes[i].height());
-
- // apply the transformation
- shape->setSize(size);
- // apply the rest of the transformation without the resizing part
- shape->applyAbsoluteTransformation(scaleMatrix.inverted() * resizeMatrix);
- shape->applyAbsoluteTransformation(mirrorMatrix);
-
- // and remember the applied transformation later for later undoing
- m_transformations[i] = shapeMatrix.inverted() * shape->absoluteTransformation(0);
+ if (m_executedCommand) {
+ m_executedCommand->undo();
+ m_executedCommand.reset();
+ }
- shape->applyAbsoluteTransformation(m_windMatrix);
+ const bool usePostScaling = m_selectedShapes.size() > 1;
- shape->update();
- i++;
- }
- tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix * m_scaleMatrix.inverted());
- m_scaleMatrix = matrix;
+ m_executedCommand.reset(
+ new KoShapeResizeCommand(
+ m_selectedShapes,
+ zoomX, zoomY,
+ stillPoint,
+ false, usePostScaling, m_postScalingCoveringTransform));
+ m_executedCommand->redo();
}
KUndo2Command *ShapeResizeStrategy::createCommand()
{
tool()->canvas()->snapGuide()->reset();
- QList<QSizeF> newSizes;
- QList<QTransform> transformations;
- const int shapeCount = m_selectedShapes.count();
- for (int i = 0; i < shapeCount; ++i) {
- newSizes << m_selectedShapes[i]->size();
- transformations << m_selectedShapes[i]->transformation();
+
+ if (m_executedCommand) {
+ m_executedCommand->setSkipOneRedo(true);
}
- KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Resize"));
- new KoShapeSizeCommand(m_selectedShapes, m_startSizes, newSizes, cmd);
- new KoShapeTransformCommand(m_selectedShapes, m_oldTransforms, transformations, cmd);
- return cmd;
+
+ 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)
{
- SelectionDecorator decorator(KoFlake::NoHandle, false, false);
- decorator.setSelection(tool()->canvas()->shapeManager()->selection());
- decorator.setHandleRadius(handleRadius());
- decorator.paint(painter, converter);
+ Q_UNUSED(painter);
+ Q_UNUSED(converter);
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.h b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.h
index 376351566b..645eee9e2d 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.h
+++ b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.h
@@ -1,70 +1,70 @@
/* This file is part of the KDE project
* 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.
*/
#ifndef SHAPERESIZESTRATEGY_H
#define SHAPERESIZESTRATEGY_H
#include <KoInteractionStrategy.h>
#include <KoFlake.h>
+#include <QScopedPointer>
#include <QPointF>
#include <QList>
#include <QTransform>
class KoToolBase;
class KoShape;
+class KoShapeResizeCommand;
/**
* A strategy for the KoInteractionTool.
* This strategy is invoked when the user starts a resize of a selection of objects,
* the stategy will then resize the objects interactively and provide a command afterwards.
*/
class ShapeResizeStrategy : public KoInteractionStrategy
{
public:
/**
* Constructor
*/
ShapeResizeStrategy(KoToolBase *tool, const QPointF &clicked, KoFlake::SelectionHandle direction);
- virtual ~ShapeResizeStrategy() {}
+ ~ShapeResizeStrategy();
void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers);
KUndo2Command *createCommand();
void finishInteraction(Qt::KeyboardModifiers modifiers);
virtual void paint(QPainter &painter, const KoViewConverter &converter);
- virtual void handleCustomEvent(KoPointerEvent *event);
private:
- void resizeBy(const QPointF &center, qreal zoomX, qreal zoomY);
+ void resizeBy(const QPointF &stillPoint, qreal zoomX, qreal zoomY);
QPointF m_start;
- QList<QPointF> m_startPositions;
- QList<QSizeF> m_startSizes;
- bool m_top, m_left, m_bottom, m_right;
- QTransform m_unwindMatrix, m_windMatrix;
- QSizeF m_initialSize;
- QPointF m_initialPosition;
- QTransform m_scaleMatrix;
- QList<QTransform> m_oldTransforms;
- QList<QTransform> m_transformations;
- QPointF m_lastScale;
QList<KoShape *> m_selectedShapes;
+
+ QTransform m_postScalingCoveringTransform;
+ QSizeF m_initialSelectionSize;
+ QTransform m_unwindMatrix;
+ bool m_top, m_left, m_bottom, m_right;
+
+ QPointF m_globalStillPoint;
+ QPointF m_globalCenterPoint;
+ QScopedPointer<KoShapeResizeCommand> m_executedCommand;
};
#endif
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.cpp
index 1375078914..1dfa225bb3 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.cpp
@@ -1,159 +1,111 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007-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 "ShapeRotateStrategy.h"
#include "SelectionDecorator.h"
-#include "SelectionTransformCommand.h"
#include <KoToolBase.h>
#include <KoCanvasBase.h>
#include <KoSelection.h>
#include <KoPointerEvent.h>
#include <KoShapeManager.h>
+#include <KoCanvasResourceManager.h>
#include <commands/KoShapeTransformCommand.h>
#include <QPointF>
#include <math.h>
#include <klocalizedstring.h>
ShapeRotateStrategy::ShapeRotateStrategy(KoToolBase *tool, const QPointF &clicked, Qt::MouseButtons buttons)
: KoInteractionStrategy(tool)
- , m_initialBoundingRect()
, m_start(clicked)
{
- m_initialSelectionMatrix = tool->canvas()->shapeManager()->selection()->transformation();
-
- QList<KoShape *> selectedShapes = tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::StrippedSelection);
- Q_FOREACH (KoShape *shape, selectedShapes) {
- if (!shape->isEditable()) {
- continue;
- }
- m_selectedShapes << shape;
- if (m_selectedShapes.count() == 1) {
- m_initialBoundingRect = shape->boundingRect();
- } else {
- m_initialBoundingRect = m_initialBoundingRect.united(shape->boundingRect());
- }
+ m_selectedShapes = tool->canvas()->shapeManager()->selection()->selectedEditableShapes();
+ Q_FOREACH (KoShape *shape, m_selectedShapes) {
m_oldTransforms << shape->transformation();
}
- if (buttons & Qt::RightButton) {
- m_rotationCenter = tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition());
- } else {
- m_rotationCenter = m_initialBoundingRect.center();
- }
+ KoFlake::AnchorPosition anchor = !(buttons & Qt::RightButton) ?
+ KoFlake::Center :
+ KoFlake::AnchorPosition(tool->canvas()->resourceManager()->resource(KoFlake::HotPosition).toInt());
+
+ m_rotationCenter = tool->canvas()->shapeManager()->selection()->absolutePosition(anchor);
tool->setStatusText(i18n("Press ALT to rotate in 45 degree steps."));
}
void ShapeRotateStrategy::handleMouseMove(const QPointF &point, Qt::KeyboardModifiers modifiers)
{
qreal angle = atan2(point.y() - m_rotationCenter.y(), point.x() - m_rotationCenter.x()) -
atan2(m_start.y() - m_rotationCenter.y(), m_start.x() - m_rotationCenter.x());
angle = angle / M_PI * 180; // convert to degrees.
if (modifiers & (Qt::AltModifier | Qt::ControlModifier)) {
// limit to 45 degree angles
qreal modula = qAbs(angle);
while (modula > 45.0) {
modula -= 45.0;
}
if (modula > 22.5) {
modula -= 45.0;
}
angle += (angle > 0 ? -1 : 1) * modula;
}
- QTransform matrix;
- matrix.translate(m_rotationCenter.x(), m_rotationCenter.y());
- matrix.rotate(angle);
- matrix.translate(-m_rotationCenter.x(), -m_rotationCenter.y());
-
- QTransform applyMatrix = matrix * m_rotationMatrix.inverted();
- m_rotationMatrix = matrix;
- Q_FOREACH (KoShape *shape, m_selectedShapes) {
- shape->update();
- shape->applyAbsoluteTransformation(applyMatrix);
- shape->update();
- }
- tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(applyMatrix);
-}
-
-void ShapeRotateStrategy::handleCustomEvent(KoPointerEvent *event)
-{
- QTransform matrix;
- matrix.translate(m_rotationCenter.x(), m_rotationCenter.y());
- matrix.rotate(0.1 * event->rotationZ());
- matrix.translate(-m_rotationCenter.x(), -m_rotationCenter.y());
-
- m_rotationMatrix *= matrix;
- Q_FOREACH (KoShape *shape, m_selectedShapes) {
- shape->update();
- shape->applyAbsoluteTransformation(matrix);
- shape->update();
- }
- tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix);
+ rotateBy(angle);
}
void ShapeRotateStrategy::rotateBy(qreal angle)
{
QTransform matrix;
matrix.translate(m_rotationCenter.x(), m_rotationCenter.y());
matrix.rotate(angle);
matrix.translate(-m_rotationCenter.x(), -m_rotationCenter.y());
QTransform applyMatrix = matrix * m_rotationMatrix.inverted();
m_rotationMatrix = matrix;
Q_FOREACH (KoShape *shape, m_selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(applyMatrix);
shape->update();
}
- tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(applyMatrix);
}
void ShapeRotateStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
- SelectionDecorator decorator(KoFlake::NoHandle, true, false);
- decorator.setSelection(tool()->canvas()->shapeManager()->selection());
- decorator.setHandleRadius(handleRadius());
- decorator.paint(painter, converter);
-
// paint the rotation center
painter.setPen(QPen(Qt::red));
painter.setBrush(QBrush(Qt::red));
painter.setRenderHint(QPainter::Antialiasing, true);
QRectF circle(0, 0, 5, 5);
circle.moveCenter(converter.documentToView(m_rotationCenter));
painter.drawEllipse(circle);
}
KUndo2Command *ShapeRotateStrategy::createCommand()
{
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, m_selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(m_selectedShapes, m_oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Rotate"));
- KoSelection *sel = tool()->canvas()->shapeManager()->selection();
- new SelectionTransformCommand(sel, m_initialSelectionMatrix, sel->transformation(), cmd);
return cmd;
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.h b/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.h
index 9de49bb5a5..47bf458008 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.h
+++ b/plugins/tools/defaulttool/defaulttool/ShapeRotateStrategy.h
@@ -1,70 +1,68 @@
/* This file is part of the KDE project
* 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.
*/
#ifndef SHAPEROTATESTRATEGY_H
#define SHAPEROTATESTRATEGY_H
#include <KoInteractionStrategy.h>
#include <QPointF>
#include <QRectF>
#include <QTransform>
#include <QList>
class KoToolBase;
class KoShape;
/**
* A strategy for the KoInteractionTool.
* This strategy is invoked when the user starts a rotate of a selection of objects,
* the stategy will then rotate the objects interactively and provide a command afterwards.
*/
class ShapeRotateStrategy : public KoInteractionStrategy
{
public:
/**
* Constructor that starts to rotate the objects.
* @param tool the parent tool which controls this strategy
* @param clicked the initial point that the user depressed (in pt).
*/
ShapeRotateStrategy(KoToolBase *tool, const QPointF &clicked, Qt::MouseButtons buttons);
virtual ~ShapeRotateStrategy() {}
void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers);
KUndo2Command *createCommand();
void finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
}
virtual void paint(QPainter &painter, const KoViewConverter &converter);
- virtual void handleCustomEvent(KoPointerEvent *event);
private:
void rotateBy(qreal angle);
- QRectF m_initialBoundingRect;
+
QPointF m_start;
QTransform m_rotationMatrix;
- QTransform m_initialSelectionMatrix;
QList<QTransform> m_oldTransforms;
QPointF m_rotationCenter;
QList<KoShape *> m_selectedShapes;
};
#endif
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp b/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp
index 1a64ed3175..316d598fc1 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp
+++ b/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.cpp
@@ -1,184 +1,171 @@
/* 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 "SelectionTransformCommand.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, const QPointF &clicked, KoFlake::SelectionHandle direction)
: KoInteractionStrategy(tool)
, m_start(clicked)
{
KoSelection *sel = tool->canvas()->shapeManager()->selection();
- QList<KoShape *> selectedShapes = sel->selectedShapes(KoFlake::StrippedSelection);
- Q_FOREACH (KoShape *shape, selectedShapes) {
- if (!shape->isEditable()) {
- continue;
- }
- m_selectedShapes << shape;
+ m_selectedShapes = sel->selectedEditableShapes();
+ Q_FOREACH (KoShape *shape, m_selectedShapes) {
m_oldTransforms << shape->transformation();
}
- m_initialSelectionMatrix = sel->transformation();
-
// Eventhoug 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 = sel->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 = sel->absoluteTransformation(0).map(sel->outlineRect().topLeft() + m_solidPoint);
+
QPointF edge;
qreal angle = 0.0;
if (m_top) {
- edge = sel->absolutePosition(KoFlake::BottomLeftCorner) - sel->absolutePosition(KoFlake::BottomRightCorner);
+ edge = sel->absolutePosition(KoFlake::BottomLeft) - sel->absolutePosition(KoFlake::BottomRight);
angle = 180.0;
} else if (m_bottom) {
- edge = sel->absolutePosition(KoFlake::TopRightCorner) - sel->absolutePosition(KoFlake::TopLeftCorner);
+ edge = sel->absolutePosition(KoFlake::TopRight) - sel->absolutePosition(KoFlake::TopLeft);
angle = 0.0;
} else if (m_left) {
- edge = sel->absolutePosition(KoFlake::BottomLeftCorner) - sel->absolutePosition(KoFlake::TopLeftCorner);
+ edge = sel->absolutePosition(KoFlake::BottomLeft) - sel->absolutePosition(KoFlake::TopLeft);
angle = 90.0;
} else if (m_right) {
- edge = sel->absolutePosition(KoFlake::TopRightCorner) - sel->absolutePosition(KoFlake::BottomRightCorner);
+ edge = sel->absolutePosition(KoFlake::TopRight) - sel->absolutePosition(KoFlake::BottomRight);
angle = 270.0;
}
qreal currentAngle = atan2(edge.y(), edge.x()) / M_PI * 180;
m_initialSelectionAngle = currentAngle - angle;
- qDebug() << " PREsol.x=" << m_solidPoint.x() << " sol.y=" << m_solidPoint.y();
- m_solidPoint = tool->canvas()->shapeManager()->selection()->absoluteTransformation(0).map(m_solidPoint);
-
// use crossproduct of top edge and left edge of selection bounding rect
// to determine if the selection is mirrored
- QPointF top = sel->absolutePosition(KoFlake::TopRightCorner) - sel->absolutePosition(KoFlake::TopLeftCorner);
- QPointF left = sel->absolutePosition(KoFlake::BottomLeftCorner) - sel->absolutePosition(KoFlake::TopLeftCorner);
+ QPointF top = sel->absolutePosition(KoFlake::TopRight) - sel->absolutePosition(KoFlake::TopLeft);
+ QPointF left = sel->absolutePosition(KoFlake::BottomLeft) - sel->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_selectedShapes) {
shape->update();
shape->applyAbsoluteTransformation(applyMatrix);
shape->update();
}
- tool()->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(applyMatrix);
m_shearMatrix = matrix;
}
void ShapeShearStrategy::paint(QPainter &painter, const KoViewConverter &converter)
{
- SelectionDecorator decorator(KoFlake::NoHandle, true, false);
- decorator.setSelection(tool()->canvas()->shapeManager()->selection());
- decorator.setHandleRadius(handleRadius());
- decorator.paint(painter, converter);
+ Q_UNUSED(painter);
+ Q_UNUSED(converter);
}
KUndo2Command *ShapeShearStrategy::createCommand()
{
QList<QTransform> newTransforms;
Q_FOREACH (KoShape *shape, m_selectedShapes) {
newTransforms << shape->transformation();
}
KoShapeTransformCommand *cmd = new KoShapeTransformCommand(m_selectedShapes, m_oldTransforms, newTransforms);
cmd->setText(kundo2_i18n("Shear"));
- KoSelection *sel = tool()->canvas()->shapeManager()->selection();
- new SelectionTransformCommand(sel, m_initialSelectionMatrix, sel->transformation(), cmd);
return cmd;
}
diff --git a/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.h b/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.h
index 1172bb546f..5e655e2c53 100644
--- a/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.h
+++ b/plugins/tools/defaulttool/defaulttool/ShapeShearStrategy.h
@@ -1,72 +1,71 @@
/* This file is part of the KDE project
* 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.
*/
#ifndef SHAPESHEARSTRATEGY_H
#define SHAPESHEARSTRATEGY_H
#include <KoInteractionStrategy.h>
#include <KoFlake.h>
#include <QPointF>
#include <QSizeF>
#include <QTransform>
class KoToolBase;
class KoShape;
/**
* A strategy for the KoInteractionTool.
* This strategy is invoked when the user starts a shear of a selection of objects,
* the stategy will then shear the objects interactively and provide a command afterwards.
*/
class ShapeShearStrategy : public KoInteractionStrategy
{
public:
/**
* Constructor that starts to rotate the objects.
* @param tool the parent tool which controls this strategy
* @param clicked the initial point that the user depressed (in pt).
* @param direction the handle that was grabbed
*/
ShapeShearStrategy(KoToolBase *tool, const QPointF &clicked, KoFlake::SelectionHandle direction);
virtual ~ShapeShearStrategy() {}
void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers);
KUndo2Command *createCommand();
void finishInteraction(Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
}
virtual void paint(QPainter &painter, const KoViewConverter &converter);
private:
QPointF m_start;
QPointF m_solidPoint;
QSizeF m_initialSize;
bool m_top, m_left, m_bottom, m_right;
qreal m_initialSelectionAngle;
QTransform m_shearMatrix;
bool m_isMirrored;
QList<QTransform> m_oldTransforms;
- QTransform m_initialSelectionMatrix;
QList<KoShape *> m_selectedShapes;
};
#endif
diff --git a/plugins/tools/karbonplugins/tools/22-actions-gradient.png b/plugins/tools/karbonplugins/tools/22-actions-gradient.png
deleted file mode 100644
index 1f06958fbb..0000000000
Binary files a/plugins/tools/karbonplugins/tools/22-actions-gradient.png and /dev/null differ
diff --git a/plugins/tools/karbonplugins/tools/CMakeLists.txt b/plugins/tools/karbonplugins/tools/CMakeLists.txt
index 2bdf67690f..3f5610caee 100644
--- a/plugins/tools/karbonplugins/tools/CMakeLists.txt
+++ b/plugins/tools/karbonplugins/tools/CMakeLists.txt
@@ -1,50 +1,47 @@
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/filterEffectTool
)
########### next target ###############
set(karbon_tools_SOURCES
KarbonToolsPlugin.cpp
KarbonCursor.cpp
CalligraphyTool/KarbonCalligraphyTool.cpp
CalligraphyTool/KarbonCalligraphyOptionWidget.cpp
CalligraphyTool/KarbonCalligraphyToolFactory.cpp
CalligraphyTool/KarbonCalligraphicShape.cpp
CalligraphyTool/KarbonCalligraphicShapeFactory.cpp
CalligraphyTool/KarbonSimplifyPath.cpp
- KarbonGradientTool.cpp
- KarbonGradientToolFactory.cpp
- KarbonGradientEditStrategy.cpp
KarbonPatternTool.cpp
KarbonPatternToolFactory.cpp
KarbonPatternEditStrategy.cpp
filterEffectTool/KarbonFilterEffectsTool.cpp
filterEffectTool/KarbonFilterEffectsToolFactory.cpp
filterEffectTool/FilterEffectEditWidget.cpp
filterEffectTool/FilterEffectScene.cpp
filterEffectTool/FilterEffectSceneItems.cpp
filterEffectTool/FilterInputChangeCommand.cpp
filterEffectTool/FilterAddCommand.cpp
filterEffectTool/FilterRemoveCommand.cpp
filterEffectTool/FilterStackSetCommand.cpp
filterEffectTool/FilterRegionChangeCommand.cpp
filterEffectTool/FilterEffectResource.cpp
filterEffectTool/FilterResourceServerProvider.cpp
filterEffectTool/FilterRegionEditStrategy.cpp
KarbonPatternOptionsWidget.cpp
)
ki18n_wrap_ui(karbon_tools_SOURCES
filterEffectTool/FilterEffectEditWidget.ui
KarbonPatternOptionsWidget.ui
)
qt5_add_resources(karbon_tools_SOURCES karbontools.qrc)
add_library(krita_karbontools MODULE ${karbon_tools_SOURCES})
target_link_libraries(krita_karbontools kritaui kritawidgets KF5::Completion)
install(TARGETS krita_karbontools DESTINATION ${KRITA_PLUGIN_INSTALL_DIR})
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp
index f8a2cd0493..7529c187a1 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.cpp
@@ -1,419 +1,433 @@
/* 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;
KarbonCalligraphicShape::KarbonCalligraphicShape(qreal caps)
: m_lastWasFlip(false)
, m_caps(caps)
{
setShapeId(KoPathShapeId);
setFillRule(Qt::WindingFill);
setBackground(QSharedPointer<KoShapeBackground>(new KoColorBackground(QColor(Qt::black))));
- setStroke(0);
+ setStroke(KoShapeStrokeModelSP());
+}
+
+KarbonCalligraphicShape::KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs)
+ : KoParameterShape(new KoParameterShapePrivate(*rhs.d_func(), this)),
+ m_points(rhs.m_points),
+ m_lastWasFlip(rhs.m_lastWasFlip),
+ m_caps(rhs.m_caps)
+{
}
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);
QList<QPointF> handles = this->handles();
handles.append(p);
setHandles(handles);
m_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);
}
}
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;
}
if (m_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;
}
}
}
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);
// 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 fomed 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()));
}
return offset;
}
void KarbonCalligraphicShape::moveHandleAction(int handleId,
const QPointF &point,
Qt::KeyboardModifiers modifiers)
{
Q_UNUSED(modifiers);
m_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);
}
simplifyPath();
QList<QPointF> handles;
Q_FOREACH (KarbonCalligraphicPoint *p, m_points) {
handles.append(p->point());
}
setHandles(handles);
setPosition(pos);
}
void KarbonCalligraphicShape::simplifyPath()
{
if (m_points.count() < 2) {
return;
}
close();
// add final cap
addCap(m_points.count() - 2, m_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();
// 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;
KoPathPoint *newPoint = new KoPathPoint(this, p);
qreal angle = m_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) {
return;
}
QList<QPointF> points;
Q_FOREACH (KarbonCalligraphicPoint *p, m_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;
while (i != m_points.end() - 1) {
QPointF point = (*i)->point();
qreal width = (*i)->width();
qreal prevWidth = (*(i - 1))->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();
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);
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 ae94eb4eff..356dbcda67 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphicShape.h
@@ -1,147 +1,151 @@
/* 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>
#define KarbonCalligraphicShapeId "KarbonCalligraphicShape"
class KarbonCalligraphicPoint
{
public:
KarbonCalligraphicPoint(const QPointF &point, qreal angle, qreal width)
: m_point(point), m_angle(angle), m_width(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;
};
/*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();
+ KoShape* cloneShape() const override;
+
void appendPoint(const QPointF &p1, qreal angle, qreal width);
void appendPointToPath(const KarbonCalligraphicPoint &p);
// returns the bounding rect of whan needs to be repainted
// after new points are added
const QRectF lastPieceBoundingRect();
void setSize(const QSizeF &newSize);
//virtual QPointF normalize();
QPointF normalize();
void simplifyPath();
void simplifyGuidePath();
// reimplemented
virtual QString pathShapeId() const;
protected:
// reimplemented
void moveHandleAction(int handleId,
const QPointF &point,
Qt::KeyboardModifiers modifiers = Qt::NoModifier);
// reimplemented
void updatePath(const QSizeF &size);
private:
+ KarbonCalligraphicShape(const KarbonCalligraphicShape &rhs);
+
// auxiliary function that actually insererts 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;
};
#endif // KARBONCALLIGRAPHICSHAPE_H
diff --git a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
index 45f783d41b..1f88b06b35 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.cpp
@@ -1,513 +1,516 @@
/* 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 <KoCanvasResourceManager.h>
#include <KoColor.h>
#include <KoShapePaintingContext.h>
-#include <KoFillConfigWidget.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->shapeManager(), SIGNAL(selectionChanged()), SLOT(updateSelectedPath()));
+ 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.transform());
KoShapePaintingContext paintContext; //FIXME
m_shape->paint(painter, converter, 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);
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 - m_customAngle + 90) / 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
}
qDebug() << "using tilt" << m_angle;
if (event->x() == 0) {
m_angle = M_PI / 2;
return;
}
// y is inverted in qt painting
m_angle = std::atan(static_cast<double>(-event->yTilt() / event->xTilt())) + M_PI / 2;
} else {
m_angle = event->rotation() + M_PI / 2;
qDebug() << "using rotation" << m_angle;
}
}
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 avarage 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, const QSet<KoShape *> &)
+void KarbonCalligraphyTool::activate(ToolActivation activation, const QSet<KoShape*> &shapes)
{
+ KoToolBase::activate(activation, shapes);
+
useCursor(Qt::CrossCursor);
m_lastShape = 0;
}
void KarbonCalligraphyTool::deactivate()
{
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"));
- fillWidget->setCanvas(canvas());
- widgets.append(fillWidget);
+ //KoFillConfigWidget *fillWidget = new KoFillConfigWidget(0);
+ //fillWidget->setWindowTitle(i18n("Fill"));
+ //widgets.append(fillWidget);
KarbonCalligraphyOptionWidget *widget = new KarbonCalligraphyOptionWidget;
connect(widget, SIGNAL(usePathChanged(bool)),
this, SLOT(setUsePath(bool)));
connect(widget, SIGNAL(usePressureChanged(bool)),
this, SLOT(setUsePressure(bool)));
connect(widget, SIGNAL(useAngleChanged(bool)),
this, SLOT(setUseAngle(bool)));
connect(widget, SIGNAL(widthChanged(double)),
this, SLOT(setStrokeWidth(double)));
connect(widget, SIGNAL(thinningChanged(double)),
this, SLOT(setThinning(double)));
connect(widget, SIGNAL(angleChanged(int)),
this, SLOT(setAngle(int)));
connect(widget, SIGNAL(fixationChanged(double)),
this, SLOT(setFixation(double)));
connect(widget, SIGNAL(capsChanged(double)),
this, SLOT(setCaps(double)));
connect(widget, SIGNAL(massChanged(double)),
this, SLOT(setMass(double)));
connect(widget, SIGNAL(dragChanged(double)),
this, SLOT(setDrag(double)));
connect(this, SIGNAL(pathSelectedChanged(bool)),
widget, SLOT(setUsePathEnabled(bool)));
// add shortcuts
QAction *action = new QAction(i18n("Calligraphy: increase width"), this);
action->setShortcut(Qt::Key_Right);
connect(action, SIGNAL(triggered()), widget, SLOT(increaseWidth()));
addAction("calligraphy_increase_width", action);
action = new QAction(i18n("Calligraphy: decrease width"), this);
action->setShortcut(Qt::Key_Left);
connect(action, SIGNAL(triggered()), widget, SLOT(decreaseWidth()));
addAction("calligraphy_decrease_width", action);
action = new QAction(i18n("Calligraphy: increase angle"), this);
action->setShortcut(Qt::Key_Up);
connect(action, SIGNAL(triggered()), widget, SLOT(increaseAngle()));
addAction("calligraphy_increase_angle", action);
action = new QAction(i18n("Calligraphy: decrease angle"), this);
action->setShortcut(Qt::Key_Down);
connect(action, SIGNAL(triggered()), widget, SLOT(decreaseAngle()));
addAction("calligraphy_decrease_angle", action);
// sync all parameters with the loaded profile
widget->emitAll();
widget->setObjectName(i18n("Calligraphy"));
widget->setWindowTitle(i18n("Calligraphy"));
widgets.append(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/CalligraphyTool/KarbonCalligraphyTool.h b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h
index 50fd45f946..2b810a2862 100644
--- a/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h
+++ b/plugins/tools/karbonplugins/tools/CalligraphyTool/KarbonCalligraphyTool.h
@@ -1,110 +1,110 @@
/* 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 KARBONCALLIGRAPHYTOOL_H
#define KARBONCALLIGRAPHYTOOL_H
#include <KoToolBase.h>
#include <KoPathShape.h>
#include <QPointer>
class KoPathShape;
class KarbonCalligraphicShape;
class KarbonCalligraphyTool : public KoToolBase
{
Q_OBJECT
public:
explicit KarbonCalligraphyTool(KoCanvasBase *canvas);
~KarbonCalligraphyTool();
void paint(QPainter &painter, const KoViewConverter &converter);
void mousePressEvent(KoPointerEvent *event);
void mouseMoveEvent(KoPointerEvent *event);
void mouseReleaseEvent(KoPointerEvent *event);
QList<QPointer<QWidget> > createOptionWidgets();
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape *> &shapes);
void deactivate();
Q_SIGNALS:
void pathSelectedChanged(bool selection);
private Q_SLOTS:
void setUsePath(bool usePath);
void setUsePressure(bool usePressure);
void setUseAngle(bool useAngle);
void setStrokeWidth(double width);
void setThinning(double thinning);
void setAngle(int angle); // set theangle in degrees
void setFixation(double fixation);
void setCaps(double caps);
void setMass(double mass); // set the mass in user friendly format
void setDrag(double drag);
void updateSelectedPath();
private:
void addPoint(KoPointerEvent *event);
// auxiliary function that sets m_angle
void setAngle(KoPointerEvent *event);
// auxiliary functions to calculate the dynamic parameters
// returns the new point and sets speed to the speed
QPointF calculateNewPoint(const QPointF &mousePos, QPointF *speed);
qreal calculateWidth(qreal pressure);
qreal calculateAngle(const QPointF &oldSpeed, const QPointF &newSpeed);
QPointF m_lastPoint;
KarbonCalligraphicShape *m_shape;
// used to determine if the device supports tilt
bool m_deviceSupportsTilt;
bool m_usePath; // follow selected path
bool m_usePressure; // use tablet pressure
bool m_useAngle; // use tablet angle
qreal m_strokeWidth;
qreal m_lastWidth;
qreal m_customAngle; // angle set by the user
qreal m_angle; // angle to use, may use the device angle, in radians!!!
qreal m_fixation;
qreal m_thinning;
qreal m_caps;
qreal m_mass; // in raw format (not user friendly)
qreal m_drag; // from 0.0 to 1.0
KoPathShape *m_selectedPath;
QPainterPath m_selectedPathOutline;
qreal m_followPathPosition;
bool m_endOfPath;
QPointF m_lastMousePos;
bool m_isDrawing;
int m_pointCount;
// dynamic parameters
QPointF m_speed; // used as a vector
// last calligraphic shape drawn, if any
KarbonCalligraphicShape *m_lastShape;
};
#endif // KARBONCALLIGRAPHYTOOL_H
diff --git a/plugins/tools/karbonplugins/tools/KarbonGradientEditStrategy.cpp b/plugins/tools/karbonplugins/tools/KarbonGradientEditStrategy.cpp
deleted file mode 100644
index 19b246494f..0000000000
--- a/plugins/tools/karbonplugins/tools/KarbonGradientEditStrategy.cpp
+++ /dev/null
@@ -1,616 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007-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 "KarbonGradientEditStrategy.h"
-
-#include <KoFlake.h>
-#include <KoShape.h>
-#include <KoViewConverter.h>
-#include <KoShapeBackgroundCommand.h>
-#include <KoShapeStrokeCommand.h>
-#include <KoGradientBackground.h>
-#include <KoGradientHelper.h>
-
-#include <QBrush>
-#include <QGradient>
-#include <kundo2command.h>
-#include <QPainter>
-
-#include <math.h>
-
-int GradientStrategy::m_handleRadius = 3;
-int GradientStrategy::m_grabSensitivity = 3;
-
-const qreal stopDistance = 15.0;
-
-/// Returns scalar product of two given vectors
-qreal GradientStrategy::scalarProduct(const QPointF &p1, const QPointF &p2)
-{
- return p1.x() * p2.x() + p1.y() * p2.y();
-}
-
-GradientStrategy::GradientStrategy(KoShape *shape, const QGradient *gradient, Target target)
- : m_shape(shape)
- , m_editing(false)
- , m_target(target)
- , m_gradientLine(0, 1)
- , m_selection(None)
- , m_selectionIndex(0)
- , m_type(gradient->type())
-{
- if (m_target == Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(m_shape->background());
- if (fill) {
- m_matrix = fill->transform() * m_shape->absoluteTransformation(0);
- }
- } else {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(m_shape->stroke());
- if (stroke) {
- m_matrix = stroke->lineBrush().transform() * m_shape->absoluteTransformation(0);
- }
- }
- m_stops = gradient->stops();
-}
-
-void GradientStrategy::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) {
- if (m_target == Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(m_shape->background());
- if (fill) {
- m_oldBrush = QBrush(*fill->gradient());
- m_oldBrush.setTransform(fill->transform());
- }
- } else {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(m_shape->stroke());
- if (stroke) {
- m_oldStroke = *stroke;
- m_oldBrush = stroke->lineBrush();
- }
- }
- m_newBrush = m_oldBrush;
- }
-}
-
-bool GradientStrategy::hitHandle(const QPointF &mousePos, const KoViewConverter &converter, bool select)
-{
- QRectF roi = grabRect(converter);
-
- int handleIndex = 0;
- Q_FOREACH (const QPointF &handle, m_handles) {
- roi.moveCenter(m_matrix.map(handle));
- if (roi.contains(mousePos)) {
- if (select) {
- setSelection(Handle, handleIndex);
- }
- return true;
- }
- handleIndex++;
- }
-
- if (select) {
- setSelection(None);
- }
-
- return false;
-}
-
-bool GradientStrategy::hitLine(const QPointF &mousePos, const KoViewConverter &converter, bool select)
-{
- qreal maxDistance = converter.viewToDocumentX(grabSensitivity());
- if (mouseAtLineSegment(mousePos, maxDistance)) {
- m_lastMousePos = mousePos;
- if (select) {
- setSelection(Line);
- }
- return true;
- }
-
- if (select) {
- setSelection(None);
- }
-
- return false;
-}
-
-bool GradientStrategy::hitStop(const QPointF &mousePos, const KoViewConverter &converter, bool select)
-{
- QRectF roi = grabRect(converter);
-
- QList<StopHandle> handles = stopHandles(converter);
-
- int stopCount = m_stops.count();
- for (int i = 0; i < stopCount; ++i) {
- roi.moveCenter(handles[i].second);
- if (roi.contains(mousePos)) {
- if (select) {
- setSelection(Stop, i);
- }
- m_lastMousePos = mousePos;
- return true;
- }
- }
-
- if (select) {
- setSelection(None);
- }
-
- return false;
-}
-
-void GradientStrategy::paintHandle(QPainter &painter, const KoViewConverter &converter, const QPointF &position)
-{
- QRectF hr = handleRect(converter);
- hr.moveCenter(position);
- painter.drawRect(hr);
-}
-
-void GradientStrategy::paintStops(QPainter &painter, const KoViewConverter &converter)
-{
- painter.save();
-
- QRectF hr = handleRect(converter);
-
- QPen defPen = painter.pen();
- QList<StopHandle> handles = stopHandles(converter);
- int stopCount = m_stops.count();
- for (int i = 0; i < stopCount; ++i) {
- hr.moveCenter(handles[i].second);
-
- painter.setPen(defPen);
- painter.drawLine(handles[i].first, handles[i].second);
- painter.setBrush(m_stops[i].second);
- painter.setPen(invertedColor(m_stops[i].second));
- if (m_selection == Stop && m_selectionIndex == i) {
- QTransform m;
- m.translate(hr.center().x(), hr.center().y());
- m.rotate(45.0);
- m.translate(-hr.center().x(), -hr.center().y());
- painter.save();
- painter.setWorldTransform(m, true);
- painter.drawRect(hr);
- painter.restore();
- } else {
- painter.drawEllipse(hr);
- }
- }
-
- painter.restore();
-}
-
-void GradientStrategy::paint(QPainter &painter, const KoViewConverter &converter, bool selected)
-{
- m_shape->applyConversion(painter, converter);
-
- QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]);
- QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]);
-
- // draw the gradient line
- painter.drawLine(startPoint, stopPoint);
-
- // draw the gradient stops
- if (selected) {
- paintStops(painter, converter);
- }
-
- // draw the gradient handles
- Q_FOREACH (const QPointF &handle, m_handles) {
- paintHandle(painter, converter, m_matrix.map(handle));
- }
-}
-
-qreal GradientStrategy::projectToGradientLine(const QPointF &point)
-{
- QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]);
- QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]);
- QPointF diff = stopPoint - startPoint;
- qreal diffLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
- if (diffLength == 0.0f) {
- return 0.0f;
- }
- // project mouse position relative to stop position on gradient line
- qreal scalar = scalarProduct(point - startPoint, diff / diffLength);
- return scalar /= diffLength;
-}
-
-bool GradientStrategy::mouseAtLineSegment(const QPointF &mousePos, qreal maxDistance)
-{
- qreal scalar = projectToGradientLine(mousePos);
- if (scalar < 0.0 || scalar > 1.0) {
- return false;
- }
- // calculate vector between relative mouse position and projected mouse position
- QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]);
- QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]);
- QPointF distVec = startPoint + scalar * (stopPoint - startPoint) - mousePos;
- qreal dist = distVec.x() * distVec.x() + distVec.y() * distVec.y();
- if (dist > maxDistance * maxDistance) {
- return false;
- }
-
- return true;
-}
-
-void GradientStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers)
-{
- Q_UNUSED(modifiers)
-
- QTransform invMatrix = m_matrix.inverted();
- switch (m_selection) {
- case Line: {
- uint handleCount = m_handles.count();
- QPointF delta = invMatrix.map(mouseLocation) - invMatrix.map(m_lastMousePos);
- for (uint i = 0; i < handleCount; ++i) {
- m_handles[i] += delta;
- }
- m_lastMousePos = mouseLocation;
- break;
- }
- case Handle:
- m_handles[m_selectionIndex] = invMatrix.map(mouseLocation);
- break;
- case Stop: {
- qreal scalar = projectToGradientLine(mouseLocation);
- scalar = qMax(qreal(0.0), scalar);
- scalar = qMin(scalar, qreal(1.0));
- m_stops[m_selectionIndex].first = scalar;
- m_lastMousePos = mouseLocation;
- break;
- }
- default:
- return;
- }
-
- applyChanges();
-}
-
-bool GradientStrategy::handleDoubleClick(const QPointF &mouseLocation)
-{
- if (m_selection == Line) {
- // double click on gradient line inserts a new gradient stop
-
- qreal scalar = projectToGradientLine(mouseLocation);
- // calculate distance to gradient line
- QPointF startPoint = m_matrix.map(m_handles[m_gradientLine.first]);
- QPointF stopPoint = m_matrix.map(m_handles[m_gradientLine.second]);
- QPointF diff = stopPoint - startPoint;
- QPointF diffToLine = startPoint + scalar * diff - mouseLocation;
- qreal distToLine = diffToLine.x() * diffToLine.x() + diffToLine.y() * diffToLine.y();
- if (distToLine > m_handleRadius * m_handleRadius) {
- return false;
- }
-
- QColor newColor = KoGradientHelper::colorAt(scalar, m_stops);
- m_stops.append(QGradientStop(scalar, newColor));
- } else if (m_selection == Stop) {
- // double click on stop handle removes gradient stop
-
- // do not allow removing one of the last two stops
- if (m_stops.count() <= 2) {
- return false;
- }
- m_stops.remove(m_selectionIndex);
- setSelection(None);
- } else {
- return false;
- }
-
- applyChanges();
-
- return true;
-}
-
-void GradientStrategy::applyChanges()
-{
- m_newBrush = brush();
- if (m_target == Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(m_shape->background());
- if (fill) {
- fill->setGradient(*m_newBrush.gradient());
- fill->setTransform(m_newBrush.transform());
- }
- } else {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(m_shape->stroke());
- if (stroke) {
- stroke->setLineBrush(m_newBrush);
- }
- }
-}
-
-KUndo2Command *GradientStrategy::createCommand(KUndo2Command *parent)
-{
- if (m_newBrush == m_oldBrush) {
- return 0;
- }
-
- if (m_target == Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(m_shape->background());
- if (fill) {
- QSharedPointer<KoGradientBackground> newFill(new KoGradientBackground(*fill->gradient(), fill->transform()));
- fill->setGradient(*m_oldBrush.gradient());
- fill->setTransform(m_oldBrush.transform());
- return new KoShapeBackgroundCommand(m_shape, newFill, parent);
- }
- } else {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(m_shape->stroke());
- if (stroke) {
- *stroke = m_oldStroke;
- KoShapeStroke *newStroke = new KoShapeStroke(*stroke);
- newStroke->setLineBrush(m_newBrush);
- return new KoShapeStrokeCommand(m_shape, newStroke, parent);
- }
- }
-
- return 0;
-}
-
-QRectF GradientStrategy::boundingRect(const KoViewConverter &converter) const
-{
- // calculate the bounding rect of the handles
- QRectF bbox(m_matrix.map(m_handles[0]), QSize(0, 0));
- for (int i = 1; i < m_handles.count(); ++i) {
- QPointF handle = m_matrix.map(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()));
- }
- QList<StopHandle> handles = stopHandles(converter);
- Q_FOREACH (const StopHandle &stopHandle, handles) {
- QPointF handle = stopHandle.second;
- 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()));
- }
- // quick hack for gradient stops
- //bbox.adjust( -stopDistance, -stopDistance, stopDistance, stopDistance );
- return bbox.adjusted(-m_handleRadius, -m_handleRadius, m_handleRadius, m_handleRadius);
-}
-
-void GradientStrategy::repaint(const KoViewConverter &converter) const
-{
- QRectF gradientRect = boundingRect(converter).adjusted(-1, -1, 1, 1);
- m_shape->update(m_shape->documentToShape(gradientRect));
- m_shape->update();
-}
-
-const QGradient *GradientStrategy::gradient()
-{
- if (m_target == Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(m_shape->background());
- if (!fill) {
- return 0;
- }
- return fill->gradient();
- } else {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(m_shape->stroke());
- if (!stroke) {
- return 0;
- }
- return stroke->lineBrush().gradient();
- }
-}
-
-GradientStrategy::Target GradientStrategy::target() const
-{
- return m_target;
-}
-
-void GradientStrategy::startDrawing(const QPointF &mousePos)
-{
- QTransform invMatrix = m_matrix.inverted();
-
- int handleCount = m_handles.count();
- for (int handleId = 0; handleId < handleCount; ++handleId) {
- m_handles[handleId] = invMatrix.map(mousePos);
- }
-
- setSelection(Handle, handleCount - 1);
- setEditing(true);
-}
-
-bool GradientStrategy::hasSelection() const
-{
- return m_selection != None;
-}
-
-KoShape *GradientStrategy::shape()
-{
- return m_shape;
-}
-
-QGradient::Type GradientStrategy::type() const
-{
- return m_type;
-}
-
-void GradientStrategy::updateStops()
-{
- QBrush brush;
- if (m_target == Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(m_shape->background());
- if (fill) {
- m_stops = fill->gradient()->stops();
- }
- } else {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(m_shape->stroke());
- if (stroke) {
- brush = stroke->lineBrush();
- if (brush.gradient()) {
- m_stops = brush.gradient()->stops();
- }
- }
- }
-}
-
-int GradientStrategy::selectedColorStop() const
-{
- if (m_selection == Stop) {
- return m_selectionIndex;
- } else {
- return -1;
- }
-}
-
-GradientStrategy::SelectionType GradientStrategy::selection() const
-{
- return m_selection;
-}
-
-void GradientStrategy::setGradientLine(int start, int stop)
-{
- m_gradientLine = QPair<int, int>(start, stop);
-}
-
-QRectF GradientStrategy::handleRect(const KoViewConverter &converter) const
-{
- return converter.viewToDocument(QRectF(0, 0, 2 * m_handleRadius, 2 * m_handleRadius));
-}
-
-QRectF GradientStrategy::grabRect(const KoViewConverter &converter) const
-{
- return converter.viewToDocument(QRectF(0, 0, 2 * m_grabSensitivity, 2 * m_grabSensitivity));
-}
-
-void GradientStrategy::setSelection(SelectionType selection, int index)
-{
- m_selection = selection;
- m_selectionIndex = index;
-}
-
-QColor GradientStrategy::invertedColor(const QColor &color)
-{
- return QColor(255 - color.red(), 255 - color.green(), 255 - color.blue());
-}
-
-QList<GradientStrategy::StopHandle> GradientStrategy::stopHandles(const KoViewConverter &converter) const
-{
- // get the gradient line start and end point in document coordinates
- QPointF start = m_matrix.map(m_handles[m_gradientLine.first]);
- QPointF stop = m_matrix.map(m_handles[m_gradientLine.second]);
-
- // calculate orthogonal vector to the gradient line
- // using the cross product of the line vector and the negative z-axis
- QPointF diff = stop - start;
- QPointF ortho(-diff.y(), diff.x());
- qreal orthoLength = sqrt(ortho.x() * ortho.x() + ortho.y() * ortho.y());
- if (orthoLength == 0.0) {
- ortho = QPointF(stopDistance, 0.0f);
- } else {
- ortho *= stopDistance / orthoLength;
- }
-
- // make handles have always the same distance to the gradient line
- // independent of acual zooming
- ortho = converter.viewToDocument(ortho);
-
- QList<StopHandle> handles;
- Q_FOREACH (const QGradientStop &stop, m_stops) {
- QPointF base = start + stop.first * diff;
- handles.append(StopHandle(base, base + ortho));
- }
-
- return handles;
-}
-
-/////////////////////////////////////////////////////////////////
-// strategy implementations
-/////////////////////////////////////////////////////////////////
-LinearGradientStrategy::LinearGradientStrategy(KoShape *shape, const QLinearGradient *gradient, Target target)
- : GradientStrategy(shape, gradient, target)
-{
- Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode);
- QSizeF size(shape->size());
- m_handles.append(KoFlake::toAbsolute(gradient->start(), size));
- m_handles.append(KoFlake::toAbsolute(gradient->finalStop(), size));
-}
-
-QBrush LinearGradientStrategy::brush()
-{
- QSizeF size(m_shape->size());
- QLinearGradient gradient(KoFlake::toRelative(m_handles[start], size), KoFlake::toRelative(m_handles[stop], size));
- gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
- gradient.setStops(m_stops);
- gradient.setSpread(m_oldBrush.gradient()->spread());
- QBrush brush = QBrush(gradient);
- brush.setTransform(m_oldBrush.transform());
- return brush;
-}
-
-RadialGradientStrategy::RadialGradientStrategy(KoShape *shape, const QRadialGradient *gradient, Target target)
- : GradientStrategy(shape, gradient, target)
-{
- Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode);
- QSizeF size(shape->size());
- QPointF absoluteCenter(KoFlake::toAbsolute(gradient->center(), size));
- qreal radius = gradient->radius() * size.width();
-
- m_handles.append(absoluteCenter);
- m_handles.append(KoFlake::toAbsolute(gradient->focalPoint(), size));
- m_handles.append(absoluteCenter + QPointF(radius, 0));
- setGradientLine(0, 2);
-}
-
-QBrush RadialGradientStrategy::brush()
-{
- QSizeF size(m_shape->size());
- QPointF relativeCenter(KoFlake::toRelative(m_handles[center], size));
- QPointF d = KoFlake::toRelative(m_handles[radius], size) - relativeCenter;
- qreal r = sqrt(d.x() * d.x() + d.y() * d.y());
- QRadialGradient gradient(relativeCenter, r, KoFlake::toRelative(m_handles[focal], size));
- gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
- gradient.setStops(m_stops);
- gradient.setSpread(m_oldBrush.gradient()->spread());
- QBrush brush = QBrush(gradient);
- brush.setTransform(m_oldBrush.transform());
- return brush;
-}
-
-ConicalGradientStrategy::ConicalGradientStrategy(KoShape *shape, const QConicalGradient *gradient, Target target)
- : GradientStrategy(shape, gradient, target)
-{
- Q_ASSERT(gradient->coordinateMode() == QGradient::ObjectBoundingMode);
- QSizeF size(m_shape->size());
- qreal scale = 0.25 * (size.height() + size.width());
- qreal angle = gradient->angle() * M_PI / 180.0;
- QPointF center(KoFlake::toAbsolute(gradient->center(), size));
- m_handles.append(center);
- m_handles.append(center + scale * QPointF(cos(angle), -sin(angle)));
-}
-
-QBrush ConicalGradientStrategy::brush()
-{
- QPointF d = m_handles[direction] - m_handles[center];
- qreal angle = atan2(-d.y(), d.x()) / M_PI * 180.0;
- if (angle < 0.0) {
- angle += 360;
- }
- QConicalGradient gradient(KoFlake::toRelative(m_handles[center], m_shape->size()), angle);
- gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
- gradient.setStops(m_stops);
- gradient.setSpread(m_oldBrush.gradient()->spread());
- QBrush brush = QBrush(gradient);
- brush.setTransform(m_oldBrush.transform());
- return brush;
-}
diff --git a/plugins/tools/karbonplugins/tools/KarbonGradientEditStrategy.h b/plugins/tools/karbonplugins/tools/KarbonGradientEditStrategy.h
deleted file mode 100644
index 188492f64e..0000000000
--- a/plugins/tools/karbonplugins/tools/KarbonGradientEditStrategy.h
+++ /dev/null
@@ -1,226 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007-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 _KARBONGRADIENTEDITSTRATEGY_H_
-#define _KARBONGRADIENTEDITSTRATEGY_H_
-
-#include <QRectF>
-#include <QBrush>
-
-#include <KoShapeStroke.h>
-#include <KoGradientBackground.h>
-
-class QPainter;
-class KUndo2Command;
-class QLinearGradient;
-class QRadialGradient;
-class QConicalGradient;
-class KoShape;
-class KoViewConverter;
-
-/// The base class for gradient editing strategies
-class GradientStrategy
-{
-public:
- /// The different targets of the gradients
- enum Target { Fill, Stroke };
- /// The selection types
- enum SelectionType { None, Handle, Line, Stop };
-
- /// constructs new strategy on the specified shape and target
- explicit GradientStrategy(KoShape *shape, const QGradient *gradient, Target target);
-
- virtual ~GradientStrategy() {}
-
- /// painting of the gradient editing handles
- void paint(QPainter &painter, const KoViewConverter &converter, bool selected);
-
- /// selects handle at the given position
- bool hitHandle(const QPointF &mousePos, const KoViewConverter &converter, bool select);
-
- /// selects the gradient line at the given position
- bool hitLine(const QPointF &mousePos, const KoViewConverter &converter, bool select);
-
- /// selects the gradient stop at the given position
- bool hitStop(const QPointF &mousePos, const KoViewConverter &converter, bool select);
-
- /// mouse position handling for moving handles
- void handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers);
-
- /// mouse double click handling
- bool handleDoubleClick(const QPointF &mouseLocation);
-
- /// sets the strategy into editing mode
- void setEditing(bool on);
-
- /// checks if strategy is in editing mode
- bool isEditing() const
- {
- return m_editing;
- }
-
- /// create the command for changing the shapes background
- KUndo2Command *createCommand(KUndo2Command *parent);
-
- /// schedules a repaint of the shape and gradient handles
- void repaint(const KoViewConverter &converter) const;
-
- /// sets the handle radius in pixel used for painting the handles
- static void setHandleRadius(uint radius)
- {
- m_handleRadius = radius;
- }
-
- /// returns the actual handle radius in pixel
- static uint handleRadius()
- {
- return m_handleRadius;
- }
-
- /// Sets the grab sensitivity in pixel used for grabbing handles or lines
- static void setGrabSensitivity(int grabSensitivity)
- {
- m_grabSensitivity = grabSensitivity;
- }
-
- /// Returns the actual grab sensitivity in pixel
- static int grabSensitivity()
- {
- return m_grabSensitivity;
- }
-
- /// returns the gradient handles bounding rect
- QRectF boundingRect(const KoViewConverter &converter) const;
-
- /// returns the actual gradient
- const QGradient *gradient();
-
- /// Returns the gradient target
- Target target() const;
-
- /// Starts drawing the gradient at the given mouse position
- void startDrawing(const QPointF &mousePos);
-
- /// Returns if strategy has a selection
- bool hasSelection() const;
-
- /// Returns the shape associated with the gradient
- KoShape *shape();
-
- /// Returns the type of this gradient strategy
- QGradient::Type type() const;
-
- /// Triggers updating the gradient stops from the shape
- void updateStops();
-
- /// Returns the currently selected color stop index
- int selectedColorStop() const;
-
- /// Returns the actual selection type
- SelectionType selection() const;
-
-protected:
- /// Sets the actual selection
- void setSelection(SelectionType selection, int index = 0);
-
- /// paints a handle at the given position
- void paintHandle(QPainter &painter, const KoViewConverter &converter, const QPointF &position);
-
- /// paints the
- void paintStops(QPainter &painter, const KoViewConverter &converter);
-
- /// checks if given mouse position is on specified line segment
- bool mouseAtLineSegment(const QPointF &mousePos, qreal maxDistance);
-
- /// Sets the handle indices defining the gradient line
- void setGradientLine(int start, int stop);
-
- /// Returns the handle rect
- QRectF handleRect(const KoViewConverter &converter) const;
-
- /// Returns the grab rect
- QRectF grabRect(const KoViewConverter &converter) const;
-
- /// creates an updated brush from the actual data
- virtual QBrush brush() = 0;
-
- KoShape *m_shape; ///< the shape we are working on
- QBrush m_oldBrush; ///< the old background brush
- QBrush m_newBrush; ///< the new background brush
- QList<QPointF> m_handles; ///< the list of handles
- QGradientStops m_stops; ///< the gradient stops
- QTransform m_matrix; ///< matrix to map handle into document coordinate system
- KoShapeStroke m_oldStroke; ///< the old stroke
-private:
-
- qreal scalarProduct(const QPointF &p1, const QPointF &p2);
-
- typedef QPair<QPointF, QPointF> StopHandle;
- QColor invertedColor(const QColor &color);
- QList<StopHandle> stopHandles(const KoViewConverter &converter) const;
-
- /// Projects point onto gradient line returning the position on the line
- qreal projectToGradientLine(const QPointF &point);
-
- void applyChanges();
-
- static int m_handleRadius; ///< the handle radius for all gradient strategies
- static int m_grabSensitivity; ///< the grabbing sensitivity
-
- bool m_editing; /// the edit mode flag
- Target m_target; ///< the gradient target
- QPair<int, int> m_gradientLine; ///< the handle indices defining the gradient line
- QPointF m_lastMousePos; ///< last mouse position
- SelectionType m_selection; ///< the actual selection type
- int m_selectionIndex; ///< the actual selection index
- QGradient::Type m_type; ///< the gradient strategy type
-};
-
-/// Strategy for editing a linear gradient
-class LinearGradientStrategy : public GradientStrategy
-{
-public:
- LinearGradientStrategy(KoShape *shape, const QLinearGradient *gradient, Target target);
-private:
- virtual QBrush brush();
- enum Handles { start, stop };
-};
-
-/// Strategy for editing a radial gradient
-class RadialGradientStrategy : public GradientStrategy
-{
-public:
- RadialGradientStrategy(KoShape *shape, const QRadialGradient *gradient, Target target);
-private:
- virtual QBrush brush();
- enum Handles { center, focal, radius };
-};
-
-/// Strategy for editing a conical gradient
-class ConicalGradientStrategy : public GradientStrategy
-{
-public:
- ConicalGradientStrategy(KoShape *shape, const QConicalGradient *gradient, Target target);
-private:
- virtual QBrush brush();
- enum Handles { center, direction };
-};
-
-#endif // _KARBONGRADIENTEDITSTRATEGY_H_
-
diff --git a/plugins/tools/karbonplugins/tools/KarbonGradientTool.cpp b/plugins/tools/karbonplugins/tools/KarbonGradientTool.cpp
deleted file mode 100644
index 8c5286af2e..0000000000
--- a/plugins/tools/karbonplugins/tools/KarbonGradientTool.cpp
+++ /dev/null
@@ -1,609 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007-2008,2011 Jan Hambrecht <jaham@gmx.net>
- * Copyright (C) 2007,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 "KarbonGradientTool.h"
-#include "KarbonGradientEditStrategy.h"
-#include "KarbonCursor.h"
-
-#include <KoGradientEditWidget.h>
-
-#include <KoShape.h>
-#include <KoCanvasBase.h>
-#include <KoDocumentResourceManager.h>
-#include <KoCanvasResourceManager.h>
-#include <KoShapeManager.h>
-#include <KoViewConverter.h>
-#include <KoSelection.h>
-#include <KoPointerEvent.h>
-#include <KoShapeBackgroundCommand.h>
-#include <KoShapeStrokeCommand.h>
-#include <KoResourceServerProvider.h>
-#include <KoGradientBackground.h>
-#include <KoGradientHelper.h>
-#include <KoShapeController.h>
-#include <KoShapeBackground.h>
-#include <resources/KoResource.h>
-#include <KoResourceItemChooser.h>
-#include <KoResourceServerAdapter.h>
-
-#include <klocalizedstring.h>
-
-#include <QPainter>
-
-// helper function
-GradientStrategy *createStrategy(KoShape *shape, const QGradient *gradient, GradientStrategy::Target target)
-{
- if (!shape || ! gradient) {
- return 0;
- }
-
- if (gradient->type() == QGradient::LinearGradient) {
- return new LinearGradientStrategy(shape, static_cast<const QLinearGradient *>(gradient), target);
- } else if (gradient->type() == QGradient::RadialGradient) {
- return new RadialGradientStrategy(shape, static_cast<const QRadialGradient *>(gradient), target);
- } else if (gradient->type() == QGradient::ConicalGradient) {
- return new ConicalGradientStrategy(shape, static_cast<const QConicalGradient *>(gradient), target);
- } else {
- return 0;
- }
-}
-
-KarbonGradientTool::KarbonGradientTool(KoCanvasBase *canvas)
- : KoToolBase(canvas)
- , m_gradient(0)
- , m_currentStrategy(0)
- , m_hoverStrategy(0)
- , m_gradientWidget(0)
- , m_currentCmd(0)
- , m_oldSnapStrategies(0)
-{
-}
-
-KarbonGradientTool::~KarbonGradientTool()
-{
- delete m_gradient;
-}
-
-void KarbonGradientTool::paint(QPainter &painter, const KoViewConverter &converter)
-{
- painter.setBrush(Qt::green); //TODO make configurable
- painter.setPen(Qt::blue); //TODO make configurable
-
- Q_FOREACH (GradientStrategy *strategy, m_strategies) {
- bool current = (strategy == m_currentStrategy);
- painter.save();
- if (current) {
- painter.setBrush(Qt::red); //TODO make configurable
- }
- strategy->paint(painter, converter, current);
- painter.restore();
- }
-}
-
-void KarbonGradientTool::repaintDecorations()
-{
- Q_FOREACH (GradientStrategy *strategy, m_strategies) {
- canvas()->updateCanvas(strategy->boundingRect(*canvas()->viewConverter()));
- }
-}
-
-void KarbonGradientTool::mousePressEvent(KoPointerEvent *event)
-{
- Q_UNUSED(event);
- if (!m_gradient) {
- return;
- }
-
- // do we have a selected gradient ?
- if (m_currentStrategy) {
- // now select whatever we hit
- if (m_currentStrategy->hitHandle(event->point, *canvas()->viewConverter(), true) ||
- m_currentStrategy->hitStop(event->point, *canvas()->viewConverter(), true) ||
- m_currentStrategy->hitLine(event->point, *canvas()->viewConverter(), true)) {
- m_currentStrategy->setEditing(true);
- m_currentStrategy->repaint(*canvas()->viewConverter());
- return;
- }
- m_currentStrategy->repaint(*canvas()->viewConverter());
- }
- // are we hovering over a gradient ?
- if (m_hoverStrategy) {
- // now select whatever we hit
- if (m_hoverStrategy->hitHandle(event->point, *canvas()->viewConverter(), true) ||
- m_hoverStrategy->hitStop(event->point, *canvas()->viewConverter(), true) ||
- m_hoverStrategy->hitLine(event->point, *canvas()->viewConverter(), true)) {
- m_currentStrategy = m_hoverStrategy;
- m_hoverStrategy = 0;
- m_currentStrategy->setEditing(true);
- m_currentStrategy->repaint(*canvas()->viewConverter());
- return;
- }
- }
-
- qreal grabDist = canvas()->viewConverter()->viewToDocumentX(GradientStrategy::grabSensitivity());
- QRectF roi(QPointF(), QSizeF(grabDist, grabDist));
- roi.moveCenter(event->point);
- // check if we are on a shape without a gradient yet
- QList<KoShape *> shapes = canvas()->shapeManager()->shapesAt(roi);
- KoSelection *selection = canvas()->shapeManager()->selection();
-
- KoGradientEditWidget::GradientTarget target = m_gradientWidget->target();
-
- GradientStrategy *newStrategy = 0;
-
- Q_FOREACH (KoShape *shape, shapes) {
- if (!selection->isSelected(shape)) {
- continue;
- }
-
- if (target == KoGradientEditWidget::FillGradient) {
- // target is fill so check the background style
- if (!dynamic_cast<KoGradientBackground *>(shape->background().data())) {
- QSharedPointer<KoGradientBackground> fill(new KoGradientBackground(*m_gradient));
- m_currentCmd = new KoShapeBackgroundCommand(shape, fill);
- shape->setBackground(fill);
- newStrategy = createStrategy(shape, m_gradient, GradientStrategy::Fill);
- }
- } else {
- // target is stroke so check the stroke style
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(shape->stroke());
- if (!stroke) {
- stroke = new KoShapeStroke(1.0);
- stroke->setLineBrush(QBrush(*m_gradient));
- m_currentCmd = new KoShapeStrokeCommand(shape, stroke);
- shape->setStroke(stroke);
- newStrategy = createStrategy(shape, m_gradient, GradientStrategy::Stroke);
- break;
- } else {
- Qt::BrushStyle style = stroke->lineBrush().style();
- if (style < Qt::LinearGradientPattern || style > Qt::RadialGradientPattern) {
- KoShapeStroke *newStroke = new KoShapeStroke(*stroke);
- newStroke->setLineBrush(QBrush(*m_gradient));
- m_currentCmd = new KoShapeStrokeCommand(shape, newStroke);
- stroke->setLineBrush(QBrush(*m_gradient));
- newStrategy = createStrategy(shape, m_gradient, GradientStrategy::Stroke);
- break;
- }
- }
- }
- }
-
- if (newStrategy) {
- m_currentStrategy = newStrategy;
- m_strategies.insert(m_currentStrategy->shape(), m_currentStrategy);
- m_currentStrategy->startDrawing(event->point);
- }
-}
-
-void KarbonGradientTool::mouseMoveEvent(KoPointerEvent *event)
-{
- m_hoverStrategy = 0;
-
- // do we have a selected gradient ?
- if (m_currentStrategy) {
- // are we editing the current selected gradient ?
- if (m_currentStrategy->isEditing()) {
- QPointF mousePos = event->point;
- // snap to bounding box when moving handles
- if (m_currentStrategy->selection() == GradientStrategy::Handle) {
- mousePos = canvas()->snapGuide()->snap(mousePos, event->modifiers());
- }
-
- m_currentStrategy->repaint(*canvas()->viewConverter());
- m_currentStrategy->handleMouseMove(mousePos, event->modifiers());
- m_currentStrategy->repaint(*canvas()->viewConverter());
- return;
- }
- // are we on a gradient handle ?
- else if (m_currentStrategy->hitHandle(event->point, *canvas()->viewConverter(), false)) {
- m_currentStrategy->repaint(*canvas()->viewConverter());
- useCursor(KarbonCursor::needleMoveArrow());
- emit statusTextChanged(i18n("Drag to move gradient position."));
- return;
- }
- // are we on a gradient stop handle ?
- else if (m_currentStrategy->hitStop(event->point, *canvas()->viewConverter(), false)) {
- m_currentStrategy->repaint(*canvas()->viewConverter());
- useCursor(KarbonCursor::needleMoveArrow());
- const QGradient *g = m_currentStrategy->gradient();
- if (g && g->stops().count() > 2) {
- emit statusTextChanged(i18n("Drag to move color stop. Double click to remove color stop."));
- } else {
- emit statusTextChanged(i18n("Drag to move color stop."));
- }
- return;
- }
- // are we near the gradient line ?
- else if (m_currentStrategy->hitLine(event->point, *canvas()->viewConverter(), false)) {
- m_currentStrategy->repaint(*canvas()->viewConverter());
- useCursor(Qt::SizeAllCursor);
- emit statusTextChanged(i18n("Drag to move gradient position. Double click to insert color stop."));
- return;
- }
- }
-
- // we have no selected gradient, so lets check if at least
- // the mouse hovers over another gradient (handles and line)
-
- // first check if we hit any handles
- Q_FOREACH (GradientStrategy *strategy, m_strategies) {
- if (strategy->hitHandle(event->point, *canvas()->viewConverter(), false)) {
- m_hoverStrategy = strategy;
- useCursor(KarbonCursor::needleMoveArrow());
- return;
- }
- }
- // now check if we hit any lines
- Q_FOREACH (GradientStrategy *strategy, m_strategies) {
- if (strategy->hitLine(event->point, *canvas()->viewConverter(), false)) {
- m_hoverStrategy = strategy;
- useCursor(Qt::SizeAllCursor);
- return;
- }
- }
-
- useCursor(KarbonCursor::needleArrow());
-}
-
-void KarbonGradientTool::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) {
- KUndo2Command *cmd = m_currentStrategy->createCommand(m_currentCmd);
- canvas()->addCommand(m_currentCmd ? m_currentCmd : cmd);
- m_currentCmd = 0;
- if (m_gradientWidget) {
- m_gradientWidget->setGradient(*m_currentStrategy->gradient());
- if (m_currentStrategy->target() == GradientStrategy::Fill) {
- m_gradientWidget->setTarget(KoGradientEditWidget::FillGradient);
- } else {
- m_gradientWidget->setTarget(KoGradientEditWidget::StrokeGradient);
- }
- m_gradientWidget->setStopIndex(m_currentStrategy->selectedColorStop());
- }
- m_currentStrategy->setEditing(false);
- }
-}
-
-void KarbonGradientTool::mouseDoubleClickEvent(KoPointerEvent *event)
-{
- if (!m_currentStrategy) {
- return;
- }
-
- canvas()->updateCanvas(m_currentStrategy->boundingRect(*canvas()->viewConverter()));
-
- if (m_currentStrategy->handleDoubleClick(event->point)) {
- KUndo2Command *cmd = m_currentStrategy->createCommand(m_currentCmd);
- canvas()->addCommand(m_currentCmd ? m_currentCmd : cmd);
- m_currentCmd = 0;
- if (m_gradientWidget) {
- m_gradientWidget->setGradient(*m_currentStrategy->gradient());
- if (m_currentStrategy->target() == GradientStrategy::Fill) {
- m_gradientWidget->setTarget(KoGradientEditWidget::FillGradient);
- } else {
- m_gradientWidget->setTarget(KoGradientEditWidget::StrokeGradient);
- }
- }
- canvas()->updateCanvas(m_currentStrategy->boundingRect(*canvas()->viewConverter()));
- }
-}
-
-void KarbonGradientTool::keyPressEvent(QKeyEvent *event)
-{
- switch (event->key()) {
- case Qt::Key_I: {
- uint handleRadius = GradientStrategy::handleRadius();
- if (event->modifiers() & Qt::ControlModifier) {
- handleRadius--;
- } else {
- handleRadius++;
- }
- // XXX: this is a KoDocumentResourceController feature, but shouldn't it be canvas?
- canvas()->shapeController()->resourceManager()->setHandleRadius(handleRadius);
- }
- break;
- default:
- event->ignore();
- return;
- }
- event->accept();
-}
-
-void KarbonGradientTool::activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes)
-{
- Q_UNUSED(toolActivation);
- if (shapes.isEmpty()) {
- emit done();
- return;
- }
-
- initialize();
- repaintDecorations();
-
- useCursor(KarbonCursor::needleArrow());
-
- // save old enabled snap strategies, set bounding box snap strategy
- m_oldSnapStrategies = canvas()->snapGuide()->enabledSnapStrategies();
- canvas()->snapGuide()->enableSnapStrategies(KoSnapGuide::BoundingBoxSnapping);
- canvas()->snapGuide()->reset();
-}
-
-void KarbonGradientTool::initialize()
-{
- if (m_currentStrategy && m_currentStrategy->isEditing()) {
- return;
- }
-
- m_hoverStrategy = 0;
-
- QList<KoShape *> selectedShapes = canvas()->shapeManager()->selection()->selectedShapes();
- QList<GradientStrategy *> strategies = m_strategies.values();
- // remove all gradient strategies no longer applicable
- Q_FOREACH (GradientStrategy *strategy, strategies) {
- // is this gradient shape still selected ?
- if (!selectedShapes.contains(strategy->shape()) || ! strategy->shape()->isEditable()) {
- m_strategies.remove(strategy->shape(), strategy);
- delete strategy;
- if (m_currentStrategy == strategy) {
- m_currentStrategy = 0;
- }
- continue;
- }
- // is the gradient a fill gradient but shape has no fill gradient anymore ?
- if (strategy->target() == GradientStrategy::Fill) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(strategy->shape()->background());
- if (!fill || ! fill->gradient() || fill->gradient()->type() != strategy->type()) {
- // delete the gradient
- m_strategies.remove(strategy->shape(), strategy);
- delete strategy;
- if (m_currentStrategy == strategy) {
- m_currentStrategy = 0;
- }
- continue;
- }
- }
- // is the gradient a stroke gradient but shape has no stroke gradient anymore ?
- if (strategy->target() == GradientStrategy::Stroke) {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(strategy->shape()->stroke());
- if (!stroke || ! stroke->lineBrush().gradient() || stroke->lineBrush().gradient()->type() != strategy->type()) {
- // delete the gradient
- m_strategies.remove(strategy->shape(), strategy);
- delete strategy;
- if (m_currentStrategy == strategy) {
- m_currentStrategy = 0;
- }
- continue;
- }
- }
- }
-
- // now create new strategies if needed
- Q_FOREACH (KoShape *shape, selectedShapes) {
- if (!shape->isEditable()) {
- continue;
- }
-
- bool strokeExists = false;
- bool fillExists = false;
- // check which gradient strategies exist for this shape
- Q_FOREACH (GradientStrategy *strategy, m_strategies.values(shape)) {
- if (strategy->target() == GradientStrategy::Fill) {
- fillExists = true;
- strategy->updateStops();
- }
- if (strategy->target() == GradientStrategy::Stroke) {
- strokeExists = true;
- strategy->updateStops();
- }
- }
-
- if (!fillExists) {
- QSharedPointer<KoGradientBackground> fill = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
- if (fill) {
- GradientStrategy *fillStrategy = createStrategy(shape, fill->gradient(), GradientStrategy::Fill);
- if (fillStrategy) {
- m_strategies.insert(shape, fillStrategy);
- fillStrategy->repaint(*canvas()->viewConverter());
- }
- }
- }
-
- if (!strokeExists) {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(shape->stroke());
- if (stroke) {
- GradientStrategy *strokeStrategy = createStrategy(shape, stroke->lineBrush().gradient(), GradientStrategy::Stroke);
- if (strokeStrategy) {
- m_strategies.insert(shape, strokeStrategy);
- strokeStrategy->repaint(*canvas()->viewConverter());
- }
- }
- }
- }
-
- if (m_strategies.count() == 0) {
- // create a default gradient
- m_gradient = new QLinearGradient(QPointF(0, 0), QPointF(1, 1));
- m_gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
- m_gradient->setColorAt(0.0, Qt::white);
- m_gradient->setColorAt(1.0, Qt::green);
- return;
- }
- // automatically select strategy when editing single shape
- if (selectedShapes.count() == 1 && m_strategies.count()) {
- if (!m_currentStrategy || ! m_strategies.values().contains(m_currentStrategy)) {
- m_currentStrategy = m_strategies.values().first();
- }
- }
-
- delete m_gradient;
- GradientStrategy *strategy = m_currentStrategy ? m_currentStrategy : m_strategies.values().first();
- GradientStrategy::setHandleRadius(handleRadius());
- GradientStrategy::setGrabSensitivity(grabSensitivity());
- m_gradient = KoFlake::cloneGradient(strategy->gradient());
- if (m_gradientWidget) {
- if (m_gradient) {
- m_gradientWidget->setGradient(*m_gradient);
- }
- if (strategy->target() == GradientStrategy::Fill) {
- m_gradientWidget->setTarget(KoGradientEditWidget::FillGradient);
- } else {
- m_gradientWidget->setTarget(KoGradientEditWidget::StrokeGradient);
- }
- }
-}
-
-void KarbonGradientTool::deactivate()
-{
- delete m_gradient;
- m_gradient = 0;
-
- m_currentStrategy = 0;
- m_hoverStrategy = 0;
- qDeleteAll(m_strategies);
- m_strategies.clear();
-
- // restore previously set snap strategies
- canvas()->snapGuide()->enableSnapStrategies(m_oldSnapStrategies);
- canvas()->snapGuide()->reset();
-}
-
-void KarbonGradientTool::documentResourceChanged(int key, const QVariant &res)
-{
- switch (key) {
- case KoDocumentResourceManager::HandleRadius:
- Q_FOREACH (GradientStrategy *strategy, m_strategies) {
- strategy->repaint(*canvas()->viewConverter());
- }
- GradientStrategy::setHandleRadius(res.toUInt());
- Q_FOREACH (GradientStrategy *strategy, m_strategies) {
- strategy->repaint(*canvas()->viewConverter());
- }
- break;
- case KoDocumentResourceManager::GrabSensitivity:
- GradientStrategy::setGrabSensitivity(res.toUInt());
- break;
- default:
- return;
- }
-}
-
-QList<QPointer<QWidget> > KarbonGradientTool::createOptionWidgets()
-{
- m_gradientWidget = new KoGradientEditWidget();
- if (m_gradient) {
- m_gradientWidget->setGradient(*m_gradient);
- }
-
- connect(m_gradientWidget, SIGNAL(changed()), this, SLOT(gradientChanged()));
-
- KoResourceServer<KoAbstractGradient> *rserver = KoResourceServerProvider::instance()->gradientServer();
- QSharedPointer<KoAbstractResourceServerAdapter> adapter(new KoResourceServerAdapter<KoAbstractGradient>(rserver));
- KoResourceItemChooser *chooser = new KoResourceItemChooser(adapter, m_gradientWidget);
- chooser->setObjectName("KarbonGradientChooser");
- chooser->setColumnCount(1);
-
- connect(chooser, SIGNAL(resourceSelected(KoResource*)),
- this, SLOT(gradientSelected(KoResource*)));
-
- QList<QPointer<QWidget> > widgets;
- m_gradientWidget->setWindowTitle(i18n("Edit Gradient"));
- widgets.append(m_gradientWidget);
- chooser->setWindowTitle(i18n("Predefined Gradients"));
- widgets.append(chooser);
-
- return widgets;
-}
-
-void KarbonGradientTool::gradientSelected(KoResource *resource)
-{
- if (!resource) {
- return;
- }
-
- KoAbstractGradient *gradient = dynamic_cast<KoAbstractGradient *>(resource);
- if (!gradient) {
- return;
- }
-
- QGradient *newGradient = gradient->toQGradient();
- if (newGradient) {
- m_gradientWidget->setGradient(*newGradient);
- gradientChanged();
- delete newGradient;
- }
-}
-
-void KarbonGradientTool::gradientChanged()
-{
- QList<KoShape *> selectedShapes = canvas()->shapeManager()->selection()->selectedShapes();
-
- QGradient::Type type = m_gradientWidget->type();
- QGradient::Spread spread = m_gradientWidget->spread();
- QGradientStops stops = m_gradientWidget->stops();
-
- if (m_gradientWidget->target() == KoGradientEditWidget::FillGradient) {
- QList<QSharedPointer<KoShapeBackground> > newFills;
- Q_FOREACH (KoShape *shape, selectedShapes) {
- QSharedPointer<KoGradientBackground> newFill;
- QSharedPointer<KoGradientBackground> oldFill = qSharedPointerDynamicCast<KoGradientBackground>(shape->background());
- if (oldFill) {
- QGradient *g = KoGradientHelper::convertGradient(oldFill->gradient(), type);
- g->setSpread(spread);
- g->setStops(stops);
- newFill = QSharedPointer<KoGradientBackground>(new KoGradientBackground(g, oldFill->transform()));
- } else {
- QGradient *g = KoGradientHelper::defaultGradient(type, spread, stops);
- newFill = QSharedPointer<KoGradientBackground>(new KoGradientBackground(g));
- }
- newFills.append(newFill);
- }
- canvas()->addCommand(new KoShapeBackgroundCommand(selectedShapes, newFills));
- } else {
- QList<KoShapeStrokeModel *> newStrokes;
- Q_FOREACH (KoShape *shape, selectedShapes) {
- KoShapeStroke *stroke = dynamic_cast<KoShapeStroke *>(shape->stroke());
- KoShapeStroke *newStroke = 0;
- if (stroke) {
- newStroke = new KoShapeStroke(*stroke);
- } else {
- newStroke = new KoShapeStroke(1.0);
- }
- QBrush newGradient;
- if (newStroke->lineBrush().gradient()) {
- QGradient *g = KoGradientHelper::convertGradient(newStroke->lineBrush().gradient(), type);
- g->setSpread(spread);
- g->setStops(stops);
- newGradient = QBrush(*g);
- delete g;
- } else {
- QGradient *g = KoGradientHelper::defaultGradient(type, spread, stops);
- newGradient = QBrush(*g);
- delete g;
- }
- newStroke->setLineBrush(newGradient);
- newStrokes.append(newStroke);
- }
- canvas()->addCommand(new KoShapeStrokeCommand(selectedShapes, newStrokes));
- }
- initialize();
-}
-
diff --git a/plugins/tools/karbonplugins/tools/KarbonGradientTool.h b/plugins/tools/karbonplugins/tools/KarbonGradientTool.h
deleted file mode 100644
index a3fa3c4542..0000000000
--- a/plugins/tools/karbonplugins/tools/KarbonGradientTool.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007-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 KARBONGRADIENTTOOL_H
-#define KARBONGRADIENTTOOL_H
-
-#include <KoToolBase.h>
-#include <KoSnapGuide.h>
-#include <QGradient>
-#include <QMultiMap>
-
-class GradientStrategy;
-class KoGradientEditWidget;
-class KUndo2Command;
-class KoShape;
-class KoResource;
-
-/**
- * A tool for editing gradient backgrounds of shapes.
- * The gradients can be edited by moving gradient
- * handles directly on the canvas.
- */
-class KarbonGradientTool : public KoToolBase
-{
- Q_OBJECT
-public:
- explicit KarbonGradientTool(KoCanvasBase *canvas);
- ~KarbonGradientTool();
-
- virtual void paint(QPainter &painter, const KoViewConverter &converter);
- virtual void repaintDecorations();
-
- virtual void mousePressEvent(KoPointerEvent *event);
- virtual void mouseMoveEvent(KoPointerEvent *event);
- virtual void mouseReleaseEvent(KoPointerEvent *event);
- virtual void mouseDoubleClickEvent(KoPointerEvent *event);
- virtual void keyPressEvent(QKeyEvent *event);
-
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
- virtual void deactivate();
-
-public Q_SLOTS:
- virtual void documentResourceChanged(int key, const QVariant &res);
-
-protected:
- /// reimplemented from KoToolBase
- virtual QList<QPointer<QWidget> > createOptionWidgets();
-
-private Q_SLOTS:
- void initialize();
- void gradientChanged();
- void gradientSelected(KoResource *);
-private:
- QGradient *m_gradient;
- QMultiMap<KoShape *, GradientStrategy *> m_strategies; ///< the list of gradient strategies
- GradientStrategy *m_currentStrategy; ///< the current editing strategy
- GradientStrategy *m_hoverStrategy; ///< the strategy the mouse hovers over
- KoGradientEditWidget *m_gradientWidget;
- KUndo2Command *m_currentCmd;
- KoSnapGuide::Strategies m_oldSnapStrategies; ///< the previously enables snap strategies
-};
-
-#endif // KARBONGRADIENTTOOL_H
diff --git a/plugins/tools/karbonplugins/tools/KarbonGradientToolFactory.cpp b/plugins/tools/karbonplugins/tools/KarbonGradientToolFactory.cpp
deleted file mode 100644
index 53a9e17d7c..0000000000
--- a/plugins/tools/karbonplugins/tools/KarbonGradientToolFactory.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include "KarbonGradientToolFactory.h"
-#include "KarbonGradientTool.h"
-
-#include <KoIcon.h>
-#include <klocalizedstring.h>
-#include <QDebug>
-
-KarbonGradientToolFactory::KarbonGradientToolFactory()
- : KoToolFactoryBase("KarbonGradientTool")
-{
- setToolTip(i18n("Gradient editing"));
- setSection(mainToolType());
- setIconName(koIconNameCStr("format-fill-color"));
- // or probably rather "fill-gradient", please request that icon on TechBase
- setPriority(7);
-}
-
-KarbonGradientToolFactory::~KarbonGradientToolFactory()
-{
-}
-
-KoToolBase *KarbonGradientToolFactory::createTool(KoCanvasBase *canvas)
-{
- return new KarbonGradientTool(canvas);
-}
diff --git a/plugins/tools/karbonplugins/tools/KarbonGradientToolFactory.h b/plugins/tools/karbonplugins/tools/KarbonGradientToolFactory.h
deleted file mode 100644
index 17ed5a0652..0000000000
--- a/plugins/tools/karbonplugins/tools/KarbonGradientToolFactory.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/* 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 _KARBONGRADIENTTOOLFACTORY_H_
-#define _KARBONGRADIENTTOOLFACTORY_H_
-
-#include <KoToolFactoryBase.h>
-
-class KarbonGradientToolFactory : public KoToolFactoryBase
-{
-public:
- KarbonGradientToolFactory();
- ~KarbonGradientToolFactory();
-
- KoToolBase *createTool(KoCanvasBase *canvas);
-};
-
-#endif // _KARBONGRADIENTTOOLFACTORY_H_
diff --git a/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp b/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp
index e2ef28a793..6071618d0b 100644
--- a/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp
+++ b/plugins/tools/karbonplugins/tools/KarbonPatternTool.cpp
@@ -1,367 +1,370 @@
/* 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 <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
// 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()->isEditable()) {
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->isEditable()) {
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 toolActivation, const QSet<KoShape *> &shapes)
+void KarbonPatternTool::activate(ToolActivation activation, const QSet<KoShape *> &shapes)
{
- Q_UNUSED(toolActivation);
+ 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/KarbonPatternTool.h b/plugins/tools/karbonplugins/tools/KarbonPatternTool.h
index add6440add..7c83f6f1c8 100644
--- a/plugins/tools/karbonplugins/tools/KarbonPatternTool.h
+++ b/plugins/tools/karbonplugins/tools/KarbonPatternTool.h
@@ -1,68 +1,68 @@
/* 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.
*/
#ifndef _KARBONPATTERNTOOL_H_
#define _KARBONPATTERNTOOL_H_
#include <KoToolBase.h>
#include <QMap>
class QPainter;
class KoResource;
class KarbonPatternEditStrategyBase;
class KarbonPatternOptionsWidget;
class KoShape;
class KarbonPatternTool : public KoToolBase
{
Q_OBJECT
public:
explicit KarbonPatternTool(KoCanvasBase *canvas);
~KarbonPatternTool();
void paint(QPainter &painter, const KoViewConverter &converter);
void repaintDecorations();
void mousePressEvent(KoPointerEvent *event);
void mouseMoveEvent(KoPointerEvent *event);
void mouseReleaseEvent(KoPointerEvent *event);
void keyPressEvent(QKeyEvent *event);
- virtual void activate(ToolActivation toolActivation, const QSet<KoShape *> &shapes);
+ virtual void activate(ToolActivation activation, const QSet<KoShape *> &shapes);
void deactivate();
public Q_SLOTS:
virtual void documentResourceChanged(int key, const QVariant &res);
protected:
virtual QList<QPointer<QWidget> > createOptionWidgets();
private Q_SLOTS:
void patternSelected(KoResource *resource);
void initialize();
/// updates options widget from selected pattern
void updateOptionsWidget();
void patternChanged();
private:
QMap<KoShape *, KarbonPatternEditStrategyBase *> m_strategies; ///< the list of editing strategies, one for each shape
KarbonPatternEditStrategyBase *m_currentStrategy; ///< the current editing strategy
KarbonPatternOptionsWidget *m_optionsWidget;
};
#endif // _KARBONPATTERNTOOL_H_
diff --git a/plugins/tools/karbonplugins/tools/KarbonToolsPlugin.cpp b/plugins/tools/karbonplugins/tools/KarbonToolsPlugin.cpp
index a57c00573d..9e48d217cf 100644
--- a/plugins/tools/karbonplugins/tools/KarbonToolsPlugin.cpp
+++ b/plugins/tools/karbonplugins/tools/KarbonToolsPlugin.cpp
@@ -1,45 +1,43 @@
/* This file is part of the KDE project
* Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KarbonToolsPlugin.h"
#include "CalligraphyTool/KarbonCalligraphyToolFactory.h"
#include "CalligraphyTool/KarbonCalligraphicShapeFactory.h"
-#include "KarbonGradientToolFactory.h"
#include "KarbonPatternToolFactory.h"
#include "KarbonFilterEffectsToolFactory.h"
#include <KoToolRegistry.h>
#include <KoShapeRegistry.h>
#include <kpluginfactory.h>
#include <kpluginloader.h>
K_PLUGIN_FACTORY_WITH_JSON(KarbonToolsPluginFactory, "karbon_tools.json", registerPlugin<KarbonToolsPlugin>();)
KarbonToolsPlugin::KarbonToolsPlugin(QObject *parent, const QVariantList &)
: QObject(parent)
{
KoToolRegistry::instance()->add(new KarbonCalligraphyToolFactory());
- KoToolRegistry::instance()->add(new KarbonGradientToolFactory());
- KoToolRegistry::instance()->add(new KarbonPatternToolFactory());
- KoToolRegistry::instance()->add(new KarbonFilterEffectsToolFactory());
+ //KoToolRegistry::instance()->add(new KarbonPatternToolFactory());
+ //KoToolRegistry::instance()->add(new KarbonFilterEffectsToolFactory());
KoShapeRegistry::instance()->add(new KarbonCalligraphicShapeFactory());
}
#include <KarbonToolsPlugin.moc>
diff --git a/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp b/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp
index 2804161e05..5a92a591e0 100644
--- a/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp
+++ b/plugins/tools/karbonplugins/tools/filterEffectTool/KarbonFilterEffectsTool.cpp
@@ -1,554 +1,554 @@
/* 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 "KoShapeManager.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();
// 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->shapeManager(), SIGNAL(selectionChanged()),
+ connect(canvas->selectedShapesProxy(), SIGNAL(selectionChanged()),
this, SLOT(selectionChanged()));
- connect(canvas->shapeManager(), SIGNAL(selectionContentChanged()),
+ 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);
// 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()->shapeManager()->selection()->firstSelectedShape(KoFlake::TopLevelSelection);
+ 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()->shapeManager()->selection()->firstSelectedShape(KoFlake::TopLevelSelection);
+ 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("%");
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("%");
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("%");
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("%");
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/karbonplugins/tools/karbontools.qrc b/plugins/tools/karbonplugins/tools/karbontools.qrc
index 262292346b..b2323305b3 100644
--- a/plugins/tools/karbonplugins/tools/karbontools.qrc
+++ b/plugins/tools/karbonplugins/tools/karbontools.qrc
@@ -1,9 +1,7 @@
-<!DOCTYPE RCC>
-<RCC version="1.0">
- <qresource>
+<RCC>
+ <qresource prefix="/">
<file alias="calligraphy.png">22-actions-calligraphy.png</file>
- <file alias="gradient.png">22-actions-gradient.png</file>
<file alias="pattern.png">22-actions-pattern.png</file>
<file alias="tool_imageeffects.png">32-actions-tool_imageeffects.png</file>
</qresource>
</RCC>
diff --git a/plugins/tools/selectiontools/kis_tool_select_outline.cc b/plugins/tools/selectiontools/kis_tool_select_outline.cc
index fa8d5656a9..571b55f3e6 100644
--- a/plugins/tools/selectiontools/kis_tool_select_outline.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_outline.cc
@@ -1,265 +1,264 @@
/*
* kis_tool_select_freehand.h - part of Krayon^WKrita
*
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_select_outline.h"
#include <QApplication>
#include <QPainter>
#include <QWidget>
#include <QPainterPath>
#include <QLayout>
#include <QVBoxLayout>
#include <kis_debug.h>
#include <klocalizedstring.h>
#include <KoPointerEvent.h>
#include <KoShapeController.h>
#include <KoPathShape.h>
#include <KoColorSpace.h>
#include <KoCompositeOp.h>
#include <KoViewConverter.h>
#include <kis_layer.h>
#include <kis_selection_options.h>
#include <kis_cursor.h>
#include <kis_image.h>
#include "kis_painter.h"
#include <brushengine/kis_paintop_registry.h>
#include "canvas/kis_canvas2.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include "kis_algebra_2d.h"
#define FEEDBACK_LINE_WIDTH 2
KisToolSelectOutline::KisToolSelectOutline(KoCanvasBase * canvas)
: KisToolSelect(canvas,
KisCursor::load("tool_outline_selection_cursor.png", 5, 5),
i18n("Outline Selection")),
m_continuedMode(false)
{
connect(&m_widgetHelper, &KisSelectionToolConfigWidgetHelper::selectionActionChanged,
this, &KisToolSelectOutline::setSelectionAction);
}
KisToolSelectOutline::~KisToolSelectOutline()
{
}
void KisToolSelectOutline::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Control) {
m_continuedMode = true;
}
KisToolSelect::keyPressEvent(event);
}
void KisToolSelectOutline::keyReleaseEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Control ||
!(event->modifiers() & Qt::ControlModifier)) {
m_continuedMode = false;
if (mode() != PAINT_MODE && !m_points.isEmpty()) {
finishSelectionAction();
}
}
KisToolSelect::keyReleaseEvent(event);
}
void KisToolSelectOutline::mouseMoveEvent(KoPointerEvent *event)
{
m_lastCursorPos = convertToPixelCoord(event);
if (m_continuedMode && mode() != PAINT_MODE) {
updateContinuedMode();
}
}
void KisToolSelectOutline::beginPrimaryAction(KoPointerEvent *event)
{
KisToolSelectBase::beginPrimaryAction(event);
if (!selectionEditable()) {
event->ignore();
return;
}
setMode(KisTool::PAINT_MODE);
if (m_continuedMode && !m_points.isEmpty()) {
m_paintPath.lineTo(pixelToView(convertToPixelCoord(event)));
} else {
m_paintPath.moveTo(pixelToView(convertToPixelCoord(event)));
}
m_points.append(convertToPixelCoord(event));
}
void KisToolSelectOutline::continuePrimaryAction(KoPointerEvent *event)
{
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
KisToolSelectBase::continuePrimaryAction(event);
QPointF point = convertToPixelCoord(event);
m_paintPath.lineTo(pixelToView(point));
m_points.append(point);
updateFeedback();
}
void KisToolSelectOutline::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
KisToolSelectBase::endPrimaryAction(event);
setMode(KisTool::HOVER_MODE);
if (!m_continuedMode) {
finishSelectionAction();
}
}
void KisToolSelectOutline::finishSelectionAction()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
kisCanvas->updateCanvas();
QRectF boundingViewRect =
pixelToView(KisAlgebra2D::accumulateBounds(m_points));
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Outline"));
- if (!helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction()) &&
- m_points.count() > 2) {
-
+ if (m_points.count() > 2 &&
+ !helper.tryDeselectCurrentSelection(boundingViewRect, selectionAction())) {
QApplication::setOverrideCursor(KisCursor::waitCursor());
if (selectionMode() == PIXEL_SELECTION) {
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisPainter painter(tmpSel);
painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
painter.setPaintOpPreset(currentPaintOpPreset(), currentNode(), currentImage());
painter.setAntiAliasPolygonFill(antiAliasSelection());
painter.setFillStyle(KisPainter::FillStyleForegroundColor);
painter.setStrokeStyle(KisPainter::StrokeStyleNone);
painter.paintPolygon(m_points);
QPainterPath cache;
cache.addPolygon(m_points);
cache.closeSubpath();
tmpSel->setOutlineCache(cache);
helper.selectPixelSelection(tmpSel, selectionAction());
} else {
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
QTransform resolutionMatrix;
resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
path->moveTo(resolutionMatrix.map(m_points[0]));
for (int i = 1; i < m_points.count(); i++)
path->lineTo(resolutionMatrix.map(m_points[i]));
path->close();
path->normalize();
helper.addSelectionShape(path);
}
QApplication::restoreOverrideCursor();
}
m_points.clear();
m_paintPath = QPainterPath();
}
void KisToolSelectOutline::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if ((mode() == KisTool::PAINT_MODE || m_continuedMode) &&
!m_points.isEmpty()) {
QPainterPath outline = m_paintPath;
if (m_continuedMode && mode() != KisTool::PAINT_MODE) {
outline.lineTo(pixelToView(m_lastCursorPos));
}
paintToolOutline(&gc, outline);
}
}
void KisToolSelectOutline::updateFeedback()
{
if (m_points.count() > 1) {
qint32 lastPointIndex = m_points.count() - 1;
QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_points[lastPointIndex]).normalized();
updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
updateCanvasPixelRect(updateRect);
}
}
void KisToolSelectOutline::updateContinuedMode()
{
if (!m_points.isEmpty()) {
qint32 lastPointIndex = m_points.count() - 1;
QRectF updateRect = QRectF(m_points[lastPointIndex - 1], m_lastCursorPos).normalized();
updateRect = kisGrowRect(updateRect, FEEDBACK_LINE_WIDTH);
updateCanvasPixelRect(updateRect);
}
}
void KisToolSelectOutline::deactivate()
{
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
KIS_ASSERT_RECOVER_RETURN(kisCanvas);
kisCanvas->updateCanvas();
m_continuedMode = false;
KisTool::deactivate();
}
void KisToolSelectOutline::setSelectionAction(int action)
{
changeSelectionAction(action);
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_path.cc b/plugins/tools/selectiontools/kis_tool_select_path.cc
index 23ecc10b51..fa0dd2d5af 100644
--- a/plugins/tools/selectiontools/kis_tool_select_path.cc
+++ b/plugins/tools/selectiontools/kis_tool_select_path.cc
@@ -1,167 +1,173 @@
/*
* 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_tool_select_path.h"
#include <KoPathShape.h>
#include "kis_cursor.h"
#include "kis_image.h"
#include "kis_painter.h"
#include "kis_selection_options.h"
#include "kis_canvas_resource_provider.h"
#include "kis_canvas2.h"
#include "kis_pixel_selection.h"
#include "kis_selection_tool_helper.h"
#include <KisView.h>
KisToolSelectPath::KisToolSelectPath(KoCanvasBase * canvas)
: KisToolSelectBase<KisDelegatedSelectPathWrapper>(canvas,
KisCursor::load("tool_polygonal_selection_cursor.png", 6, 6),
i18n("Select path"),
(KisTool*) (new __KisToolSelectPathLocalTool(canvas, this)))
{
}
void KisToolSelectPath::requestStrokeEnd()
{
localTool()->endPathWithoutLastPoint();
}
void KisToolSelectPath::requestStrokeCancellation()
{
localTool()->cancelPath();
}
void KisToolSelectPath::mousePressEvent(KoPointerEvent* event)
{
if (!selectionEditable()) return;
DelegatedSelectPathTool::mousePressEvent(event);
}
// Install an event filter to catch right-click events.
// This code is duplicated in kis_tool_path.cc
bool KisToolSelectPath::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
if (event->type() == QEvent::MouseButtonPress ||
- event->type() == QEvent::MouseButtonDblClick) {
+ event->type() == QEvent::MouseButtonDblClick) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::RightButton) {
localTool()->removeLastPoint();
return true;
}
} else if (event->type() == QEvent::TabletPress) {
QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
if (tabletEvent->button() == Qt::RightButton) {
localTool()->removeLastPoint();
return true;
}
}
return false;
}
QList<QPointer<QWidget> > KisToolSelectPath::createOptionWidgets()
{
QList<QPointer<QWidget> > widgetsList =
DelegatedSelectPathTool::createOptionWidgets();
- return widgetsList;
+ QList<QPointer<QWidget> > filteredWidgets;
+ Q_FOREACH (QWidget* widget, widgetsList) {
+ if (widget->objectName() != "Stroke widget") {
+ filteredWidgets.push_back(widget);
+ }
+ }
+ return filteredWidgets;
}
void KisToolSelectPath::setAlternateSelectionAction(SelectionAction action)
{
// We will turn off the ability to change the selection in the middle of drawing a path.
if (!m_localTool->listeningToModifiers()) {
KisToolSelectBase<KisDelegatedSelectPathWrapper>::setAlternateSelectionAction(action);
}
}
bool KisDelegatedSelectPathWrapper::listeningToModifiers() {
return m_localTool->listeningToModifiers();
}
void KisDelegatedSelectPathWrapper::beginPrimaryAction(KoPointerEvent *event) {
mousePressEvent(event);
}
void KisDelegatedSelectPathWrapper::continuePrimaryAction(KoPointerEvent *event){
mouseMoveEvent(event);
}
void KisDelegatedSelectPathWrapper::endPrimaryAction(KoPointerEvent *event) {
mouseReleaseEvent(event);
}
__KisToolSelectPathLocalTool::__KisToolSelectPathLocalTool(KoCanvasBase * canvas, KisToolSelectPath* parentTool)
: KoCreatePathTool(canvas), m_selectionTool(parentTool)
{
}
void __KisToolSelectPathLocalTool::paintPath(KoPathShape &pathShape, QPainter &painter, const KoViewConverter &converter)
{
Q_UNUSED(converter);
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
if (!kisCanvas)
return;
QTransform matrix;
matrix.scale(kisCanvas->image()->xRes(), kisCanvas->image()->yRes());
matrix.translate(pathShape.position().x(), pathShape.position().y());
m_selectionTool->paintToolOutline(&painter, m_selectionTool->pixelToView(matrix.map(pathShape.outline())));
}
void __KisToolSelectPathLocalTool::addPathShape(KoPathShape* pathShape)
{
pathShape->normalize();
pathShape->close();
KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
if (!kisCanvas)
return;
KisImageWSP image = kisCanvas->image();
KisSelectionToolHelper helper(kisCanvas, kundo2_i18n("Select by Bezier Curve"));
if (m_selectionTool->selectionMode() == PIXEL_SELECTION) {
KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
KisPainter painter(tmpSel);
painter.setPaintColor(KoColor(Qt::black, tmpSel->colorSpace()));
painter.setFillStyle(KisPainter::FillStyleForegroundColor);
painter.setAntiAliasPolygonFill(m_selectionTool->antiAliasSelection());
painter.setStrokeStyle(KisPainter::StrokeStyleNone);
QTransform matrix;
matrix.scale(image->xRes(), image->yRes());
matrix.translate(pathShape->position().x(), pathShape->position().y());
QPainterPath path = matrix.map(pathShape->outline());
painter.fillPainterPath(path);
tmpSel->setOutlineCache(path);
helper.selectPixelSelection(tmpSel, m_selectionTool->selectionAction());
delete pathShape;
} else {
helper.addSelectionShape(pathShape);
}
}
diff --git a/plugins/tools/selectiontools/kis_tool_select_path.h b/plugins/tools/selectiontools/kis_tool_select_path.h
index f321c52cf1..261fbed4ba 100644
--- a/plugins/tools/selectiontools/kis_tool_select_path.h
+++ b/plugins/tools/selectiontools/kis_tool_select_path.h
@@ -1,107 +1,107 @@
/*
* Copyright (c) 2007 Sven Langkamp <sven.langkamp@gmail.com>
* Copyright (c) 2015 Michael Abrahams <miabraha@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_TOOL_SELECT_PATH_H_
#define KIS_TOOL_SELECT_PATH_H_
#include <KoCreatePathTool.h>
#include <KoToolFactoryBase.h>
#include "kis_tool_select_base.h"
#include "kis_delegated_tool.h"
#include <kis_icon.h>
class KoCanvasBase;
class KisToolSelectPath;
class __KisToolSelectPathLocalTool : public KoCreatePathTool {
public:
__KisToolSelectPathLocalTool(KoCanvasBase * canvas, KisToolSelectPath* parentTool);
virtual void paintPath(KoPathShape &path, QPainter &painter, const KoViewConverter &converter);
virtual void addPathShape(KoPathShape* pathShape);
using KoCreatePathTool::createOptionWidgets;
using KoCreatePathTool::endPathWithoutLastPoint;
using KoCreatePathTool::endPath;
using KoCreatePathTool::cancelPath;
using KoCreatePathTool::removeLastPoint;
private:
KisToolSelectPath* const m_selectionTool;
};
typedef KisDelegatedTool<KisTool, __KisToolSelectPathLocalTool,
- DeselectShapesActivationPolicy> DelegatedSelectPathTool;
+DeselectShapesActivationPolicy> DelegatedSelectPathTool;
struct KisDelegatedSelectPathWrapper : public DelegatedSelectPathTool {
KisDelegatedSelectPathWrapper(KoCanvasBase *canvas,
const QCursor &cursor,
KisTool* delegateTool)
: DelegatedSelectPathTool(canvas, cursor, (__KisToolSelectPathLocalTool*) delegateTool)
{
}
bool listeningToModifiers();
// If an event is explicitly forwarded only as an action (e.g. shift-click is captured by "change size")
// we will receive a primary action but no mousePressEvent. Thus these events must be explicitly forwarded.
void beginPrimaryAction(KoPointerEvent *event);
void continuePrimaryAction(KoPointerEvent *event);
void endPrimaryAction(KoPointerEvent *event);
};
class KisToolSelectPath : public KisToolSelectBase<KisDelegatedSelectPathWrapper>
{
Q_OBJECT
public:
KisToolSelectPath(KoCanvasBase * canvas);
void mousePressEvent(KoPointerEvent* event);
bool eventFilter(QObject *obj, QEvent *event);
protected:
void requestStrokeCancellation();
void requestStrokeEnd();
void setAlternateSelectionAction(SelectionAction action);
friend class __KisToolSelectPathLocalTool;
QList<QPointer<QWidget> > createOptionWidgets();
};
class KisToolSelectPathFactory : public KoToolFactoryBase
{
public:
KisToolSelectPathFactory()
: KoToolFactoryBase("KisToolSelectPath") {
setToolTip(i18n("Bezier Curve Selection Tool"));
setSection(TOOL_TYPE_SELECTION);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
setIconName(koIconNameCStr("tool_path_selection"));
setPriority(6);
}
virtual ~KisToolSelectPathFactory() {}
virtual KoToolBase * createTool(KoCanvasBase *canvas) {
return new KisToolSelectPath(canvas);
}
};
#endif // KIS_TOOL_SELECT_PATH_H_
diff --git a/plugins/tools/tool_polygon/kis_tool_polygon.cc b/plugins/tools/tool_polygon/kis_tool_polygon.cc
index 3ba0ea21b3..7188db2ea3 100644
--- a/plugins/tools/tool_polygon/kis_tool_polygon.cc
+++ b/plugins/tools/tool_polygon/kis_tool_polygon.cc
@@ -1,94 +1,94 @@
/*
* kis_tool_polygon.cc -- part of Krita
*
* Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
* Copyright (c) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* Copyright (C) 2010 Boudewijn Rempt <boud@kogmbh.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_polygon.h"
#include <KoPointerEvent.h>
#include <KoCanvasBase.h>
#include <KoPathShape.h>
#include <KoShapeStroke.h>
#include <brushengine/kis_paintop_registry.h>
#include "kis_figure_painting_tool_helper.h"
#include <recorder/kis_action_recorder.h>
#include <recorder/kis_recorded_path_paint_action.h>
#include <recorder/kis_node_query_path.h>
#include <kis_system_locker.h>
KisToolPolygon::KisToolPolygon(KoCanvasBase *canvas)
: KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polygon_cursor.png", 6, 6))
{
setObjectName("tool_polygon");
setSupportOutline(true);
}
KisToolPolygon::~KisToolPolygon()
{
}
void KisToolPolygon::resetCursorStyle()
{
KisToolPolylineBase::resetCursorStyle();
overrideCursorIfNotEditable();
}
void KisToolPolygon::finishPolyline(const QVector<QPointF>& points)
{
if (image()) {
KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset());
setupPaintAction(&linePaintAction);
linePaintAction.addPolyLine(points.toList());
linePaintAction.addLine(KisPaintInformation(points.last()), KisPaintInformation(points.first()));
image()->actionRecorder()->addAction(linePaintAction);
}
if (!currentNode()->inherits("KisShapeLayer")) {
KisSystemLocker locker(currentNode());
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polygon"),
image(),
currentNode(),
canvas()->resourceManager(),
strokeStyle(),
fillStyle());
helper.paintPolygon(points);
} else {
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
QTransform resolutionMatrix;
resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
path->moveTo(resolutionMatrix.map(points[0]));
for (int i = 1; i < points.count(); i++)
path->lineTo(resolutionMatrix.map(points[i]));
path->close();
path->normalize();
- KoShapeStroke* border = new KoShapeStroke(1.0, currentFgColor().toQColor());
+ KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor()));
path->setStroke(border);
addShape(path);
}
}
diff --git a/plugins/tools/tool_polyline/kis_tool_polyline.cc b/plugins/tools/tool_polyline/kis_tool_polyline.cc
index 1da0023f5e..801c8f80ab 100644
--- a/plugins/tools/tool_polyline/kis_tool_polyline.cc
+++ b/plugins/tools/tool_polyline/kis_tool_polyline.cc
@@ -1,98 +1,98 @@
/*
* Copyright (c) 2004 Michael Thaler <michael.thaler@physik.tu-muenchen.de>
* Copyright (c) 2008 Boudewijn Rempt <boud@valdyas.org>
* Copyright (c) 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_tool_polyline.h"
#include <QVector>
#include <KoCanvasBase.h>
#include <KoPathShape.h>
#include <KoShapeStroke.h>
#include <brushengine/kis_paintop_preset.h>
#include "kis_figure_painting_tool_helper.h"
#include <recorder/kis_action_recorder.h>
#include <recorder/kis_recorded_path_paint_action.h>
#include <recorder/kis_node_query_path.h>
#include <kis_system_locker.h>
KisToolPolyline::KisToolPolyline(KoCanvasBase * canvas)
: KisToolPolylineBase(canvas, KisToolPolylineBase::PAINT, KisCursor::load("tool_polyline_cursor.png", 6, 6))
{
setObjectName("tool_polyline");
setSupportOutline(true);
}
KisToolPolyline::~KisToolPolyline()
{
}
void KisToolPolyline::resetCursorStyle()
{
KisToolPolylineBase::resetCursorStyle();
overrideCursorIfNotEditable();
}
QWidget* KisToolPolyline::createOptionWidget()
{
// there are no options there
return KisTool::createOptionWidget();
}
void KisToolPolyline::finishPolyline(const QVector<QPointF>& points)
{
if (image()) {
KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset());
setupPaintAction(&linePaintAction);
linePaintAction.addPolyLine(points.toList());
image()->actionRecorder()->addAction(linePaintAction);
}
if (!currentNode()->inherits("KisShapeLayer")) {
KisSystemLocker locker(currentNode());
KisFigurePaintingToolHelper helper(kundo2_i18n("Draw Polyline"),
image(),
currentNode(),
canvas()->resourceManager(),
strokeStyle(),
fillStyle());
helper.paintPolyline(points);
} else {
KoPathShape* path = new KoPathShape();
path->setShapeId(KoPathShapeId);
QTransform resolutionMatrix;
resolutionMatrix.scale(1 / currentImage()->xRes(), 1 / currentImage()->yRes());
path->moveTo(resolutionMatrix.map(points[0]));
for (int i = 1; i < points.count(); i++)
path->lineTo(resolutionMatrix.map(points[i]));
path->normalize();
- KoShapeStroke* border = new KoShapeStroke(1.0, currentFgColor().toQColor());
+ KoShapeStrokeSP border(new KoShapeStroke(currentStrokeWidth(), currentFgColor().toQColor()));
path->setStroke(border);
addShape(path);
}
notifyModified();
}
diff --git a/plugins/tools/tool_transform2/kis_cage_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_cage_transform_strategy.cpp
index a961b1c11e..df39a447fb 100644
--- a/plugins/tools/tool_transform2/kis_cage_transform_strategy.cpp
+++ b/plugins/tools/tool_transform2/kis_cage_transform_strategy.cpp
@@ -1,99 +1,99 @@
/*
* 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_cage_transform_strategy.h"
#include "tool_transform_args.h"
#include <QPointF>
#include <QPainter>
-#include "krita_utils.h"
+#include "kis_painting_tweaks.h"
#include "kis_cursor.h"
#include <kis_cage_transform_worker.h>
struct KisCageTransformStrategy::Private
{
Private(KisCageTransformStrategy *_q)
: q(_q)
{
}
KisCageTransformStrategy * const q;
};
KisCageTransformStrategy::KisCageTransformStrategy(const KisCoordinatesConverter *converter,
ToolTransformArgs &currentArgs,
TransformTransactionProperties &transaction)
: KisWarpTransformStrategy(converter, currentArgs, transaction),
m_d(new Private(this))
{
overrideDrawingItems(true, false, true);
setCloseOnStartPointClick(true);
setClipOriginalPointsPosition(false);
}
KisCageTransformStrategy::~KisCageTransformStrategy()
{
}
void KisCageTransformStrategy::drawConnectionLines(QPainter &gc,
const QVector<QPointF> &origPoints,
const QVector<QPointF> &transfPoints,
bool isEditingPoints)
{
const int numPoints = origPoints.size();
if (numPoints <= 1) return;
QPen antsPen;
QPen outlinePen;
- KritaUtils::initAntsPen(&antsPen, &outlinePen);
+ KisPaintingTweaks::initAntsPen(&antsPen, &outlinePen);
const int iterateLimit = isEditingPoints ? numPoints : numPoints + 1;
for (int i = 1; i < iterateLimit; ++i) {
int idx = i % numPoints;
int prevIdx = (i - 1) % numPoints;
gc.setPen(outlinePen);
gc.drawLine(transfPoints[prevIdx], transfPoints[idx]);
gc.setPen(antsPen);
gc.drawLine(transfPoints[prevIdx], transfPoints[idx]);
}
}
QImage KisCageTransformStrategy::calculateTransformedImage(ToolTransformArgs &currentArgs,
const QImage &srcImage,
const QVector<QPointF> &origPoints,
const QVector<QPointF> &transfPoints,
const QPointF &srcOffset,
QPointF *dstOffset)
{
Q_UNUSED(currentArgs);
KisCageTransformWorker worker(srcImage,
srcOffset,
origPoints,
0,
16);
worker.prepareTransform();
worker.setTransformedCage(transfPoints);
return worker.runOnQImage(dstOffset);
}
diff --git a/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp
index f1b7b61d48..99924a23c0 100644
--- a/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp
+++ b/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp
@@ -1,722 +1,722 @@
/*
* 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_free_transform_strategy.h"
#include <QPointF>
#include <QPainter>
#include <QMatrix4x4>
#include <KoResourcePaths.h>
#include "kis_coordinates_converter.h"
#include "tool_transform_args.h"
#include "transform_transaction_properties.h"
#include "krita_utils.h"
#include "kis_cursor.h"
#include "kis_transform_utils.h"
#include "kis_free_transform_strategy_gsl_helpers.h"
enum StrokeFunction {
ROTATE = 0,
MOVE,
RIGHTSCALE,
TOPRIGHTSCALE,
TOPSCALE,
TOPLEFTSCALE,
LEFTSCALE,
BOTTOMLEFTSCALE,
BOTTOMSCALE,
BOTTOMRIGHTSCALE,
BOTTOMSHEAR,
RIGHTSHEAR,
TOPSHEAR,
LEFTSHEAR,
MOVECENTER,
PERSPECTIVE
};
struct KisFreeTransformStrategy::Private
{
Private(KisFreeTransformStrategy *_q,
const KisCoordinatesConverter *_converter,
ToolTransformArgs &_currentArgs,
TransformTransactionProperties &_transaction)
: q(_q),
converter(_converter),
currentArgs(_currentArgs),
transaction(_transaction),
imageTooBig(false)
{
scaleCursors[0] = KisCursor::sizeHorCursor();
scaleCursors[1] = KisCursor::sizeFDiagCursor();
scaleCursors[2] = KisCursor::sizeVerCursor();
scaleCursors[3] = KisCursor::sizeBDiagCursor();
scaleCursors[4] = KisCursor::sizeHorCursor();
scaleCursors[5] = KisCursor::sizeFDiagCursor();
scaleCursors[6] = KisCursor::sizeVerCursor();
scaleCursors[7] = KisCursor::sizeBDiagCursor();
shearCursorPixmap.load(":/shear_cursor.png");
}
KisFreeTransformStrategy *q;
/// standard members ///
const KisCoordinatesConverter *converter;
//////
ToolTransformArgs &currentArgs;
//////
TransformTransactionProperties &transaction;
QTransform thumbToImageTransform;
QImage originalImage;
QTransform paintingTransform;
QPointF paintingOffset;
QTransform handlesTransform;
/// custom members ///
StrokeFunction function;
struct HandlePoints {
QPointF topLeft;
QPointF topMiddle;
QPointF topRight;
QPointF middleLeft;
QPointF rotationCenter;
QPointF middleRight;
QPointF bottomLeft;
QPointF bottomMiddle;
QPointF bottomRight;
};
HandlePoints transformedHandles;
QTransform transform;
QCursor scaleCursors[8]; // cursors for the 8 directions
QPixmap shearCursorPixmap;
bool imageTooBig;
ToolTransformArgs clickArgs;
QPointF clickPos;
QCursor getScaleCursor(const QPointF &handlePt);
QCursor getShearCursor(const QPointF &start, const QPointF &end);
void recalculateTransformations();
void recalculateTransformedHandles();
};
KisFreeTransformStrategy::KisFreeTransformStrategy(const KisCoordinatesConverter *converter,
KoSnapGuide *snapGuide,
ToolTransformArgs &currentArgs,
TransformTransactionProperties &transaction)
: KisSimplifiedActionPolicyStrategy(converter, snapGuide),
m_d(new Private(this, converter, currentArgs, transaction))
{
}
KisFreeTransformStrategy::~KisFreeTransformStrategy()
{
}
void KisFreeTransformStrategy::Private::recalculateTransformedHandles()
{
transformedHandles.topLeft = transform.map(transaction.originalTopLeft());
transformedHandles.topMiddle = transform.map(transaction.originalMiddleTop());
transformedHandles.topRight = transform.map(transaction.originalTopRight());
transformedHandles.middleLeft = transform.map(transaction.originalMiddleLeft());
transformedHandles.rotationCenter = transform.map(currentArgs.originalCenter() + currentArgs.rotationCenterOffset());
transformedHandles.middleRight = transform.map(transaction.originalMiddleRight());
transformedHandles.bottomLeft = transform.map(transaction.originalBottomLeft());
transformedHandles.bottomMiddle = transform.map(transaction.originalMiddleBottom());
transformedHandles.bottomRight = transform.map(transaction.originalBottomRight());
}
void KisFreeTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive)
{
- if (perspectiveModifierActive) {
+ if (perspectiveModifierActive && !m_d->transaction.shouldAvoidPerspectiveTransform()) {
m_d->function = PERSPECTIVE;
return;
}
QPolygonF transformedPolygon = m_d->transform.map(QPolygonF(m_d->transaction.originalRect()));
qreal handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
qreal rotationHandleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
StrokeFunction defaultFunction =
transformedPolygon.containsPoint(mousePos, Qt::OddEvenFill) ? MOVE : ROTATE;
KisTransformUtils::HandleChooser<StrokeFunction>
handleChooser(mousePos, defaultFunction);
handleChooser.addFunction(m_d->transformedHandles.topMiddle,
handleRadius, TOPSCALE);
handleChooser.addFunction(m_d->transformedHandles.topRight,
handleRadius, TOPRIGHTSCALE);
handleChooser.addFunction(m_d->transformedHandles.middleRight,
handleRadius, RIGHTSCALE);
handleChooser.addFunction(m_d->transformedHandles.bottomRight,
handleRadius, BOTTOMRIGHTSCALE);
handleChooser.addFunction(m_d->transformedHandles.bottomMiddle,
handleRadius, BOTTOMSCALE);
handleChooser.addFunction(m_d->transformedHandles.bottomLeft,
handleRadius, BOTTOMLEFTSCALE);
handleChooser.addFunction(m_d->transformedHandles.middleLeft,
handleRadius, LEFTSCALE);
handleChooser.addFunction(m_d->transformedHandles.topLeft,
handleRadius, TOPLEFTSCALE);
handleChooser.addFunction(m_d->transformedHandles.rotationCenter,
rotationHandleRadius, MOVECENTER);
m_d->function = handleChooser.function();
if (m_d->function == ROTATE || m_d->function == MOVE) {
QRectF originalRect = m_d->transaction.originalRect();
QPointF t = m_d->transform.inverted().map(mousePos);
if (t.x() >= originalRect.left() && t.x() <= originalRect.right()) {
if (fabs(t.y() - originalRect.top()) <= handleRadius)
m_d->function = TOPSHEAR;
if (fabs(t.y() - originalRect.bottom()) <= handleRadius)
m_d->function = BOTTOMSHEAR;
}
if (t.y() >= originalRect.top() && t.y() <= originalRect.bottom()) {
if (fabs(t.x() - originalRect.left()) <= handleRadius)
m_d->function = LEFTSHEAR;
if (fabs(t.x() - originalRect.right()) <= handleRadius)
m_d->function = RIGHTSHEAR;
}
}
}
QCursor KisFreeTransformStrategy::Private::getScaleCursor(const QPointF &handlePt)
{
QPointF handlePtInWidget = converter->imageToWidget(handlePt);
QPointF centerPtInWidget = converter->imageToWidget(currentArgs.transformedCenter());
QPointF direction = handlePtInWidget - centerPtInWidget;
qreal angle = atan2(direction.y(), direction.x());
angle = normalizeAngle(angle);
int octant = qRound(angle * 4. / M_PI) % 8;
return scaleCursors[octant];
}
QCursor KisFreeTransformStrategy::Private::getShearCursor(const QPointF &start, const QPointF &end)
{
QPointF startPtInWidget = converter->imageToWidget(start);
QPointF endPtInWidget = converter->imageToWidget(end);
QPointF direction = endPtInWidget - startPtInWidget;
qreal angle = atan2(-direction.y(), direction.x());
return QCursor(shearCursorPixmap.transformed(QTransform().rotateRadians(-angle)));
}
QCursor KisFreeTransformStrategy::getCurrentCursor() const
{
QCursor cursor;
switch (m_d->function) {
case MOVE:
cursor = KisCursor::moveCursor();
break;
case ROTATE:
cursor = KisCursor::rotateCursor();
break;
case PERSPECTIVE:
//TODO: find another cursor for perspective
cursor = KisCursor::rotateCursor();
break;
case RIGHTSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.middleRight);
break;
case TOPSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.topMiddle);
break;
case LEFTSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.middleLeft);
break;
case BOTTOMSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomMiddle);
break;
case TOPRIGHTSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.topRight);
break;
case BOTTOMLEFTSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomLeft);
break;
case TOPLEFTSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.topLeft);
break;
case BOTTOMRIGHTSCALE:
cursor = m_d->getScaleCursor(m_d->transformedHandles.bottomRight);
break;
case MOVECENTER:
cursor = KisCursor::handCursor();
break;
case BOTTOMSHEAR:
cursor = m_d->getShearCursor(m_d->transformedHandles.bottomLeft, m_d->transformedHandles.bottomRight);
break;
case RIGHTSHEAR:
cursor = m_d->getShearCursor(m_d->transformedHandles.bottomRight, m_d->transformedHandles.topRight);
break;
case TOPSHEAR:
cursor = m_d->getShearCursor(m_d->transformedHandles.topRight, m_d->transformedHandles.topLeft);
break;
case LEFTSHEAR:
cursor = m_d->getShearCursor(m_d->transformedHandles.topLeft, m_d->transformedHandles.bottomLeft);
break;
}
return cursor;
}
void KisFreeTransformStrategy::paint(QPainter &gc)
{
gc.save();
gc.setOpacity(m_d->transaction.basePreviewOpacity());
gc.setTransform(m_d->paintingTransform, true);
gc.drawImage(m_d->paintingOffset, originalImage());
gc.restore();
// Draw Handles
QRectF handleRect =
KisTransformUtils::handleRect(KisTransformUtils::handleVisualRadius,
m_d->handlesTransform,
m_d->transaction.originalRect(), 0, 0);
qreal rX = 1;
qreal rY = 1;
QRectF rotationCenterRect =
KisTransformUtils::handleRect(KisTransformUtils::rotationHandleVisualRadius,
m_d->handlesTransform,
m_d->transaction.originalRect(),
&rX,
&rY);
QPainterPath handles;
handles.moveTo(m_d->transaction.originalTopLeft());
handles.lineTo(m_d->transaction.originalTopRight());
handles.lineTo(m_d->transaction.originalBottomRight());
handles.lineTo(m_d->transaction.originalBottomLeft());
handles.lineTo(m_d->transaction.originalTopLeft());
handles.addRect(handleRect.translated(m_d->transaction.originalTopLeft()));
handles.addRect(handleRect.translated(m_d->transaction.originalTopRight()));
handles.addRect(handleRect.translated(m_d->transaction.originalBottomLeft()));
handles.addRect(handleRect.translated(m_d->transaction.originalBottomRight()));
handles.addRect(handleRect.translated(m_d->transaction.originalMiddleLeft()));
handles.addRect(handleRect.translated(m_d->transaction.originalMiddleRight()));
handles.addRect(handleRect.translated(m_d->transaction.originalMiddleTop()));
handles.addRect(handleRect.translated(m_d->transaction.originalMiddleBottom()));
QPointF rotationCenter = m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset();
QPointF dx(rX + 3, 0);
QPointF dy(0, rY + 3);
handles.addEllipse(rotationCenterRect.translated(rotationCenter));
handles.moveTo(rotationCenter - dx);
handles.lineTo(rotationCenter + dx);
handles.moveTo(rotationCenter - dy);
handles.lineTo(rotationCenter + dy);
gc.save();
//gc.setTransform(m_d->handlesTransform, true); <-- don't do like this!
QPainterPath mappedHandles = m_d->handlesTransform.map(handles);
QPen pen[2];
pen[0].setWidth(1);
pen[1].setWidth(2);
pen[1].setColor(Qt::lightGray);
for (int i = 1; i >= 0; --i) {
gc.setPen(pen[i]);
gc.drawPath(mappedHandles);
}
gc.restore();
}
void KisFreeTransformStrategy::externalConfigChanged()
{
m_d->recalculateTransformations();
}
bool KisFreeTransformStrategy::beginPrimaryAction(const QPointF &pt)
{
m_d->clickArgs = m_d->currentArgs;
m_d->clickPos = pt;
return true;
}
void KisFreeTransformStrategy::continuePrimaryAction(const QPointF &mousePos,
bool shiftModifierActive,
bool altModifierActive)
{
// Note: "shiftModifierActive" just tells us if the shift key is being pressed
// Note: "altModifierActive" just tells us if the alt key is being pressed
const QPointF anchorPoint = m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset();
switch (m_d->function) {
case MOVE: {
QPointF diff = mousePos - m_d->clickPos;
if (shiftModifierActive) {
KisTransformUtils::MatricesPack m(m_d->clickArgs);
QTransform t = m.S * m.projectedP;
QPointF originalDiff = t.inverted().map(diff);
if (qAbs(originalDiff.x()) >= qAbs(originalDiff.y())) {
originalDiff.setY(0);
} else {
originalDiff.setX(0);
}
diff = t.map(originalDiff);
}
m_d->currentArgs.setTransformedCenter(m_d->clickArgs.transformedCenter() + diff);
break;
}
case ROTATE:
{
KisTransformUtils::MatricesPack clickM(m_d->clickArgs);
QTransform clickT = clickM.finalTransform();
QPointF rotationCenter = m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset();
QPointF clickMouseImagePos = clickT.inverted().map(m_d->clickPos) - rotationCenter;
QPointF mouseImagePos = clickT.inverted().map(mousePos) - rotationCenter;
qreal a1 = atan2(clickMouseImagePos.y(), clickMouseImagePos.x());
qreal a2 = atan2(mouseImagePos.y(), mouseImagePos.x());
qreal theta = a2 - a1;
// Snap with shift key
if (shiftModifierActive) {
const qreal snapAngle = M_PI_4 / 6.0; // fifteen degrees
qint32 thetaIndex = static_cast<qint32>((theta / snapAngle) + 0.5);
m_d->currentArgs.setAZ(normalizeAngle(thetaIndex * snapAngle));
}
else {
m_d->currentArgs.setAZ(normalizeAngle(m_d->clickArgs.aZ() + theta));
}
KisTransformUtils::MatricesPack m(m_d->currentArgs);
QTransform t = m.finalTransform();
QPointF newRotationCenter = t.map(m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset());
QPointF oldRotationCenter = clickT.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldRotationCenter - newRotationCenter);
}
break;
case PERSPECTIVE:
{
QPointF diff = mousePos - m_d->clickPos;
double thetaX = - diff.y() * M_PI / m_d->transaction.originalHalfHeight() / 2 / fabs(m_d->currentArgs.scaleY());
m_d->currentArgs.setAX(normalizeAngle(m_d->clickArgs.aX() + thetaX));
qreal sign = qAbs(m_d->currentArgs.aX() - M_PI) < M_PI / 2 ? -1.0 : 1.0;
double thetaY = sign * diff.x() * M_PI / m_d->transaction.originalHalfWidth() / 2 / fabs(m_d->currentArgs.scaleX());
m_d->currentArgs.setAY(normalizeAngle(m_d->clickArgs.aY() + thetaY));
KisTransformUtils::MatricesPack m(m_d->currentArgs);
QTransform t = m.finalTransform();
QPointF newRotationCenter = t.map(m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset());
KisTransformUtils::MatricesPack clickM(m_d->clickArgs);
QTransform clickT = clickM.finalTransform();
QPointF oldRotationCenter = clickT.map(m_d->clickArgs.originalCenter() + m_d->clickArgs.rotationCenterOffset());
m_d->currentArgs.setTransformedCenter(m_d->currentArgs.transformedCenter() + oldRotationCenter - newRotationCenter);
}
break;
case TOPSCALE:
case BOTTOMSCALE: {
QPointF staticPoint;
QPointF movingPoint;
qreal extraSign;
if (m_d->function == TOPSCALE) {
staticPoint = m_d->transaction.originalMiddleBottom();
movingPoint = m_d->transaction.originalMiddleTop();
extraSign = -1.0;
} else {
staticPoint = m_d->transaction.originalMiddleTop();
movingPoint = m_d->transaction.originalMiddleBottom();
extraSign = 1.0;
}
// override scale static point if it is locked
if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) &&
!qFuzzyCompare(anchorPoint.y(), movingPoint.y())) {
staticPoint = anchorPoint;
}
QPointF mouseImagePos = m_d->transform.inverted().map(mousePos);
qreal sign = mouseImagePos.y() <= staticPoint.y() ? -extraSign : extraSign;
m_d->currentArgs.setScaleY(sign * m_d->currentArgs.scaleY());
QPointF staticPointInView = m_d->transform.map(staticPoint);
qreal dist = kisDistance(staticPointInView, mousePos);
GSL::ScaleResult1D result =
GSL::calculateScaleY(m_d->currentArgs,
staticPoint,
staticPointInView,
movingPoint,
dist);
if (shiftModifierActive || m_d->currentArgs.keepAspectRatio()) {
qreal aspectRatio = m_d->clickArgs.scaleX() / m_d->clickArgs.scaleY();
m_d->currentArgs.setScaleX(aspectRatio * result.scale);
}
m_d->currentArgs.setScaleY(result.scale);
m_d->currentArgs.setTransformedCenter(result.transformedCenter);
break;
}
case LEFTSCALE:
case RIGHTSCALE: {
QPointF staticPoint;
QPointF movingPoint;
qreal extraSign;
if (m_d->function == LEFTSCALE) {
staticPoint = m_d->transaction.originalMiddleRight();
movingPoint = m_d->transaction.originalMiddleLeft();
extraSign = -1.0;
} else {
staticPoint = m_d->transaction.originalMiddleLeft();
movingPoint = m_d->transaction.originalMiddleRight();
extraSign = 1.0;
}
// override scale static point if it is locked
if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) &&
!qFuzzyCompare(anchorPoint.x(), movingPoint.x())) {
staticPoint = anchorPoint;
}
QPointF mouseImagePos = m_d->transform.inverted().map(mousePos);
qreal sign = mouseImagePos.x() <= staticPoint.x() ? -extraSign : extraSign;
m_d->currentArgs.setScaleX(sign * m_d->currentArgs.scaleX());
QPointF staticPointInView = m_d->transform.map(staticPoint);
qreal dist = kisDistance(staticPointInView, mousePos);
GSL::ScaleResult1D result =
GSL::calculateScaleX(m_d->currentArgs,
staticPoint,
staticPointInView,
movingPoint,
dist);
if (shiftModifierActive || m_d->currentArgs.keepAspectRatio()) {
qreal aspectRatio = m_d->clickArgs.scaleY() / m_d->clickArgs.scaleX();
m_d->currentArgs.setScaleY(aspectRatio * result.scale);
}
m_d->currentArgs.setScaleX(result.scale);
m_d->currentArgs.setTransformedCenter(result.transformedCenter);
break;
}
case TOPRIGHTSCALE:
case BOTTOMRIGHTSCALE:
case TOPLEFTSCALE:
case BOTTOMLEFTSCALE: {
QPointF staticPoint;
QPointF movingPoint;
if (m_d->function == TOPRIGHTSCALE) {
staticPoint = m_d->transaction.originalBottomLeft();
movingPoint = m_d->transaction.originalTopRight();
} else if (m_d->function == BOTTOMRIGHTSCALE) {
staticPoint = m_d->transaction.originalTopLeft();
movingPoint = m_d->transaction.originalBottomRight();
} else if (m_d->function == TOPLEFTSCALE) {
staticPoint = m_d->transaction.originalBottomRight();
movingPoint = m_d->transaction.originalTopLeft();
} else {
staticPoint = m_d->transaction.originalTopRight();
movingPoint = m_d->transaction.originalBottomLeft();
}
// override scale static point if it is locked
if ((m_d->currentArgs.transformAroundRotationCenter() ^ altModifierActive) &&
!(qFuzzyCompare(anchorPoint.x(), movingPoint.x()) ||
qFuzzyCompare(anchorPoint.y(), movingPoint.y()))) {
staticPoint = anchorPoint;
}
QPointF staticPointInView = m_d->transform.map(staticPoint);
QPointF movingPointInView = mousePos;
if (shiftModifierActive || m_d->currentArgs.keepAspectRatio()) {
KisTransformUtils::MatricesPack m(m_d->clickArgs);
QTransform t = m.finalTransform();
QPointF refDiff = t.map(movingPoint) - staticPointInView;
QPointF realDiff = mousePos - staticPointInView;
realDiff = kisProjectOnVector(refDiff, realDiff);
movingPointInView = staticPointInView + realDiff;
}
GSL::ScaleResult2D result =
GSL::calculateScale2D(m_d->currentArgs,
staticPoint,
staticPointInView,
movingPoint,
movingPointInView);
m_d->currentArgs.setScaleX(result.scaleX);
m_d->currentArgs.setScaleY(result.scaleY);
m_d->currentArgs.setTransformedCenter(result.transformedCenter);
break;
}
case MOVECENTER: {
QPointF pt = m_d->transform.inverted().map(mousePos);
pt = KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect());
QPointF newRotationCenterOffset = pt - m_d->currentArgs.originalCenter();
if (shiftModifierActive) {
if (qAbs(newRotationCenterOffset.x()) > qAbs(newRotationCenterOffset.y())) {
newRotationCenterOffset.ry() = 0;
} else {
newRotationCenterOffset.rx() = 0;
}
}
m_d->currentArgs.setRotationCenterOffset(newRotationCenterOffset);
emit requestResetRotationCenterButtons();
}
break;
case TOPSHEAR:
case BOTTOMSHEAR: {
KisTransformUtils::MatricesPack m(m_d->clickArgs);
QTransform backwardT = (m.S * m.projectedP).inverted();
QPointF diff = backwardT.map(mousePos - m_d->clickPos);
qreal sign = m_d->function == BOTTOMSHEAR ? 1.0 : -1.0;
// get the dx pixels corresponding to the current shearX factor
qreal dx = sign * m_d->clickArgs.shearX() * m_d->clickArgs.scaleY() * m_d->transaction.originalHalfHeight(); // get the dx pixels corresponding to the current shearX factor
dx += diff.x();
// calculate the new shearX factor
m_d->currentArgs.setShearX(sign * dx / m_d->currentArgs.scaleY() / m_d->transaction.originalHalfHeight()); // calculate the new shearX factor
break;
}
case LEFTSHEAR:
case RIGHTSHEAR: {
KisTransformUtils::MatricesPack m(m_d->clickArgs);
QTransform backwardT = (m.S * m.projectedP).inverted();
QPointF diff = backwardT.map(mousePos - m_d->clickPos);
qreal sign = m_d->function == RIGHTSHEAR ? 1.0 : -1.0;
// get the dx pixels corresponding to the current shearX factor
qreal dy = sign * m_d->clickArgs.shearY() * m_d->clickArgs.scaleX() * m_d->transaction.originalHalfWidth();
dy += diff.y();
// calculate the new shearY factor
m_d->currentArgs.setShearY(sign * dy / m_d->clickArgs.scaleX() / m_d->transaction.originalHalfWidth());
break;
}
}
m_d->recalculateTransformations();
}
bool KisFreeTransformStrategy::endPrimaryAction()
{
bool shouldSave = !m_d->imageTooBig;
if (m_d->imageTooBig) {
m_d->currentArgs = m_d->clickArgs;
m_d->recalculateTransformations();
}
return shouldSave;
}
void KisFreeTransformStrategy::Private::recalculateTransformations()
{
KisTransformUtils::MatricesPack m(currentArgs);
QTransform sanityCheckMatrix = m.TS * m.SC * m.S * m.projectedP;
/**
* The center of the original image should still
* stay the origin of CS
*/
KIS_ASSERT_RECOVER_NOOP(sanityCheckMatrix.map(currentArgs.originalCenter()).manhattanLength() < 1e-4);
transform = m.finalTransform();
QTransform viewScaleTransform = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
handlesTransform = transform * viewScaleTransform;
QTransform tl = QTransform::fromTranslate(transaction.originalTopLeft().x(), transaction.originalTopLeft().y());
paintingTransform = tl.inverted() * q->thumbToImageTransform() * tl * transform * viewScaleTransform;
paintingOffset = transaction.originalTopLeft();
// check whether image is too big to be displayed or not
imageTooBig = KisTransformUtils::checkImageTooBig(transaction.originalRect(), m);
// recalculate cached handles position
recalculateTransformedHandles();
emit q->requestShowImageTooBig(imageTooBig);
}
diff --git a/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp
index aaa55684c3..ab548fd54c 100644
--- a/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp
+++ b/plugins/tools/tool_transform2/kis_liquify_transform_strategy.cpp
@@ -1,309 +1,309 @@
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_liquify_transform_strategy.h"
#include <algorithm>
#include <QPointF>
#include <QPainter>
#include "KoPointerEvent.h"
#include "kis_coordinates_converter.h"
#include "tool_transform_args.h"
#include "transform_transaction_properties.h"
#include "krita_utils.h"
#include "kis_cursor.h"
#include "kis_transform_utils.h"
#include "kis_algebra_2d.h"
#include "kis_transform_utils.h"
#include "kis_liquify_paint_helper.h"
#include "kis_liquify_transform_worker.h"
#include "KoCanvasResourceManager.h"
struct KisLiquifyTransformStrategy::Private
{
Private(KisLiquifyTransformStrategy *_q,
const KisCoordinatesConverter *_converter,
ToolTransformArgs &_currentArgs,
TransformTransactionProperties &_transaction,
const KoCanvasResourceManager *_manager)
: manager(_manager),
q(_q),
converter(_converter),
currentArgs(_currentArgs),
transaction(_transaction),
helper(_converter),
recalculateOnNextRedraw(false)
{
}
const KoCanvasResourceManager *manager;
KisLiquifyTransformStrategy * const q;
/// standard members ///
const KisCoordinatesConverter *converter;
//////
ToolTransformArgs &currentArgs;
//////
TransformTransactionProperties &transaction;
QTransform paintingTransform;
QPointF paintingOffset;
QTransform handlesTransform;
/// custom members ///
QImage transformedImage;
// size-gesture-related
QPointF lastMouseWidgetPos;
QPointF startResizeImagePos;
QPoint startResizeGlobalCursorPos;
KisLiquifyPaintHelper helper;
bool recalculateOnNextRedraw;
void recalculateTransformations();
inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
};
KisLiquifyTransformStrategy::KisLiquifyTransformStrategy(const KisCoordinatesConverter *converter,
ToolTransformArgs &currentArgs,
TransformTransactionProperties &transaction,
const KoCanvasResourceManager *manager)
: m_d(new Private(this, converter, currentArgs, transaction, manager))
{
}
KisLiquifyTransformStrategy::~KisLiquifyTransformStrategy()
{
}
QPainterPath KisLiquifyTransformStrategy::getCursorOutline() const
{
return m_d->helper.brushOutline(*m_d->currentArgs.liquifyProperties());
}
void KisLiquifyTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive)
{
Q_UNUSED(mousePos);
Q_UNUSED(perspectiveModifierActive);
}
QCursor KisLiquifyTransformStrategy::getCurrentCursor() const
{
return Qt::BlankCursor;
}
void KisLiquifyTransformStrategy::paint(QPainter &gc)
{
// Draw preview image
if (m_d->recalculateOnNextRedraw) {
m_d->recalculateTransformations();
m_d->recalculateOnNextRedraw = false;
}
gc.save();
gc.setOpacity(m_d->transaction.basePreviewOpacity());
gc.setTransform(m_d->paintingTransform, true);
gc.drawImage(m_d->paintingOffset, m_d->transformedImage);
gc.restore();
}
void KisLiquifyTransformStrategy::externalConfigChanged()
{
if (!m_d->currentArgs.liquifyWorker()) return;
m_d->recalculateTransformations();
}
bool KisLiquifyTransformStrategy::acceptsClicks() const
{
return true;
}
bool KisLiquifyTransformStrategy::beginPrimaryAction(KoPointerEvent *event)
{
m_d->helper.configurePaintOp(*m_d->currentArgs.liquifyProperties(), m_d->currentArgs.liquifyWorker());
m_d->helper.startPaint(event, m_d->manager);
m_d->recalculateTransformations();
return true;
}
void KisLiquifyTransformStrategy::continuePrimaryAction(KoPointerEvent *event)
{
m_d->helper.continuePaint(event);
// the updates should be compressed
m_d->recalculateOnNextRedraw = true;
emit requestCanvasUpdate();
}
bool KisLiquifyTransformStrategy::endPrimaryAction(KoPointerEvent *event)
{
if (m_d->helper.endPaint(event)) {
m_d->recalculateTransformations();
emit requestCanvasUpdate();
}
return true;
}
void KisLiquifyTransformStrategy::hoverActionCommon(KoPointerEvent *event)
{
m_d->helper.hoverPaint(event);
}
void KisLiquifyTransformStrategy::activateAlternateAction(KisTool::AlternateAction action)
{
if (action == KisTool::PickFgNode || action == KisTool::PickBgNode ||
action == KisTool::PickFgImage || action == KisTool::PickBgImage) {
KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties();
props->setReverseDirection(!props->reverseDirection());
emit requestUpdateOptionWidget();
}
}
void KisLiquifyTransformStrategy::deactivateAlternateAction(KisTool::AlternateAction action)
{
if (action == KisTool::PickFgNode || action == KisTool::PickBgNode ||
action == KisTool::PickFgImage || action == KisTool::PickBgImage) {
KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties();
props->setReverseDirection(!props->reverseDirection());
emit requestUpdateOptionWidget();
}
}
bool KisLiquifyTransformStrategy::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
{
if (action == KisTool::ChangeSize) {
QPointF widgetPoint = m_d->converter->documentToWidget(event->point);
m_d->lastMouseWidgetPos = widgetPoint;
m_d->startResizeImagePos = m_d->converter->documentToImage(event->point);
m_d->startResizeGlobalCursorPos = QCursor::pos();
return true;
} else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode ||
action == KisTool::PickFgImage || action == KisTool::PickBgImage) {
return beginPrimaryAction(event);
}
return false;
}
void KisLiquifyTransformStrategy::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
{
if (action == KisTool::ChangeSize) {
QPointF widgetPoint = m_d->converter->documentToWidget(event->point);
QPointF diff = widgetPoint - m_d->lastMouseWidgetPos;
KisLiquifyProperties *props = m_d->currentArgs.liquifyProperties();
const qreal linearizedOffset = diff.x() / KisTransformUtils::scaleFromAffineMatrix(m_d->converter->imageToWidgetTransform());
const qreal newSize = qBound(props->minSize(), props->size() + linearizedOffset, props->maxSize());
props->setSize(newSize);
m_d->currentArgs.saveLiquifyTransformMode();
m_d->lastMouseWidgetPos = widgetPoint;
emit requestCursorOutlineUpdate(m_d->startResizeImagePos);
} else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode ||
action == KisTool::PickFgImage || action == KisTool::PickBgImage) {
return continuePrimaryAction(event);
}
}
bool KisLiquifyTransformStrategy::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action)
{
Q_UNUSED(event);
if (action == KisTool::ChangeSize) {
QCursor::setPos(m_d->startResizeGlobalCursorPos);
return true;
} else if (action == KisTool::PickFgNode || action == KisTool::PickBgNode ||
action == KisTool::PickFgImage || action == KisTool::PickBgImage) {
return endPrimaryAction(event);
}
return false;
}
inline QPointF KisLiquifyTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization)
{
return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt);
}
void KisLiquifyTransformStrategy::Private::recalculateTransformations()
{
KIS_ASSERT_RECOVER_RETURN(currentArgs.liquifyWorker());
QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter);
QTransform resultTransform = q->thumbToImageTransform() * scaleTransform;
qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultTransform);
bool useFlakeOptimization = scale < 1.0;
paintingOffset = transaction.originalTopLeft();
if (!q->originalImage().isNull()) {
if (useFlakeOptimization) {
transformedImage = q->originalImage().transformed(q->thumbToImageTransform() * scaleTransform);
paintingTransform = QTransform();
} else {
transformedImage = q->originalImage();
paintingTransform = q->thumbToImageTransform() * scaleTransform;
}
QTransform imageToRealThumbTransform =
useFlakeOptimization ?
scaleTransform :
- QTransform();
+ q->thumbToImageTransform().inverted();
QPointF origTLInFlake =
imageToRealThumbTransform.map(transaction.originalTopLeft());
transformedImage =
currentArgs.liquifyWorker()->runOnQImage(transformedImage,
origTLInFlake,
imageToRealThumbTransform,
&paintingOffset);
} else {
transformedImage = q->originalImage();
paintingOffset = imageToThumb(transaction.originalTopLeft(), false);
paintingTransform = q->thumbToImageTransform() * scaleTransform;
}
handlesTransform = scaleTransform;
}
diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc
index 60b113cf79..d696f4f409 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform.cc
+++ b/plugins/tools/tool_transform2/kis_tool_transform.cc
@@ -1,1194 +1,1249 @@
/*
* 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 <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 <kis_system_locker.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 "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_changesTracker(&m_transaction)
, 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;
connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(const QPointF&)), SLOT(cursorOutlineUpdateRequested(const QPointF&)));
connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget()));
connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested()));
connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested()));
connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool)));
connect(&m_changesTracker, SIGNAL(sigConfigChanged()),
this, SLOT(slotTrackerChangedConfig()));
}
KisToolTransform::~KisToolTransform()
{
cancelStroke();
}
void KisToolTransform::outlineChanged()
{
emit freeTransformChanged();
m_canvas->updateCanvas();
}
void KisToolTransform::canvasUpdateRequested()
{
m_canvas->updateCanvas();
}
void KisToolTransform::resetCursorStyle()
{
setFunctionalCursor();
}
void KisToolTransform::resetRotationCenterButtonsRequested()
{
if (!m_optionsWidget) return;
m_optionsWidget->resetRotationCenterButtons();
}
void KisToolTransform::imageTooBigRequested(bool value)
{
if (!m_optionsWidget) return;
m_optionsWidget->setTooBigLabelVisible(value);
}
KisTransformStrategyBase* KisToolTransform::currentStrategy() const
{
if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) {
return m_freeStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::WARP) {
return m_warpStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) {
return m_cageStrategy.data();
} else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
return m_liquifyStrategy.data();
} else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ {
return m_perspectiveStrategy.data();
}
}
void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
{
Q_UNUSED(converter);
if (!m_strokeData.strokeId()) return;
QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0));
if (m_refRect != newRefRect) {
m_refRect = newRefRect;
currentStrategy()->externalConfigChanged();
}
gc.save();
if (m_optionsWidget && m_optionsWidget->showDecorations()) {
gc.setOpacity(0.3);
gc.fillPath(m_selectionPath, Qt::black);
}
gc.restore();
currentStrategy()->paint(gc);
if (!m_cursorOutline.isEmpty()) {
QPainterPath mappedOutline =
KisTransformUtils::imageToFlakeTransform(
m_canvas->coordinatesConverter()).map(m_cursorOutline);
paintToolOutline(&gc, mappedOutline);
}
}
void KisToolTransform::setFunctionalCursor()
{
if (overrideCursorIfNotEditable()) {
return;
}
if (!m_strokeData.strokeId()) {
useCursor(KisCursor::pointingHandCursor());
} else {
useCursor(currentStrategy()->getCurrentCursor());
}
}
void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos)
{
QRect canvasUpdateRect;
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect = m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
m_cursorOutline = currentStrategy()->
getCursorOutline().translated(imagePos);
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect |=
m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
if (!canvasUpdateRect.isEmpty()) {
// grow rect a bit to follow interpolation fuzziness
canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
m_canvas->updateCanvas(canvasUpdateRect);
}
}
void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (!nodeEditable()) {
event->ignore();
return;
}
- if (currentNode()->inherits("KisShapeLayer") || currentNode()->inherits("KisFileLayer")) {
- QString message = i18n("The transform tool cannot transform a vector or file layer. Use a transform mask instead.");
- KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
- kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
- event->ignore();
- return;
- }
-
if (!m_strokeData.strokeId()) {
startStroke(m_currentArgs.mode(), false);
} else {
bool result = false;
if (usePrimaryAction) {
result = currentStrategy()->beginPrimaryAction(event);
} else {
result = currentStrategy()->beginAlternateAction(event, action);
}
if (result) {
setMode(KisTool::PAINT_MODE);
}
}
m_actuallyMoveWhileSelected = false;
outlineChanged();
}
void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (mode() != KisTool::PAINT_MODE) return;
m_actuallyMoveWhileSelected = true;
if (usePrimaryAction) {
currentStrategy()->continuePrimaryAction(event);
} else {
currentStrategy()->continueAlternateAction(event, action);
}
updateOptionWidget();
outlineChanged();
}
void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action)
{
if (mode() != KisTool::PAINT_MODE) return;
setMode(KisTool::HOVER_MODE);
if (m_actuallyMoveWhileSelected ||
currentStrategy()->acceptsClicks()) {
bool result = false;
if (usePrimaryAction) {
result = currentStrategy()->endPrimaryAction(event);
} else {
result = currentStrategy()->endAlternateAction(event, action);
}
if (result) {
commitChanges();
}
outlineChanged();
}
updateOptionWidget();
updateApplyResetAvailability();
}
void KisToolTransform::beginPrimaryAction(KoPointerEvent *event)
{
beginActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::continuePrimaryAction(KoPointerEvent *event)
{
continueActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::endPrimaryAction(KoPointerEvent *event)
{
endActionImpl(event, true, KisTool::NONE);
}
void KisToolTransform::activatePrimaryAction()
{
currentStrategy()->activatePrimaryAction();
setFunctionalCursor();
}
void KisToolTransform::deactivatePrimaryAction()
{
currentStrategy()->deactivatePrimaryAction();
}
void KisToolTransform::activateAlternateAction(AlternateAction action)
{
currentStrategy()->activateAlternateAction(action);
setFunctionalCursor();
}
void KisToolTransform::deactivateAlternateAction(AlternateAction action)
{
currentStrategy()->deactivateAlternateAction(action);
}
void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
{
beginActionImpl(event, false, action);
}
void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
{
continueActionImpl(event, false, action);
}
void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action)
{
endActionImpl(event, false, action);
}
void KisToolTransform::mousePressEvent(KoPointerEvent *event)
{
KisTool::mousePressEvent(event);
}
void KisToolTransform::mouseMoveEvent(KoPointerEvent *event)
{
QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
cursorOutlineUpdateRequested(mousePos);
if (!MOVE_CONDITION(event, KisTool::PAINT_MODE)) {
currentStrategy()->hoverActionCommon(event);
setFunctionalCursor();
KisTool::mouseMoveEvent(event);
return;
}
}
void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event)
{
KisTool::mouseReleaseEvent(event);
}
void KisToolTransform::touchEvent( QTouchEvent* event )
{
//Count all moving touch points
int touchCount = 0;
Q_FOREACH ( QTouchEvent::TouchPoint tp, event->touchPoints() ) {
if( tp.state() == Qt::TouchPointMoved ) {
touchCount++;
}
}
//Use the touch point count to determine the gesture
switch( touchCount ) {
case 1: { //Panning
QTouchEvent::TouchPoint tp = event->touchPoints().at( 0 );
QPointF diff = tp.screenPos() - tp.lastScreenPos();
m_currentArgs.setTransformedCenter( m_currentArgs.transformedCenter() + diff );
outlineChanged();
break;
}
case 2: { //Scaling
QTouchEvent::TouchPoint tp1 = event->touchPoints().at( 0 );
QTouchEvent::TouchPoint tp2 = event->touchPoints().at( 1 );
float lastZoom = (tp1.lastScreenPos() - tp2.lastScreenPos()).manhattanLength();
float newZoom = (tp1.screenPos() - tp2.screenPos()).manhattanLength();
float diff = (newZoom - lastZoom) / 100;
m_currentArgs.setScaleX( m_currentArgs.scaleX() + diff );
m_currentArgs.setScaleY( m_currentArgs.scaleY() + diff );
outlineChanged();
break;
}
case 3: { //Rotation
/* TODO: implement touch-based rotation.
Vector2f center;
Q_FOREACH ( const QTouchEvent::TouchPoint &tp, event->touchPoints() ) {
if( tp.state() == Qt::TouchPointMoved ) {
center += Vector2f( tp.screenPos().x(), tp.screenPos().y() );
}
}
center /= touchCount;
QTouchEvent::TouchPoint tp = event->touchPoints().at(0);
Vector2f oldPosition = (Vector2f( tp.lastScreenPos().x(), tp.lastScreenPos().y() ) - center).normalized();
Vector2f newPosition = (Vector2f( tp.screenPos().x(), tp.screenPos().y() ) - center).normalized();
float oldAngle = qAcos( oldPosition.dot( Vector2f( 0.0f, 0.0f ) ) );
float newAngle = qAcos( newPosition.dot( Vector2f( 0.0f, 0.0f ) ) );
float diff = newAngle - oldAngle;
m_currentArgs.setAZ( m_currentArgs.aZ() + diff );
outlineChanged();
*/
break;
}
}
}
void KisToolTransform::applyTransform()
{
slotApplyTransform();
}
KisToolTransform::TransformToolMode KisToolTransform::transformMode() const
{
TransformToolMode mode = FreeTransformMode;
switch (m_currentArgs.mode())
{
case ToolTransformArgs::FREE_TRANSFORM:
mode = FreeTransformMode;
break;
case ToolTransformArgs::WARP:
mode = WarpTransformMode;
break;
case ToolTransformArgs::CAGE:
mode = CageTransformMode;
break;
case ToolTransformArgs::LIQUIFY:
mode = LiquifyTransformMode;
break;
case ToolTransformArgs::PERSPECTIVE_4POINT:
mode = PerspectiveTransformMode;
break;
default:
KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
}
return mode;
}
double KisToolTransform::translateX() const
{
return m_currentArgs.transformedCenter().x();
}
double KisToolTransform::translateY() const
{
return m_currentArgs.transformedCenter().y();
}
double KisToolTransform::rotateX() const
{
return m_currentArgs.aX();
}
double KisToolTransform::rotateY() const
{
return m_currentArgs.aY();
}
double KisToolTransform::rotateZ() const
{
return m_currentArgs.aZ();
}
double KisToolTransform::scaleX() const
{
return m_currentArgs.scaleX();
}
double KisToolTransform::scaleY() const
{
return m_currentArgs.scaleY();
}
double KisToolTransform::shearX() const
{
return m_currentArgs.shearX();
}
double KisToolTransform::shearY() const
{
return m_currentArgs.shearY();
}
KisToolTransform::WarpType KisToolTransform::warpType() const
{
switch(m_currentArgs.warpType()) {
case KisWarpTransformWorker::AFFINE_TRANSFORM:
return AffineWarpType;
case KisWarpTransformWorker::RIGID_TRANSFORM:
return RigidWarpType;
case KisWarpTransformWorker::SIMILITUDE_TRANSFORM:
return SimilitudeWarpType;
default:
return RigidWarpType;
}
}
double KisToolTransform::warpFlexibility() const
{
return m_currentArgs.alpha();
}
int KisToolTransform::warpPointDensity() const
{
return m_currentArgs.numPoints();
}
void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode)
{
ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM;
switch (newMode) {
case FreeTransformMode:
mode = ToolTransformArgs::FREE_TRANSFORM;
break;
case WarpTransformMode:
mode = ToolTransformArgs::WARP;
break;
case CageTransformMode:
mode = ToolTransformArgs::CAGE;
break;
case LiquifyTransformMode:
mode = ToolTransformArgs::LIQUIFY;
break;
case PerspectiveTransformMode:
mode = ToolTransformArgs::PERSPECTIVE_4POINT;
break;
default:
KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode");
}
if( mode != m_currentArgs.mode() ) {
if( newMode == FreeTransformMode ) {
m_optionsWidget->slotSetFreeTransformModeButtonClicked( true );
} else if( newMode == WarpTransformMode ) {
m_optionsWidget->slotSetWarpModeButtonClicked( true );
} else if( newMode == CageTransformMode ) {
m_optionsWidget->slotSetCageModeButtonClicked( true );
} else if( newMode == LiquifyTransformMode ) {
m_optionsWidget->slotSetLiquifyModeButtonClicked( true );
} else if( newMode == PerspectiveTransformMode ) {
m_optionsWidget->slotSetPerspectiveModeButtonClicked( true );
}
emit transformModeChanged();
}
}
void KisToolTransform::setRotateX( double rotation )
{
m_currentArgs.setAX( normalizeAngle(rotation) );
}
void KisToolTransform::setRotateY( double rotation )
{
m_currentArgs.setAY( normalizeAngle(rotation) );
}
void KisToolTransform::setRotateZ( double rotation )
{
m_currentArgs.setAZ( normalizeAngle(rotation) );
}
void KisToolTransform::setWarpType( KisToolTransform::WarpType type )
{
switch( type ) {
case RigidWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM);
break;
case AffineWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM);
break;
case SimilitudeWarpType:
m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM);
break;
default:
break;
}
}
void KisToolTransform::setWarpFlexibility( double flexibility )
{
m_currentArgs.setAlpha( flexibility );
}
void KisToolTransform::setWarpPointDensity( int density )
{
m_optionsWidget->slotSetWarpDensity(density);
}
bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node)
{
bool result = false;
if (KisTransformMaskSP mask =
dynamic_cast<KisTransformMask*>(node.data())) {
KisTransformMaskParamsInterfaceSP savedParams =
mask->transformParams();
KisTransformMaskAdapter *adapter =
dynamic_cast<KisTransformMaskAdapter*>(savedParams.data());
if (adapter) {
m_currentArgs = adapter->transformArgs();
initGuiAfterTransformMode();
result = true;
}
}
return result;
}
bool KisToolTransform::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode)
{
bool result = false;
const KUndo2Command *lastCommand = image()->undoAdapter()->presentCommand();
KisNodeSP oldRootNode;
if (lastCommand &&
TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, args, &oldRootNode) &&
args->mode() == mode &&
oldRootNode == currentNode) {
args->saveContinuedState();
image()->undoAdapter()->undoLastCommand();
+
// FIXME: can we make it async?
image()->waitForDone();
+ forceRepaintDelayedLayers(oldRootNode);
result = true;
}
return result;
}
void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)
{
// NOTE: we are requesting an old value of m_currentArgs variable
// here, which is global, don't forget about this on higher
// levels.
QString filterId = m_currentArgs.filterId();
m_currentArgs = ToolTransformArgs();
m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric());
m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric());
if (mode == ToolTransformArgs::FREE_TRANSFORM) {
m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM);
} else if (mode == ToolTransformArgs::WARP) {
m_currentArgs.setMode(ToolTransformArgs::WARP);
m_optionsWidget->setDefaultWarpPoints();
m_currentArgs.setEditingTransformPoints(false);
} else if (mode == ToolTransformArgs::CAGE) {
m_currentArgs.setMode(ToolTransformArgs::CAGE);
m_currentArgs.setEditingTransformPoints(true);
} else if (mode == ToolTransformArgs::LIQUIFY) {
m_currentArgs.setMode(ToolTransformArgs::LIQUIFY);
const QRect srcRect = m_transaction.originalRect().toAlignedRect();
if (!srcRect.isEmpty()) {
m_currentArgs.initLiquifyTransformMode(m_transaction.originalRect().toAlignedRect());
}
} else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) {
m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT);
}
initGuiAfterTransformMode();
}
void KisToolTransform::initGuiAfterTransformMode()
{
currentStrategy()->externalConfigChanged();
outlineChanged();
updateOptionWidget();
updateApplyResetAvailability();
}
void KisToolTransform::updateSelectionPath()
{
m_selectionPath = QPainterPath();
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
QPainterPath selectionOutline;
KisSelectionSP selection = resources->activeSelection();
if (selection && selection->outlineCacheValid()) {
selectionOutline = selection->outlineCache();
} else {
selectionOutline.addRect(m_selectedPortionCache->exactBounds());
}
const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
m_selectionPath = i2f.map(selectionOutline);
}
void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice)
{
QImage origImg;
m_selectedPortionCache = previewDevice;
QTransform thumbToImageTransform;
const int maxSize = 2000;
QRect srcRect(m_transaction.originalRect().toAlignedRect());
int x, y, w, h;
srcRect.getRect(&x, &y, &w, &h);
if (w > maxSize || h > maxSize) {
qreal scale = qreal(maxSize) / (w > h ? w : h);
QTransform scaleTransform = QTransform::fromScale(scale, scale);
QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect();
origImg = m_selectedPortionCache->
createThumbnail(thumbRect.width(),
thumbRect.height(),
srcRect, 1,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
thumbToImageTransform = scaleTransform.inverted();
} else {
origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
thumbToImageTransform = QTransform();
}
// init both strokes since the thumbnail is initialized only once
// during the stroke
m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform);
m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform);
}
void KisToolTransform::activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes)
{
KisTool::activate(toolActivation, shapes);
if (currentNode()) {
- m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, currentNode());
+ m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
}
startStroke(ToolTransformArgs::FREE_TRANSFORM, false);
}
void KisToolTransform::deactivate()
{
endStroke();
m_canvas->updateCanvas();
KisTool::deactivate();
}
void KisToolTransform::requestUndoDuringStroke()
{
if (!m_strokeData.strokeId()) return;
m_changesTracker.requestUndo();
}
void KisToolTransform::requestStrokeEnd()
{
endStroke();
}
void KisToolTransform::requestStrokeCancellation()
{
cancelStroke();
}
void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset)
{
Q_ASSERT(!m_strokeData.strokeId());
KisResourcesSnapshotSP resources =
new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager());
KisNodeSP currentNode = resources->currentNode();
if (!currentNode || !currentNode->isEditable()) {
return;
}
/**
* FIXME: The transform tool is not completely asynchronous, it
* needs the content of the layer for creation of the stroke
* strategy. It means that we cannot start a new stroke until the
* previous one is finished. Ideally, we should create the
* m_selectedPortionCache and m_selectionPath somewhere in the
* stroke and pass it to the tool somehow. But currently, we will
* just disable starting a new stroke asynchronously
*/
if (image()->tryBarrierLock()) {
image()->unlock();
} else {
return;
}
+ /**
+ * We must ensure that the currently selected subtree
+ * has finished all its updates.
+ */
+ forceRepaintDelayedLayers(currentNode);
+
ToolTransformArgs fetchedArgs;
bool fetchedFromCommand = false;
if (!forceReset) {
fetchedFromCommand = tryFetchArgsFromCommandAndUndo(&fetchedArgs, mode, currentNode);
}
if (m_optionsWidget) {
m_workRecursively = m_optionsWidget->workRecursively() ||
!currentNode->paintDevice();
}
+ QList<KisNodeSP> nodesList = fetchNodesList(mode, currentNode, m_workRecursively);
+
+ if (nodesList.isEmpty()) {
+ KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
+ kisCanvas->viewManager()->
+ showFloatingMessage(
+ i18nc("floating message in transformation tool",
+ "Selected layer cannot be transformed with active transformation mode "),
+ koIcon("object-locked"), 4000, KisFloatingMessage::High);
+ return;
+ }
+
+
TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, resources->activeSelection(), image().data());
KisPaintDeviceSP previewDevice = strategy->previewDevice();
KisSelectionSP selection = strategy->realSelection();
QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds();
if (!selection && resources->activeSelection()) {
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Selections are not used when editing transform masks "),
QIcon(), 4000, KisFloatingMessage::Low);
}
if (srcRect.isEmpty()) {
delete strategy;
KisCanvas2 *kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
kisCanvas->viewManager()->
showFloatingMessage(
i18nc("floating message in transformation tool",
"Cannot transform empty layer "),
QIcon(), 1000, KisFloatingMessage::Medium);
return;
}
- m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode);
+ m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs, currentNode, nodesList);
initThumbnailImage(previewDevice);
updateSelectionPath();
if (!forceReset && fetchedFromCommand) {
m_currentArgs = fetchedArgs;
initGuiAfterTransformMode();
} else if (forceReset || !tryInitTransformModeFromNode(currentNode)) {
initTransformMode(mode);
}
m_strokeData = StrokeData(image()->startStroke(strategy));
- bool haveInvisibleNodes = clearDevices(m_transaction.rootNode(), m_workRecursively);
+ bool haveInvisibleNodes = clearDevices(nodesList);
if (haveInvisibleNodes) {
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);
}
Q_ASSERT(m_changesTracker.isEmpty());
commitChanges();
}
void KisToolTransform::endStroke()
{
if (!m_strokeData.strokeId()) return;
if (!m_currentArgs.isIdentity()) {
- transformDevices(m_transaction.rootNode(), m_workRecursively);
+ transformClearedDevices();
image()->addJob(m_strokeData.strokeId(),
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::SELECTION,
m_currentArgs,
- m_transaction.rootNode()));
+ m_transaction.rootNode())); // root node is used for progress only
image()->endStroke(m_strokeData.strokeId());
} else {
image()->cancelStroke(m_strokeData.strokeId());
}
m_strokeData.clear();
m_changesTracker.reset();
+ m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
+ outlineChanged();
}
void KisToolTransform::cancelStroke()
{
if (!m_strokeData.strokeId()) return;
if (m_currentArgs.continuedTransform()) {
m_currentArgs.restoreContinuedState();
endStroke();
} else {
image()->cancelStroke(m_strokeData.strokeId());
m_strokeData.clear();
m_changesTracker.reset();
+ m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {});
+ outlineChanged();
}
}
void KisToolTransform::commitChanges()
{
if (!m_strokeData.strokeId()) return;
m_changesTracker.commitConfig(m_currentArgs);
}
void KisToolTransform::slotTrackerChangedConfig()
{
slotUiChangedConfig();
updateOptionWidget();
}
-bool KisToolTransform::clearDevices(KisNodeSP node, bool recursive)
+QList<KisNodeSP> KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive)
{
- bool haveInvisibleNodes = false;
- if (!node->isEditable(false)) return haveInvisibleNodes;
+ QList<KisNodeSP> result;
+
+ auto fetchFunc =
+ [&result, mode, root] (KisNodeSP node) {
+ if (node->isEditable() &&
+ (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) &&
+ !node->inherits("KisFileLayer") &&
+ (!node->inherits("KisTransformMask") || node == root)) {
- haveInvisibleNodes = !node->visible(false);
+ result << node;
+ }
+ };
if (recursive) {
- // simple tail-recursive iteration
- KisNodeSP prevNode = node->lastChild();
- while(prevNode) {
- haveInvisibleNodes |= clearDevices(prevNode, recursive);
- prevNode = prevNode->prevSibling();
- }
+ KisLayerUtils::recursiveApplyNodes(root, fetchFunc);
+ } else {
+ fetchFunc(root);
}
- image()->addJob(m_strokeData.strokeId(),
- new TransformStrokeStrategy::ClearSelectionData(node));
+ return result;
+}
- /**
- * It might happen that the editablity state of the node would
- * change during the stroke, so we need to save the set of
- * applicable nodes right in the beginning of the processing
- */
- m_strokeData.addClearedNode(node);
+bool KisToolTransform::clearDevices(const QList<KisNodeSP> &nodes)
+{
+ bool haveInvisibleNodes = false;
+
+ Q_FOREACH (KisNodeSP node, nodes) {
+ haveInvisibleNodes |= !node->visible(false);
+
+ image()->addJob(m_strokeData.strokeId(),
+ new TransformStrokeStrategy::ClearSelectionData(node));
+
+ /**
+ * It might happen that the editablity state of the node would
+ * change during the stroke, so we need to save the set of
+ * applicable nodes right in the beginning of the processing
+ */
+ m_strokeData.addClearedNode(node);
+ }
return haveInvisibleNodes;
}
-void KisToolTransform::transformDevices(KisNodeSP node, bool recursive)
+void KisToolTransform::transformClearedDevices()
{
- if (!node->isEditable()) return;
-
- KIS_ASSERT_RECOVER_RETURN(recursive ||
- (m_strokeData.clearedNodes().size() == 1 &&
- KisNodeSP(m_strokeData.clearedNodes().first()) == node));
-
- Q_FOREACH (KisNodeSP currentNode, m_strokeData.clearedNodes()) {
- KIS_ASSERT_RECOVER_RETURN(currentNode);
+ Q_FOREACH (KisNodeSP node, m_strokeData.clearedNodes()) {
+ KIS_ASSERT_RECOVER_RETURN(node);
image()->addJob(m_strokeData.strokeId(),
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::PAINT_DEVICE,
m_currentArgs,
- currentNode));
+ node));
}
}
QWidget* KisToolTransform::createOptionWidget() {
m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0);
Q_CHECK_PTR(m_optionsWidget);
m_optionsWidget->setObjectName(toolId() + " option widget");
// See https://bugs.kde.org/show_bug.cgi?id=316896
QWidget *specialSpacer = new QWidget(m_optionsWidget);
specialSpacer->setObjectName("SpecialSpacer");
specialSpacer->setFixedSize(0, 0);
m_optionsWidget->layout()->addWidget(specialSpacer);
connect(m_optionsWidget, SIGNAL(sigConfigChanged()),
this, SLOT(slotUiChangedConfig()));
connect(m_optionsWidget, SIGNAL(sigApplyTransform()),
this, SLOT(slotApplyTransform()));
connect(m_optionsWidget, SIGNAL(sigResetTransform()),
this, SLOT(slotResetTransform()));
connect(m_optionsWidget, SIGNAL(sigRestartTransform()),
this, SLOT(slotRestartTransform()));
connect(m_optionsWidget, SIGNAL(sigEditingFinished()),
this, SLOT(slotEditingFinished()));
updateOptionWidget();
return m_optionsWidget;
}
void KisToolTransform::updateOptionWidget()
{
if (!m_optionsWidget) return;
if (!currentNode()) {
m_optionsWidget->setEnabled(false);
return;
}
else {
m_optionsWidget->setEnabled(true);
m_optionsWidget->updateConfig(m_currentArgs);
}
}
void KisToolTransform::updateApplyResetAvailability()
{
if (m_optionsWidget) {
m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity());
}
}
void KisToolTransform::slotUiChangedConfig()
{
if (mode() == KisTool::PAINT_MODE) return;
currentStrategy()->externalConfigChanged();
if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) {
m_currentArgs.saveLiquifyTransformMode();
}
outlineChanged();
updateApplyResetAvailability();
}
void KisToolTransform::slotApplyTransform()
{
QApplication::setOverrideCursor(KisCursor::waitCursor());
endStroke();
QApplication::restoreOverrideCursor();
}
void KisToolTransform::slotResetTransform()
{
if (m_currentArgs.continuedTransform()) {
ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode();
/**
* Our reset transform button can be used for two purposes:
*
* 1) Reset current transform to the initial one, which was
* loaded from the previous user action.
*
* 2) Reset transform frame to infinity when the frame is unchanged
*/
const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs);
if (transformDiffers &&
m_currentArgs.continuedTransform()->mode() == savedMode) {
m_currentArgs.restoreContinuedState();
initGuiAfterTransformMode();
slotEditingFinished();
} else {
+ KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root();
+
cancelStroke();
image()->waitForDone();
+ forceRepaintDelayedLayers(root);
startStroke(savedMode, true);
KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform());
}
} else {
initTransformMode(m_currentArgs.mode());
slotEditingFinished();
}
}
void KisToolTransform::slotRestartTransform()
{
if (!m_strokeData.strokeId()) return;
+ KisNodeSP root = m_transaction.rootNode();
+ KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above
+
ToolTransformArgs savedArgs(m_currentArgs);
cancelStroke();
image()->waitForDone();
+ forceRepaintDelayedLayers(root);
startStroke(savedArgs.mode(), true);
}
+void KisToolTransform::forceRepaintDelayedLayers(KisNodeSP root)
+{
+ KIS_SAFE_ASSERT_RECOVER_RETURN(root);
+
+ KisLayerUtils::recursiveApplyNodes(root,
+ [] (KisNodeSP node) {
+ KisDelayedUpdateNodeInterface *delayedUpdate =
+ dynamic_cast<KisDelayedUpdateNodeInterface*>(node.data());
+
+ if (delayedUpdate) {
+ delayedUpdate->forceUpdateTimedNode();
+ }
+ });
+
+ image()->waitForDone();
+}
+
+
void KisToolTransform::slotEditingFinished()
{
commitChanges();
}
void KisToolTransform::setShearY(double shear)
{
m_optionsWidget->slotSetShearY(shear);
}
void KisToolTransform::setShearX(double shear)
{
m_optionsWidget->slotSetShearX(shear);
}
void KisToolTransform::setScaleY(double scale)
{
m_optionsWidget->slotSetScaleY(scale);
}
void KisToolTransform::setScaleX(double scale)
{
m_optionsWidget->slotSetScaleX(scale);
}
void KisToolTransform::setTranslateY(double translation)
{
m_optionsWidget->slotSetTranslateY(translation);
}
void KisToolTransform::setTranslateX(double translation)
{
m_optionsWidget->slotSetTranslateX(translation);
}
diff --git a/plugins/tools/tool_transform2/kis_tool_transform.h b/plugins/tools/tool_transform2/kis_tool_transform.h
index 0bee4ec5ae..5c7d8b5299 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform.h
+++ b/plugins/tools/tool_transform2/kis_tool_transform.h
@@ -1,333 +1,337 @@
/*
* kis_tool_transform.h - 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>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public 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_TRANSFORM_H_
#define KIS_TOOL_TRANSFORM_H_
#include <kis_icon.h>
#include <QPoint>
#include <QPointF>
#include <QVector2D>
#include <QVector3D>
#include <QButtonGroup>
#include <QPointer>
#include <QKeySequence>
#include <KoToolFactoryBase.h>
#include <kis_shape_selection.h>
#include <kis_undo_adapter.h>
#include <kis_types.h>
#include <flake/kis_node_shape.h>
#include <kis_tool.h>
#include <kis_canvas2.h>
#include "tool_transform_args.h"
#include "tool_transform_changes_tracker.h"
#include "kis_tool_transform_config_widget.h"
#include "transform_transaction_properties.h"
class QTouchEvent;
class KisTransformStrategyBase;
class KisWarpTransformStrategy;
class KisCageTransformStrategy;
class KisLiquifyTransformStrategy;
class KisFreeTransformStrategy;
class KisPerspectiveTransformStrategy;
/**
* Transform tool
* This tool offers several modes.
* - Free Transform mode allows the user to translate, scale, shear, rotate and
* apply a perspective transformation to a selection or the whole canvas.
* - Warp mode allows the user to warp the selection of the canvas by grabbing
* and moving control points placed on the image. The user can either work
* with default control points, like a grid whose density can be modified, or
* place the control points manually. The modifications made on the selected
* pixels are applied only when the user clicks the Apply button : the
* semi-transparent image displayed until the user click that button is only a
* preview.
* - Cage transform is similar to warp transform with control points exactly
* placed on the outer boundary. The user draws a boundary polygon, the
* vertices of which become control points.
* - Perspective transform applies a two-point perspective transformation. The
* user can manipulate the corners of the selection. If the vanishing points
* of the resulting quadrilateral are on screen, the user can manipulate those
* as well.
* - Liquify transform transforms the selection by painting motions, as if the
* user were fingerpainting.
*/
class KisToolTransform : public KisTool
{
Q_OBJECT
Q_PROPERTY(TransformToolMode transformMode READ transformMode WRITE setTransformMode NOTIFY transformModeChanged)
Q_PROPERTY(double translateX READ translateX WRITE setTranslateX NOTIFY freeTransformChanged)
Q_PROPERTY(double translateY READ translateY WRITE setTranslateY NOTIFY freeTransformChanged)
Q_PROPERTY(double rotateX READ rotateX WRITE setRotateX NOTIFY freeTransformChanged)
Q_PROPERTY(double rotateY READ rotateY WRITE setRotateY NOTIFY freeTransformChanged)
Q_PROPERTY(double rotateZ READ rotateZ WRITE setRotateZ NOTIFY freeTransformChanged)
Q_PROPERTY(double scaleX READ scaleX WRITE setScaleX NOTIFY freeTransformChanged)
Q_PROPERTY(double scaleY READ scaleY WRITE setScaleY NOTIFY freeTransformChanged)
Q_PROPERTY(double shearX READ shearX WRITE setShearX NOTIFY freeTransformChanged)
Q_PROPERTY(double shearY READ shearY WRITE setShearY NOTIFY freeTransformChanged)
Q_PROPERTY(WarpType warpType READ warpType WRITE setWarpType NOTIFY warpTransformChanged)
Q_PROPERTY(double warpFlexibility READ warpFlexibility WRITE setWarpFlexibility NOTIFY warpTransformChanged)
Q_PROPERTY(int warpPointDensity READ warpPointDensity WRITE setWarpPointDensity NOTIFY warpTransformChanged)
public:
enum TransformToolMode {
FreeTransformMode,
WarpTransformMode,
CageTransformMode,
LiquifyTransformMode,
PerspectiveTransformMode
};
Q_ENUMS(TransformToolMode)
enum WarpType {
RigidWarpType,
AffineWarpType,
SimilitudeWarpType
};
Q_ENUMS(WarpType)
KisToolTransform(KoCanvasBase * canvas);
virtual ~KisToolTransform();
virtual QWidget* createOptionWidget();
virtual void mousePressEvent(KoPointerEvent *e);
virtual void mouseMoveEvent(KoPointerEvent *e);
virtual void mouseReleaseEvent(KoPointerEvent *e);
virtual void touchEvent(QTouchEvent *event);
void beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action);
void continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action);
void endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action);
void activatePrimaryAction();
void deactivatePrimaryAction();
void beginPrimaryAction(KoPointerEvent *event);
void continuePrimaryAction(KoPointerEvent *event);
void endPrimaryAction(KoPointerEvent *event);
void activateAlternateAction(AlternateAction action);
void deactivateAlternateAction(AlternateAction action);
void beginAlternateAction(KoPointerEvent *event, AlternateAction action);
void continueAlternateAction(KoPointerEvent *event, AlternateAction action);
void endAlternateAction(KoPointerEvent *event, AlternateAction action);
void paint(QPainter& gc, const KoViewConverter &converter);
TransformToolMode transformMode() const;
double translateX() const;
double translateY() const;
double rotateX() const;
double rotateY() const;
double rotateZ() const;
double scaleX() const;
double scaleY() const;
double shearX() const;
double shearY() const;
WarpType warpType() const;
double warpFlexibility() const;
int warpPointDensity() const;
bool wantsTouch() const { return true; }
public Q_SLOTS:
virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
virtual void deactivate();
// Applies the current transformation to the original paint device and commits it to the undo stack
void applyTransform();
void setTransformMode( KisToolTransform::TransformToolMode newMode );
void setTranslateX(double translateX);
void setTranslateY(double translateY);
void setRotateX(double rotation);
void setRotateY(double rotation);
void setRotateZ(double rotation);
void setScaleX(double scaleX);
void setScaleY(double scaleY);
void setShearX(double shearX);
void setShearY(double shearY);
void setWarpType(WarpType type);
void setWarpFlexibility(double flexibility);
void setWarpPointDensity(int density);
protected Q_SLOTS:
virtual void resetCursorStyle();
Q_SIGNALS:
void transformModeChanged();
void freeTransformChanged();
void warpTransformChanged();
public Q_SLOTS:
void requestUndoDuringStroke();
void requestStrokeEnd();
void requestStrokeCancellation();
void canvasUpdateRequested();
void cursorOutlineUpdateRequested(const QPointF &imagePos);
// Update the widget according to m_currentArgs
void updateOptionWidget();
void resetRotationCenterButtonsRequested();
void imageTooBigRequested(bool value);
private:
- bool clearDevices(KisNodeSP node, bool recursive);
- void transformDevices(KisNodeSP node, bool recursive);
+ QList<KisNodeSP> fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive);
+
+ bool clearDevices(const QList<KisNodeSP> &nodes);
+ void transformClearedDevices();
void startStroke(ToolTransformArgs::TransformMode mode, bool forceReset);
void endStroke();
void cancelStroke();
private:
void outlineChanged();
// Sets the cursor according to mouse position (doesn't take shearing into account well yet)
void setFunctionalCursor();
// Sets m_function according to mouse position and modifier
void setTransformFunction(QPointF mousePos, Qt::KeyboardModifiers modifiers);
void commitChanges();
bool tryInitTransformModeFromNode(KisNodeSP node);
bool tryFetchArgsFromCommandAndUndo(ToolTransformArgs *args, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode);
void initTransformMode(ToolTransformArgs::TransformMode mode);
void initGuiAfterTransformMode();
void initThumbnailImage(KisPaintDeviceSP previewDevice);
void updateSelectionPath();
void updateApplyResetAvailability();
+ void forceRepaintDelayedLayers(KisNodeSP root);
+
private:
ToolTransformArgs m_currentArgs;
bool m_actuallyMoveWhileSelected; // true <=> selection has been moved while clicked
KisPaintDeviceSP m_selectedPortionCache;
struct StrokeData {
StrokeData() {}
StrokeData(KisStrokeId strokeId) : m_strokeId(strokeId) {}
void clear() {
m_strokeId.clear();
m_clearedNodes.clear();
}
const KisStrokeId strokeId() const { return m_strokeId; }
void addClearedNode(KisNodeSP node) { m_clearedNodes.append(node); }
const QVector<KisNodeWSP>& clearedNodes() const { return m_clearedNodes; }
private:
KisStrokeId m_strokeId;
QVector<KisNodeWSP> m_clearedNodes;
};
StrokeData m_strokeData;
bool m_workRecursively;
QPainterPath m_selectionPath; // original (unscaled) selection outline, used for painting decorations
KisToolTransformConfigWidget *m_optionsWidget;
QPointer<KisCanvas2> m_canvas;
TransformTransactionProperties m_transaction;
TransformChangesTracker m_changesTracker;
/**
* This artificial rect is used to store the image to flake
* transformation. We check against this rect to get to know
* whether zoom has changed.
*/
QRectF m_refRect;
QScopedPointer<KisWarpTransformStrategy> m_warpStrategy;
QScopedPointer<KisCageTransformStrategy> m_cageStrategy;
QScopedPointer<KisLiquifyTransformStrategy> m_liquifyStrategy;
QScopedPointer<KisFreeTransformStrategy> m_freeStrategy;
QScopedPointer<KisPerspectiveTransformStrategy> m_perspectiveStrategy;
KisTransformStrategyBase* currentStrategy() const;
QPainterPath m_cursorOutline;
private Q_SLOTS:
void slotTrackerChangedConfig();
void slotUiChangedConfig();
void slotApplyTransform();
void slotResetTransform();
void slotRestartTransform();
void slotEditingFinished();
};
class KisToolTransformFactory : public KoToolFactoryBase
{
public:
KisToolTransformFactory()
: KoToolFactoryBase("KisToolTransform") {
setToolTip(i18n("Transform a layer or a selection"));
setSection(TOOL_TYPE_TRANSFORM);
setIconName(koIconNameCStr("krita_tool_transform"));
setShortcut(QKeySequence(Qt::CTRL + Qt::Key_T));
setPriority(2);
setActivationShapeId(KRITA_TOOL_ACTIVATION_ID);
}
virtual ~KisToolTransformFactory() {}
virtual KoToolBase * createTool(KoCanvasBase *canvas) {
return new KisToolTransform(canvas);
}
};
#endif // KIS_TOOL_TRANSFORM_H_
diff --git a/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
index 26b70b422b..ba5b4668d7 100644
--- a/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
+++ b/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
@@ -1,591 +1,591 @@
/*
* 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_warp_transform_strategy.h"
#include <algorithm>
#include <QPointF>
#include <QPainter>
#include "kis_coordinates_converter.h"
#include "tool_transform_args.h"
#include "transform_transaction_properties.h"
-#include "krita_utils.h"
+#include "kis_painting_tweaks.h"
#include "kis_cursor.h"
#include "kis_transform_utils.h"
#include "kis_algebra_2d.h"
struct KisWarpTransformStrategy::Private
{
Private(KisWarpTransformStrategy *_q,
const KisCoordinatesConverter *_converter,
ToolTransformArgs &_currentArgs,
TransformTransactionProperties &_transaction)
: q(_q),
converter(_converter),
currentArgs(_currentArgs),
transaction(_transaction),
lastNumPoints(0),
drawConnectionLines(true),
drawOrigPoints(true),
drawTransfPoints(true),
closeOnStartPointClick(false),
clipOriginalPointsPosition(true),
pointWasDragged(false)
{
}
KisWarpTransformStrategy * const q;
/// standard members ///
const KisCoordinatesConverter *converter;
//////
ToolTransformArgs &currentArgs;
//////
TransformTransactionProperties &transaction;
QTransform paintingTransform;
QPointF paintingOffset;
QTransform handlesTransform;
/// custom members ///
QImage transformedImage;
int pointIndexUnderCursor;
enum Mode {
OVER_POINT = 0,
MULTIPLE_POINT_SELECTION,
MOVE_MODE,
ROTATE_MODE,
SCALE_MODE,
NOTHING
};
Mode mode;
QVector<int> pointsInAction;
int lastNumPoints;
bool drawConnectionLines;
bool drawOrigPoints;
bool drawTransfPoints;
bool closeOnStartPointClick;
bool clipOriginalPointsPosition;
QPointF pointPosOnClick;
bool pointWasDragged;
QPointF lastMousePos;
void recalculateTransformations();
inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
bool shouldCloseTheCage() const;
QVector<QPointF*> getSelectedPoints(QPointF *center, bool limitToSelectedOnly = false) const;
};
KisWarpTransformStrategy::KisWarpTransformStrategy(const KisCoordinatesConverter *converter,
ToolTransformArgs &currentArgs,
TransformTransactionProperties &transaction)
: KisSimplifiedActionPolicyStrategy(converter),
m_d(new Private(this, converter, currentArgs, transaction))
{
}
KisWarpTransformStrategy::~KisWarpTransformStrategy()
{
}
void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive)
{
double handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
bool cursorOverPoint = false;
m_d->pointIndexUnderCursor = -1;
KisTransformUtils::HandleChooser<Private::Mode>
handleChooser(mousePos, Private::NOTHING);
const QVector<QPointF> &points = m_d->currentArgs.transfPoints();
for (int i = 0; i < points.size(); ++i) {
if (handleChooser.addFunction(points[i],
handleRadius, Private::NOTHING)) {
cursorOverPoint = true;
m_d->pointIndexUnderCursor = i;
}
}
if (cursorOverPoint) {
m_d->mode = perspectiveModifierActive &&
!m_d->currentArgs.isEditingTransformPoints() ?
Private::MULTIPLE_POINT_SELECTION : Private::OVER_POINT;
} else if (!m_d->currentArgs.isEditingTransformPoints()) {
QPolygonF polygon(m_d->currentArgs.transfPoints());
bool insidePolygon = polygon.boundingRect().contains(mousePos);
m_d->mode = insidePolygon ? Private::MOVE_MODE :
!perspectiveModifierActive ? Private::ROTATE_MODE :
Private::SCALE_MODE;
} else {
m_d->mode = Private::NOTHING;
}
}
QCursor KisWarpTransformStrategy::getCurrentCursor() const
{
QCursor cursor;
switch (m_d->mode) {
case Private::OVER_POINT:
cursor = KisCursor::pointingHandCursor();
break;
case Private::MULTIPLE_POINT_SELECTION:
cursor = KisCursor::crossCursor();
break;
case Private::MOVE_MODE:
cursor = KisCursor::moveCursor();
break;
case Private::ROTATE_MODE:
cursor = KisCursor::rotateCursor();
break;
case Private::SCALE_MODE:
cursor = KisCursor::sizeVerCursor();
break;
case Private::NOTHING:
cursor = KisCursor::arrowCursor();
break;
}
return cursor;
}
void KisWarpTransformStrategy::overrideDrawingItems(bool drawConnectionLines,
bool drawOrigPoints,
bool drawTransfPoints)
{
m_d->drawConnectionLines = drawConnectionLines;
m_d->drawOrigPoints = drawOrigPoints;
m_d->drawTransfPoints = drawTransfPoints;
}
void KisWarpTransformStrategy::setCloseOnStartPointClick(bool value)
{
m_d->closeOnStartPointClick = value;
}
void KisWarpTransformStrategy::setClipOriginalPointsPosition(bool value)
{
m_d->clipOriginalPointsPosition = value;
}
void KisWarpTransformStrategy::drawConnectionLines(QPainter &gc,
const QVector<QPointF> &origPoints,
const QVector<QPointF> &transfPoints,
bool isEditingPoints)
{
Q_UNUSED(isEditingPoints);
QPen antsPen;
QPen outlinePen;
- KritaUtils::initAntsPen(&antsPen, &outlinePen);
+ KisPaintingTweaks::initAntsPen(&antsPen, &outlinePen);
const int numPoints = origPoints.size();
for (int i = 0; i < numPoints; ++i) {
gc.setPen(outlinePen);
gc.drawLine(transfPoints[i], origPoints[i]);
gc.setPen(antsPen);
gc.drawLine(transfPoints[i], origPoints[i]);
}
}
void KisWarpTransformStrategy::paint(QPainter &gc)
{
// Draw preview image
gc.save();
gc.setOpacity(m_d->transaction.basePreviewOpacity());
gc.setTransform(m_d->paintingTransform, true);
gc.drawImage(m_d->paintingOffset, m_d->transformedImage);
gc.restore();
gc.save();
gc.setTransform(m_d->handlesTransform, true);
// draw connecting lines
if (m_d->drawConnectionLines) {
gc.setOpacity(0.5);
drawConnectionLines(gc,
m_d->currentArgs.origPoints(),
m_d->currentArgs.transfPoints(),
m_d->currentArgs.isEditingTransformPoints());
}
// draw handles
{
const int numPoints = m_d->currentArgs.origPoints().size();
QPen mainPen(Qt::black);
QPen outlinePen(Qt::white);
qreal handlesExtraScale = KisTransformUtils::scaleFromAffineMatrix(m_d->handlesTransform);
qreal dstIn = 8 / handlesExtraScale;
qreal dstOut = 10 / handlesExtraScale;
qreal srcIn = 6 / handlesExtraScale;
qreal srcOut = 6 / handlesExtraScale;
QRectF handleRect1(-0.5 * dstIn, -0.5 * dstIn, dstIn, dstIn);
QRectF handleRect2(-0.5 * dstOut, -0.5 * dstOut, dstOut, dstOut);
if (m_d->drawTransfPoints) {
gc.setOpacity(1.0);
for (int i = 0; i < numPoints; ++i) {
gc.setPen(outlinePen);
gc.drawEllipse(handleRect2.translated(m_d->currentArgs.transfPoints()[i]));
gc.setPen(mainPen);
gc.drawEllipse(handleRect1.translated(m_d->currentArgs.transfPoints()[i]));
}
QPointF center;
QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center, true);
QBrush selectionBrush = selectedPoints.size() > 1 ? Qt::red : Qt::black;
QBrush oldBrush = gc.brush();
gc.setBrush(selectionBrush);
Q_FOREACH (const QPointF *pt, selectedPoints) {
gc.drawEllipse(handleRect1.translated(*pt));
}
gc.setBrush(oldBrush);
}
if (m_d->drawOrigPoints) {
QPainterPath inLine;
inLine.moveTo(-0.5 * srcIn, 0);
inLine.lineTo( 0.5 * srcIn, 0);
inLine.moveTo( 0, -0.5 * srcIn);
inLine.lineTo( 0, 0.5 * srcIn);
QPainterPath outLine;
outLine.moveTo(-0.5 * srcOut, -0.5 * srcOut);
outLine.lineTo( 0.5 * srcOut, -0.5 * srcOut);
outLine.lineTo( 0.5 * srcOut, 0.5 * srcOut);
outLine.lineTo(-0.5 * srcOut, 0.5 * srcOut);
outLine.lineTo(-0.5 * srcOut, -0.5 * srcOut);
gc.setOpacity(0.5);
for (int i = 0; i < numPoints; ++i) {
gc.setPen(outlinePen);
gc.drawPath(outLine.translated(m_d->currentArgs.origPoints()[i]));
gc.setPen(mainPen);
gc.drawPath(inLine.translated(m_d->currentArgs.origPoints()[i]));
}
}
}
gc.restore();
}
void KisWarpTransformStrategy::externalConfigChanged()
{
if (m_d->lastNumPoints != m_d->currentArgs.transfPoints().size()) {
m_d->pointsInAction.clear();
}
m_d->recalculateTransformations();
}
bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
{
const bool isEditingPoints = m_d->currentArgs.isEditingTransformPoints();
bool retval = false;
if (m_d->mode == Private::OVER_POINT ||
m_d->mode == Private::MULTIPLE_POINT_SELECTION ||
m_d->mode == Private::MOVE_MODE ||
m_d->mode == Private::ROTATE_MODE ||
m_d->mode == Private::SCALE_MODE) {
retval = true;
} else if (isEditingPoints) {
QPointF newPos = m_d->clipOriginalPointsPosition ?
KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
pt;
m_d->currentArgs.refOriginalPoints().append(newPos);
m_d->currentArgs.refTransformedPoints().append(newPos);
m_d->mode = Private::OVER_POINT;
m_d->pointIndexUnderCursor = m_d->currentArgs.origPoints().size() - 1;
m_d->recalculateTransformations();
emit requestCanvasUpdate();
retval = true;
}
if (m_d->mode == Private::OVER_POINT) {
m_d->pointPosOnClick =
m_d->currentArgs.transfPoints()[m_d->pointIndexUnderCursor];
m_d->pointWasDragged = false;
m_d->pointsInAction.clear();
m_d->pointsInAction << m_d->pointIndexUnderCursor;
m_d->lastNumPoints = m_d->currentArgs.transfPoints().size();
} else if (m_d->mode == Private::MULTIPLE_POINT_SELECTION) {
QVector<int>::iterator it =
std::find(m_d->pointsInAction.begin(),
m_d->pointsInAction.end(),
m_d->pointIndexUnderCursor);
if (it != m_d->pointsInAction.end()) {
m_d->pointsInAction.erase(it);
} else {
m_d->pointsInAction << m_d->pointIndexUnderCursor;
}
m_d->lastNumPoints = m_d->currentArgs.transfPoints().size();
}
m_d->lastMousePos = pt;
return retval;
}
QVector<QPointF*> KisWarpTransformStrategy::Private::getSelectedPoints(QPointF *center, bool limitToSelectedOnly) const
{
QVector<QPointF> &points = currentArgs.refTransformedPoints();
QRectF boundingRect;
QVector<QPointF*> selectedPoints;
if (limitToSelectedOnly || pointsInAction.size() > 1) {
Q_FOREACH (int index, pointsInAction) {
selectedPoints << &points[index];
KisAlgebra2D::accumulateBounds(points[index], &boundingRect);
}
} else {
QVector<QPointF>::iterator it = points.begin();
QVector<QPointF>::iterator end = points.end();
for (; it != end; ++it) {
selectedPoints << &(*it);
KisAlgebra2D::accumulateBounds(*it, &boundingRect);
}
}
*center = boundingRect.center();
return selectedPoints;
}
void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shiftModifierActve, bool altModifierActive)
{
Q_UNUSED(shiftModifierActve);
Q_UNUSED(altModifierActive);
// toplevel code switches to HOVER mode if nothing is selected
KIS_ASSERT_RECOVER_RETURN(m_d->mode == Private::MOVE_MODE ||
m_d->mode == Private::ROTATE_MODE ||
m_d->mode == Private::SCALE_MODE ||
(m_d->mode == Private::OVER_POINT &&
m_d->pointIndexUnderCursor >= 0 &&
m_d->pointsInAction.size() == 1) ||
(m_d->mode == Private::MULTIPLE_POINT_SELECTION &&
m_d->pointIndexUnderCursor >= 0));
if (m_d->mode == Private::OVER_POINT) {
if (m_d->currentArgs.isEditingTransformPoints()) {
QPointF newPos = m_d->clipOriginalPointsPosition ?
KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
pt;
m_d->currentArgs.origPoint(m_d->pointIndexUnderCursor) = newPos;
m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = newPos;
} else {
m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = pt;
}
const qreal handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
qreal dist =
kisSquareDistance(
m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor),
m_d->pointPosOnClick);
if (dist > handleRadiusSq) {
m_d->pointWasDragged = true;
}
} else if (m_d->mode == Private::MOVE_MODE) {
QPointF center;
QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
QPointF diff = pt - m_d->lastMousePos;
QVector<QPointF*>::iterator it = selectedPoints.begin();
QVector<QPointF*>::iterator end = selectedPoints.end();
for (; it != end; ++it) {
**it += diff;
}
} else if (m_d->mode == Private::ROTATE_MODE) {
QPointF center;
QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
QPointF oldDirection = m_d->lastMousePos - center;
QPointF newDirection = pt - center;
qreal rotateAngle = KisAlgebra2D::angleBetweenVectors(oldDirection, newDirection);
QTransform R;
R.rotateRadians(rotateAngle);
QTransform t =
QTransform::fromTranslate(-center.x(), -center.y()) *
R *
QTransform::fromTranslate(center.x(), center.y());
QVector<QPointF*>::iterator it = selectedPoints.begin();
QVector<QPointF*>::iterator end = selectedPoints.end();
for (; it != end; ++it) {
**it = t.map(**it);
}
} else if (m_d->mode == Private::SCALE_MODE) {
QPointF center;
QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
QPolygonF polygon(m_d->currentArgs.origPoints());
QSizeF maxSize = polygon.boundingRect().size();
qreal maxDimension = qMax(maxSize.width(), maxSize.height());
qreal scale = 1.0 - (pt - m_d->lastMousePos).y() / maxDimension;
QTransform t =
QTransform::fromTranslate(-center.x(), -center.y()) *
QTransform::fromScale(scale, scale) *
QTransform::fromTranslate(center.x(), center.y());
QVector<QPointF*>::iterator it = selectedPoints.begin();
QVector<QPointF*>::iterator end = selectedPoints.end();
for (; it != end; ++it) {
**it = t.map(**it);
}
}
m_d->lastMousePos = pt;
m_d->recalculateTransformations();
emit requestCanvasUpdate();
}
bool KisWarpTransformStrategy::Private::shouldCloseTheCage() const
{
return currentArgs.isEditingTransformPoints() &&
closeOnStartPointClick &&
pointIndexUnderCursor == 0 &&
currentArgs.origPoints().size() > 2 &&
!pointWasDragged;
}
bool KisWarpTransformStrategy::acceptsClicks() const
{
return m_d->shouldCloseTheCage() ||
m_d->currentArgs.isEditingTransformPoints();
}
bool KisWarpTransformStrategy::endPrimaryAction()
{
if (m_d->shouldCloseTheCage()) {
m_d->currentArgs.setEditingTransformPoints(false);
}
return true;
}
inline QPointF KisWarpTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization)
{
return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt);
}
void KisWarpTransformStrategy::Private::recalculateTransformations()
{
QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter);
QTransform resultTransform = q->thumbToImageTransform() * scaleTransform;
qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultTransform);
bool useFlakeOptimization = scale < 1.0;
QVector<QPointF> thumbOrigPoints(currentArgs.numPoints());
QVector<QPointF> thumbTransfPoints(currentArgs.numPoints());
for (int i = 0; i < currentArgs.numPoints(); ++i) {
thumbOrigPoints[i] = imageToThumb(currentArgs.origPoints()[i], useFlakeOptimization);
thumbTransfPoints[i] = imageToThumb(currentArgs.transfPoints()[i], useFlakeOptimization);
}
paintingOffset = transaction.originalTopLeft();
if (!q->originalImage().isNull() && !currentArgs.isEditingTransformPoints()) {
QPointF origTLInFlake = imageToThumb(transaction.originalTopLeft(), useFlakeOptimization);
if (useFlakeOptimization) {
transformedImage = q->originalImage().transformed(q->thumbToImageTransform() * scaleTransform);
paintingTransform = QTransform();
} else {
transformedImage = q->originalImage();
paintingTransform = q->thumbToImageTransform() * scaleTransform;
}
transformedImage = q->calculateTransformedImage(currentArgs,
transformedImage,
thumbOrigPoints,
thumbTransfPoints,
origTLInFlake,
&paintingOffset);
} else {
transformedImage = q->originalImage();
paintingOffset = imageToThumb(transaction.originalTopLeft(), false);
paintingTransform = q->thumbToImageTransform() * scaleTransform;
}
handlesTransform = scaleTransform;
}
QImage KisWarpTransformStrategy::calculateTransformedImage(ToolTransformArgs &currentArgs,
const QImage &srcImage,
const QVector<QPointF> &origPoints,
const QVector<QPointF> &transfPoints,
const QPointF &srcOffset,
QPointF *dstOffset)
{
return KisWarpTransformWorker::transformQImage(
currentArgs.warpType(),
origPoints, transfPoints,
currentArgs.alpha(),
srcImage,
srcOffset, dstOffset);
}
diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
index 5bcd66f8db..0e7c820c35 100644
--- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
+++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
@@ -1,318 +1,315 @@
/*
* 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_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"
TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode,
KisSelectionSP selection,
KisStrokeUndoFacade *undoFacade)
: KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade),
m_selection(selection)
{
if (rootNode->childCount() || !rootNode->paintDevice()) {
KisPaintDeviceSP device;
if (KisTransformMask* tmask =
dynamic_cast<KisTransformMask*>(rootNode.data())) {
device = tmask->buildPreviewDevice();
/**
* When working with transform mask, selections are not
* taken into account.
*/
m_selection = 0;
} else {
rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection();
device = rootNode->projection();
}
m_previewDevice = createDeviceCache(device);
} else {
m_previewDevice = createDeviceCache(rootNode->paintDevice());
putDeviceCache(rootNode->paintDevice(), m_previewDevice);
}
Q_ASSERT(m_previewDevice);
m_savedRootNode = rootNode;
}
TransformStrokeStrategy::~TransformStrokeStrategy()
{
}
KisPaintDeviceSP TransformStrokeStrategy::previewDevice() const
{
return m_previewDevice;
}
KisSelectionSP TransformStrokeStrategy::realSelection() const
{
return m_selection;
}
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);
if(td) {
m_savedTransformArgs = td->config;
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());
} if (KisExternalLayer *extLayer =
dynamic_cast<KisExternalLayer*>(td->node.data())) {
if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
- td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
-
- if (td->config.aX() || td->config.aY()) {
- warnKrita << "Perspective transform of an external layer is not supported:" << extLayer->name();
- }
+ (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);
} 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;
};
void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command)
{
TransformExtraData *data = new TransformExtraData();
data->savedTransformArgs = m_savedTransformArgs;
data->rootNode = m_savedRootNode;
command->setExtraData(data);
}
bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode)
{
const TransformExtraData *data = dynamic_cast<const TransformExtraData*>(command->extraData());
if (data) {
*args = data->savedTransformArgs;
*rootNode = data->rootNode;
}
return bool(data);
}
void TransformStrokeStrategy::initStrokeCallback()
{
KisStrokeStrategyUndoCommandBased::initStrokeCallback();
if (m_selection) {
m_selection->setVisible(false);
}
}
void TransformStrokeStrategy::finishStrokeCallback()
{
if (m_selection) {
m_selection->setVisible(true);
}
KisStrokeStrategyUndoCommandBased::finishStrokeCallback();
}
void TransformStrokeStrategy::cancelStrokeCallback()
{
KisStrokeStrategyUndoCommandBased::cancelStrokeCallback();
if (m_selection) {
m_selection->setVisible(true);
}
}
diff --git a/plugins/tools/tool_transform2/transform_transaction_properties.h b/plugins/tools/tool_transform2/transform_transaction_properties.h
index 82dc2b89a5..2005f15220 100644
--- a/plugins/tools/tool_transform2/transform_transaction_properties.h
+++ b/plugins/tools/tool_transform2/transform_transaction_properties.h
@@ -1,121 +1,146 @@
/*
* 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 __TRANSFORM_TRANSACTION_PROPERTIES_H
#define __TRANSFORM_TRANSACTION_PROPERTIES_H
#include <QRectF>
#include <QPointF>
#include "kis_node.h"
+#include "kis_layer_utils.h"
+#include "kis_external_layer_iface.h"
class ToolTransformArgs;
class TransformTransactionProperties
{
public:
TransformTransactionProperties()
{
}
-TransformTransactionProperties(const QRectF &originalRect, ToolTransformArgs *currentConfig, KisNodeSP rootNode)
+TransformTransactionProperties(const QRectF &originalRect,
+ ToolTransformArgs *currentConfig,
+ KisNodeSP rootNode,
+ const QList<KisNodeSP> &transformedNodes)
: m_originalRect(originalRect),
m_currentConfig(currentConfig),
- m_rootNode(rootNode)
+ m_rootNode(rootNode),
+ m_shouldAvoidPerspectiveTransform(false),
+ m_transformedNodes(transformedNodes)
{
+ Q_FOREACH (KisNodeSP node, m_transformedNodes) {
+ if (KisExternalLayer *extLayer = dynamic_cast<KisExternalLayer*>(node.data())) {
+ if (!extLayer->supportsPerspectiveTransform()) {
+ m_shouldAvoidPerspectiveTransform = true;
+ break;
+ }
+ }
+ }
}
qreal originalHalfWidth() const {
return m_originalRect.width() / 2.0;
}
qreal originalHalfHeight() const {
return m_originalRect.height() / 2.0;
}
QRectF originalRect() const {
return m_originalRect;
}
QPointF originalCenterGeometric() const {
return m_originalRect.center();
}
QPointF originalTopLeft() const {
return m_originalRect.topLeft();
}
QPointF originalBottomLeft() const {
return m_originalRect.bottomLeft();
}
QPointF originalBottomRight() const {
return m_originalRect.bottomRight();
}
QPointF originalTopRight() const {
return m_originalRect.topRight();
}
QPointF originalMiddleLeft() const {
return QPointF(m_originalRect.left(), (m_originalRect.top() + m_originalRect.bottom()) / 2.0);
}
QPointF originalMiddleRight() const {
return QPointF(m_originalRect.right(), (m_originalRect.top() + m_originalRect.bottom()) / 2.0);
}
QPointF originalMiddleTop() const {
return QPointF((m_originalRect.left() + m_originalRect.right()) / 2.0, m_originalRect.top());
}
QPointF originalMiddleBottom() const {
return QPointF((m_originalRect.left() + m_originalRect.right()) / 2.0, m_originalRect.bottom());
}
QPoint originalTopLeftAligned() const {
return m_originalRect.toAlignedRect().topLeft();
}
QPoint originalBottomRightAligned() const {
return m_originalRect.toAlignedRect().bottomRight();
}
ToolTransformArgs* currentConfig() const {
return m_currentConfig;
}
KisNodeSP rootNode() const {
return m_rootNode;
}
qreal basePreviewOpacity() const {
return 0.9 * qreal(m_rootNode->opacity()) / 255.0;
}
+ bool shouldAvoidPerspectiveTransform() const {
+ return m_shouldAvoidPerspectiveTransform;
+ }
+
+ QList<KisNodeSP> nodesList() const {
+ return m_transformedNodes;
+ }
+
private:
/**
* Information about the original selected rect
* (before any transformations)
*/
QRectF m_originalRect;
ToolTransformArgs *m_currentConfig;
KisNodeSP m_rootNode;
+ bool m_shouldAvoidPerspectiveTransform;
+ QList<KisNodeSP> m_transformedNodes;
};
#endif /* __TRANSFORM_TRANSACTION_PROPERTIES_H */
diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h
index 250a181458..e3b55590fd 100644
--- a/sdk/tests/filestest.h
+++ b/sdk/tests/filestest.h
@@ -1,121 +1,119 @@
/*
* 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 <KisPart.h>
#include <QTemporaryFile>
#include <QFileInfo>
+#include <QApplication>
namespace TestUtil
{
void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0)
{
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);
manager.setBatchMode(true);
KisImportExportFilter::ConversionStatus 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);
}
- QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".png"));
- tmpFile.open();
- doc->setBackupFile(false);
- doc->setOutputMimeType("image/png");
- doc->setFileBatchMode(true);
- doc->saveAs(QUrl("file://" + tmpFile.fileName()));
+ 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);
- QImage sourceImage(tmpFile.fileName());
sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32);
- tmpFile.close();
-
QPoint pt;
if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy)) {
failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1();
- resultImage.save(sourceFileInfo.fileName() + ".png");
+ 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");
}
}
#endif
diff --git a/sdk/tests/qimage_test_util.h b/sdk/tests/qimage_test_util.h
new file mode 100644
index 0000000000..e1d0a294ce
--- /dev/null
+++ b/sdk/tests/qimage_test_util.h
@@ -0,0 +1,245 @@
+/*
+ * 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 QIMAGE_TEST_UTIL_H
+#define QIMAGE_TEST_UTIL_H
+
+#ifdef FILES_OUTPUT_DIR
+
+#include <QProcessEnvironment>
+#include <QDir>
+
+namespace TestUtil {
+
+inline QString fetchExternalDataFileName(const QString relativeFileName)
+{
+ static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR";
+
+ QString path;
+ if (!env.contains(unittestsDataDirPath)) {
+ warnKrita << "Environment variable" << unittestsDataDirPath << "is not set";
+ return QString();
+ } else {
+ path = env.value(unittestsDataDirPath, "");
+ }
+
+ QString filename =
+ path +
+ QDir::separator() +
+ relativeFileName;
+
+ return filename;
+}
+
+inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false)
+{
+ if (externalTest) {
+ return fetchExternalDataFileName(relativeFileName);
+ } else {
+ QString filename =
+ QString(FILES_DATA_DIR) +
+ QDir::separator() +
+ relativeFileName;
+
+ if (QFileInfo(filename).exists()) {
+ return filename;
+ }
+
+ filename =
+ QString(FILES_DEFAULT_DATA_DIR) +
+ QDir::separator() +
+ relativeFileName;
+
+ if (QFileInfo(filename).exists()) {
+ return filename;
+ }
+ }
+
+ return QString();
+}
+
+// quint8 arguments are automatically converted into int
+inline bool compareChannels(int ch1, int ch2, int fuzzy)
+{
+ return qAbs(ch1 - ch2) <= fuzzy;
+}
+
+inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0)
+{
+ // QTime t;
+ // t.start();
+
+ const int w1 = image1.width();
+ const int h1 = image1.height();
+ const int w2 = image2.width();
+ const int h2 = image2.height();
+ const int bytesPerLine = image1.bytesPerLine();
+
+ if (w1 != w2 || h1 != h2) {
+ pt.setX(-1);
+ pt.setY(-1);
+ qDebug() << "Images have different sizes" << image1.size() << image2.size();
+ return false;
+ }
+
+ int numFailingPixels = 0;
+
+ for (int y = 0; y < h1; ++y) {
+ const QRgb * const firstLine = reinterpret_cast<const QRgb *>(image2.scanLine(y));
+ const QRgb * const secondLine = reinterpret_cast<const QRgb *>(image1.scanLine(y));
+
+ if (memcmp(firstLine, secondLine, bytesPerLine) != 0) {
+ for (int x = 0; x < w1; ++x) {
+ const QRgb a = firstLine[x];
+ const QRgb b = secondLine[x];
+ const bool same =
+ compareChannels(qRed(a), qRed(b), fuzzy) &&
+ compareChannels(qGreen(a), qGreen(b), fuzzy) &&
+ compareChannels(qBlue(a), qBlue(b), fuzzy);
+ const bool sameAlpha = compareChannels(qAlpha(a), qAlpha(b), fuzzyAlpha);
+ const bool bothTransparent = sameAlpha && qAlpha(a)==0;
+
+ if (!bothTransparent && (!same || !sameAlpha)) {
+ pt.setX(x);
+ pt.setY(y);
+ numFailingPixels++;
+
+ qDebug() << " Different at" << pt
+ << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a)
+ << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b)
+ << "fuzzy" << fuzzy
+ << "fuzzyAlpha" << fuzzyAlpha
+ << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )";
+
+
+ if (numFailingPixels > maxNumFailingPixels) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ // qDebug() << "compareQImages time elapsed:" << t.elapsed();
+ // qDebug() << "Images are identical";
+ return true;
+}
+
+inline bool checkQImageImpl(bool externalTest,
+ const QImage &srcImage, const QString &testName,
+ const QString &prefix, const QString &name,
+ int fuzzy, int fuzzyAlpha, int maxNumFailingPixels)
+{
+ QImage image = srcImage.convertToFormat(QImage::Format_ARGB32);
+
+ if (fuzzyAlpha == -1) {
+ fuzzyAlpha = fuzzy;
+ }
+
+ QString filename(prefix + "_" + name + ".png");
+ QString dumpName(prefix + "_" + name + "_expected.png");
+
+ const QString standardPath =
+ testName + QDir::separator() +
+ prefix + QDir::separator() + filename;
+
+ QString fullPath = fetchDataFileLazy(standardPath, externalTest);
+
+ if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
+ // Try without the testname subdirectory
+ fullPath = fetchDataFileLazy(prefix + QDir::separator() +
+ filename,
+ externalTest);
+ }
+
+ if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
+ // Try without the prefix subdirectory
+ fullPath = fetchDataFileLazy(testName + QDir::separator() +
+ filename,
+ externalTest);
+ }
+
+ if (!QFileInfo(fullPath).exists()) {
+ fullPath = "";
+ }
+
+ bool canSkipExternalTest = fullPath.isEmpty() && externalTest;
+ QImage ref(fullPath);
+
+ bool valid = true;
+ QPoint t;
+ if(!compareQImages(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels)) {
+ bool saveStandardResults = true;
+
+ if (canSkipExternalTest) {
+ static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS";
+
+ int writeUnittests = env.value(writeUnittestsVar, "0").toInt();
+ if (writeUnittests) {
+ QString path = fetchExternalDataFileName(standardPath);
+
+ QFileInfo pathInfo(path);
+ QDir directory;
+ directory.mkpath(pathInfo.path());
+
+ qDebug() << "--- Saving reference image:" << name << path;
+ image.save(path);
+ saveStandardResults = false;
+
+ } else {
+ qDebug() << "--- External image not found. Skipping..." << name;
+ }
+ } else {
+ qDebug() << "--- Wrong image:" << name;
+ valid = false;
+ }
+
+ if (saveStandardResults) {
+ image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename);
+ ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName);
+ }
+ }
+
+ return valid;
+}
+
+inline bool checkQImage(const QImage &image, const QString &testName,
+ const QString &prefix, const QString &name,
+ int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
+{
+ return checkQImageImpl(false, image, testName,
+ prefix, name,
+ fuzzy, fuzzyAlpha, maxNumFailingPixels);
+}
+
+inline bool checkQImageExternal(const QImage &image, const QString &testName,
+ const QString &prefix, const QString &name,
+ int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
+{
+ return checkQImageImpl(true, image, testName,
+ prefix, name,
+ fuzzy, fuzzyAlpha, maxNumFailingPixels);
+}
+
+}
+
+#endif // FILES_OUTPUT_DIR
+
+#endif // QIMAGE_TEST_UTIL_H
+
diff --git a/sdk/tests/testutil.h b/sdk/tests/testutil.h
index a850de05fc..f5bf219401 100644
--- a/sdk/tests/testutil.h
+++ b/sdk/tests/testutil.h
@@ -1,624 +1,419 @@
/*
* Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TEST_UTIL
#define TEST_UTIL
#include <QProcessEnvironment>
#include <QList>
#include <QTime>
#include <QDir>
#include <KoColorSpace.h>
#include <KoColorSpaceRegistry.h>
#include <KoColorProfile.h>
#include <KoProgressProxy.h>
#include <kis_paint_device.h>
#include <kis_node.h>
#include <kis_undo_adapter.h>
#include "kis_node_graph_listener.h"
#include "kis_iterator_ng.h"
#include "kis_image.h"
#include "testing_nodes.h"
#ifndef FILES_DATA_DIR
#define FILES_DATA_DIR "."
#endif
#ifndef FILES_DEFAULT_DATA_DIR
#define FILES_DEFAULT_DATA_DIR "."
#endif
+#include "qimage_test_util.h"
/**
* Routines that are useful for writing efficient tests
*/
namespace TestUtil
{
inline KisNodeSP findNode(KisNodeSP root, const QString &name) {
if(root->name() == name) return root;
KisNodeSP child = root->firstChild();
while (child) {
if((root = findNode(child, name))) return root;
child = child->nextSibling();
}
return KisNodeSP();
}
-
-inline QString fetchExternalDataFileName(const QString relativeFileName)
-{
- static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
- static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR";
-
- QString path;
- if (!env.contains(unittestsDataDirPath)) {
- warnKrita << "Environment variable" << unittestsDataDirPath << "is not set";
- return QString();
- } else {
- path = env.value(unittestsDataDirPath, "");
- }
-
- QString filename =
- path +
- QDir::separator() +
- relativeFileName;
-
- return filename;
-}
-
-inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false)
-{
- if (externalTest) {
- return fetchExternalDataFileName(relativeFileName);
- } else {
- QString filename =
- QString(FILES_DATA_DIR) +
- QDir::separator() +
- relativeFileName;
-
- if (QFileInfo(filename).exists()) {
- return filename;
- }
-
- filename =
- QString(FILES_DEFAULT_DATA_DIR) +
- QDir::separator() +
- relativeFileName;
-
- if (QFileInfo(filename).exists()) {
- return filename;
- }
- }
-
- return QString();
-}
-
inline void dumpNodeStack(KisNodeSP node, QString prefix = QString("\t"))
{
dbgKrita << node->name();
KisNodeSP child = node->firstChild();
while (child) {
if (child->childCount() > 0) {
dumpNodeStack(child, prefix + "\t");
} else {
dbgKrita << prefix << child->name();
}
child = child->nextSibling();
}
}
class TestProgressBar : public KoProgressProxy {
public:
TestProgressBar()
: m_min(0), m_max(0), m_value(0)
{}
int maximum() const {
return m_max;
}
void setValue(int value) {
m_value = value;
}
void setRange(int min, int max) {
m_min = min;
m_max = max;
}
void setFormat(const QString &format) {
m_format = format;
}
int min() { return m_min; }
int max() { return m_max; }
int value() { return m_value; }
QString format() { return m_format; }
private:
int m_min;
int m_max;
int m_value;
QString m_format;
};
-
-inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0)
-{
- // QTime t;
- // t.start();
-
- const int w1 = image1.width();
- const int h1 = image1.height();
- const int w2 = image2.width();
- const int h2 = image2.height();
- const int bytesPerLine = image1.bytesPerLine();
-
- if (w1 != w2 || h1 != h2) {
- pt.setX(-1);
- pt.setY(-1);
- dbgKrita << "Images have different sizes" << image1.size() << image2.size();
- return false;
- }
-
- int numFailingPixels = 0;
-
- for (int y = 0; y < h1; ++y) {
- const QRgb * const firstLine = reinterpret_cast<const QRgb *>(image2.scanLine(y));
- const QRgb * const secondLine = reinterpret_cast<const QRgb *>(image1.scanLine(y));
-
- if (memcmp(firstLine, secondLine, bytesPerLine) != 0) {
- for (int x = 0; x < w1; ++x) {
- const QRgb a = firstLine[x];
- const QRgb b = secondLine[x];
- const bool same = qAbs(qRed(a) - qRed(b)) <= fuzzy
- && qAbs(qGreen(a) - qGreen(b)) <= fuzzy
- && qAbs(qBlue(a) - qBlue(b)) <= fuzzy;
- const bool sameAlpha = qAlpha(a) - qAlpha(b) <= fuzzyAlpha;
- const bool bothTransparent = sameAlpha && qAlpha(a)==0;
-
- if (!bothTransparent && (!same || !sameAlpha)) {
- pt.setX(x);
- pt.setY(y);
- numFailingPixels++;
-
- dbgKrita << " Different at" << pt
- << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a)
- << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b)
- << "fuzzy" << fuzzy
- << "fuzzyAlpha" << fuzzyAlpha
- << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )";
-
-
- if (numFailingPixels > maxNumFailingPixels) {
- return false;
- }
- }
- }
- }
- }
- // dbgKrita << "compareQImages time elapsed:" << t.elapsed();
- // dbgKrita << "Images are identical";
- return true;
-}
-
inline bool comparePaintDevices(QPoint & pt, const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2)
{
// QTime t;
// t.start();
QRect rc1 = dev1->exactBounds();
QRect rc2 = dev2->exactBounds();
if (rc1 != rc2) {
pt.setX(-1);
pt.setY(-1);
}
KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
int pixelSize = dev1->pixelSize();
for (int y = 0; y < rc1.height(); ++y) {
do {
if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0)
return false;
} while (iter1->nextPixel() && iter2->nextPixel());
iter1->nextRow();
iter2->nextRow();
}
// dbgKrita << "comparePaintDevices time elapsed:" << t.elapsed();
return true;
}
template <typename channel_type>
inline bool comparePaintDevicesClever(const KisPaintDeviceSP dev1, const KisPaintDeviceSP dev2, channel_type alphaThreshold = 0)
{
QRect rc1 = dev1->exactBounds();
QRect rc2 = dev2->exactBounds();
if (rc1 != rc2) {
dbgKrita << "Devices have different size" << ppVar(rc1) << ppVar(rc2);
return false;
}
KisHLineConstIteratorSP iter1 = dev1->createHLineConstIteratorNG(0, 0, rc1.width());
KisHLineConstIteratorSP iter2 = dev2->createHLineConstIteratorNG(0, 0, rc1.width());
int pixelSize = dev1->pixelSize();
for (int y = 0; y < rc1.height(); ++y) {
do {
if (memcmp(iter1->oldRawData(), iter2->oldRawData(), pixelSize) != 0) {
const channel_type* p1 = reinterpret_cast<const channel_type*>(iter1->oldRawData());
const channel_type* p2 = reinterpret_cast<const channel_type*>(iter2->oldRawData());
if (p1[3] < alphaThreshold && p2[3] < alphaThreshold) continue;
dbgKrita << "Failed compare paint devices:" << iter1->x() << iter1->y();
dbgKrita << "src:" << p1[0] << p1[1] << p1[2] << p1[3];
dbgKrita << "dst:" << p2[0] << p2[1] << p2[2] << p2[3];
return false;
}
} while (iter1->nextPixel() && iter2->nextPixel());
iter1->nextRow();
iter2->nextRow();
}
return true;
}
#ifdef FILES_OUTPUT_DIR
-inline bool checkQImageImpl(bool externalTest,
- const QImage &srcImage, const QString &testName,
- const QString &prefix, const QString &name,
- int fuzzy, int fuzzyAlpha, int maxNumFailingPixels)
-{
- QImage image = srcImage.convertToFormat(QImage::Format_ARGB32);
-
- if (fuzzyAlpha == -1) {
- fuzzyAlpha = fuzzy;
- }
-
- QString filename(prefix + "_" + name + ".png");
- QString dumpName(prefix + "_" + name + "_expected.png");
-
- const QString standardPath =
- testName + QDir::separator() +
- prefix + QDir::separator() + filename;
-
- QString fullPath = fetchDataFileLazy(standardPath, externalTest);
-
- if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
- // Try without the testname subdirectory
- fullPath = fetchDataFileLazy(prefix + QDir::separator() +
- filename,
- externalTest);
- }
-
- if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) {
- // Try without the prefix subdirectory
- fullPath = fetchDataFileLazy(testName + QDir::separator() +
- filename,
- externalTest);
- }
-
- if (!QFileInfo(fullPath).exists()) {
- fullPath = "";
- }
-
- bool canSkipExternalTest = fullPath.isEmpty() && externalTest;
- QImage ref(fullPath);
-
- bool valid = true;
- QPoint t;
- if(!compareQImages(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels)) {
- bool saveStandardResults = true;
-
- if (canSkipExternalTest) {
- static QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
- static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS";
-
- int writeUnittests = env.value(writeUnittestsVar, "0").toInt();
- if (writeUnittests) {
- QString path = fetchExternalDataFileName(standardPath);
-
- QFileInfo pathInfo(path);
- QDir directory;
- directory.mkpath(pathInfo.path());
-
- dbgKrita << "--- Saving reference image:" << name << path;
- image.save(path);
- saveStandardResults = false;
-
- } else {
- dbgKrita << "--- External image not found. Skipping..." << name;
- }
- } else {
- dbgKrita << "--- Wrong image:" << name;
- valid = false;
- }
-
- if (saveStandardResults) {
- image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename);
- ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName);
- }
- }
-
- return valid;
-}
-
-inline bool checkQImage(const QImage &image, const QString &testName,
- const QString &prefix, const QString &name,
- int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
-{
- return checkQImageImpl(false, image, testName,
- prefix, name,
- fuzzy, fuzzyAlpha, maxNumFailingPixels);
-}
-
-inline bool checkQImageExternal(const QImage &image, const QString &testName,
- const QString &prefix, const QString &name,
- int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0)
-{
- return checkQImageImpl(true, image, testName,
- prefix, name,
- fuzzy, fuzzyAlpha, maxNumFailingPixels);
-}
-
struct ExternalImageChecker
{
ExternalImageChecker(const QString &prefix, const QString &testName)
: m_prefix(prefix),
m_testName(testName),
m_success(true),
m_maxFailingPixels(100),
m_fuzzy(1)
{
}
void setMaxFailingPixels(int value) {
m_maxFailingPixels = value;
}
void setFuzzy(int fuzzy){
m_fuzzy = fuzzy;
}
bool testPassed() const {
return m_success;
}
inline bool checkDevice(KisPaintDeviceSP device, KisImageSP image, const QString &caseName) {
bool result =
checkQImageExternal(device->convertToQImage(0, image->bounds()),
m_testName,
m_prefix,
caseName, m_fuzzy, m_fuzzy, m_maxFailingPixels);
m_success &= result;
return result;
}
inline bool checkImage(KisImageSP image, const QString &testName) {
bool result = checkDevice(image->projection(), image, testName);
m_success &= result;
return result;
}
private:
QString m_prefix;
QString m_testName;
bool m_success;
int m_maxFailingPixels;
int m_fuzzy;
};
#endif
inline quint8 alphaDevicePixel(KisPaintDeviceSP dev, qint32 x, qint32 y)
{
KisHLineConstIteratorSP iter = dev->createHLineConstIteratorNG(x, y, 1);
const quint8 *pix = iter->oldRawData();
return *pix;
}
inline void alphaDeviceSetPixel(KisPaintDeviceSP dev, qint32 x, qint32 y, quint8 s)
{
KisHLineIteratorSP iter = dev->createHLineIteratorNG(x, y, 1);
quint8 *pix = iter->rawData();
*pix = s;
}
inline bool checkAlphaDeviceFilledWithPixel(KisPaintDeviceSP dev, const QRect &rc, quint8 expected)
{
KisHLineIteratorSP it = dev->createHLineIteratorNG(rc.x(), rc.y(), rc.width());
for (int y = rc.y(); y < rc.y() + rc.height(); y++) {
for (int x = rc.x(); x < rc.x() + rc.width(); x++) {
if(*((quint8*)it->rawData()) != expected) {
errKrita << "At point:" << x << y;
errKrita << "Expected pixel:" << expected;
errKrita << "Actual pixel: " << *((quint8*)it->rawData());
return false;
}
it->nextPixel();
}
it->nextRow();
}
return true;
}
class TestNode : public DefaultNode
{
Q_OBJECT
public:
KisNodeSP clone() const {
return KisNodeSP(new TestNode(*this));
}
};
class TestGraphListener : public KisNodeGraphListener
{
public:
virtual void aboutToAddANode(KisNode *parent, int index) {
KisNodeGraphListener::aboutToAddANode(parent, index);
beforeInsertRow = true;
}
virtual void nodeHasBeenAdded(KisNode *parent, int index) {
KisNodeGraphListener::nodeHasBeenAdded(parent, index);
afterInsertRow = true;
}
virtual void aboutToRemoveANode(KisNode *parent, int index) {
KisNodeGraphListener::aboutToRemoveANode(parent, index);
beforeRemoveRow = true;
}
virtual void nodeHasBeenRemoved(KisNode *parent, int index) {
KisNodeGraphListener::nodeHasBeenRemoved(parent, index);
afterRemoveRow = true;
}
virtual void aboutToMoveNode(KisNode *parent, int oldIndex, int newIndex) {
KisNodeGraphListener::aboutToMoveNode(parent, oldIndex, newIndex);
beforeMove = true;
}
virtual void nodeHasBeenMoved(KisNode *parent, int oldIndex, int newIndex) {
KisNodeGraphListener::nodeHasBeenMoved(parent, oldIndex, newIndex);
afterMove = true;
}
bool beforeInsertRow;
bool afterInsertRow;
bool beforeRemoveRow;
bool afterRemoveRow;
bool beforeMove;
bool afterMove;
void resetBools() {
beforeRemoveRow = false;
afterRemoveRow = false;
beforeInsertRow = false;
afterInsertRow = false;
beforeMove = false;
afterMove = false;
}
};
}
#include <kis_paint_layer.h>
#include <kis_image.h>
#include "kis_undo_stores.h"
namespace TestUtil {
struct MaskParent
{
MaskParent(const QRect &_imageRect = QRect(0,0,512,512))
: imageRect(_imageRect) {
const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
undoStore = new KisSurrogateUndoStore();
image = new KisImage(undoStore, imageRect.width(), imageRect.height(), cs, "test image");
layer = KisPaintLayerSP(new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8));
image->addNode(KisNodeSP(layer.data()));
}
KisSurrogateUndoStore *undoStore;
const QRect imageRect;
KisImageSP image;
KisPaintLayerSP layer;
};
}
namespace TestUtil {
class MeasureAvgPortion
{
public:
MeasureAvgPortion(int period)
: m_period(period),
m_val(0),
m_total(0),
m_cycles(0)
{
}
~MeasureAvgPortion() {
printValues(true);
}
void addVal(int x) {
m_val += x;
}
void addTotal(int x) {
m_total += x;
m_cycles++;
printValues();
}
private:
void printValues(bool force = false) {
if (m_cycles > m_period || force) {
dbgKrita << "Val / Total:" << qreal(m_val) / qreal(m_total);
dbgKrita << "Avg. Val: " << qreal(m_val) / m_cycles;
dbgKrita << "Avg. Total: " << qreal(m_total) / m_cycles;
dbgKrita << ppVar(m_val) << ppVar(m_total) << ppVar(m_cycles);
m_val = 0;
m_total = 0;
m_cycles = 0;
}
}
private:
int m_period;
qint64 m_val;
qint64 m_total;
qint64 m_cycles;
};
QStringList getHierarchy(KisNodeSP root, const QString &prefix = "");
bool checkHierarchy(KisNodeSP root, const QStringList &expected);
}
#endif