diff --git a/3rdparty/README.md b/3rdparty/README.md index c7ce57d3c0..f0877cde9c 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,242 +1,240 @@ = CMake external projects to build krita's dependencies on Linux, Windows or OSX = If you need to build Krita's dependencies for the following reasons: * you develop on Windows and aren't using Craft * you develop on OSX and aren't using Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux * you develop on Linux, but some dependencies aren't available for your distribution and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. == Prerequisites == Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path 2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 instead. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this * Windows: mingw-w64 7.1 (by mingw-builds) - 32-bit (x86) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/7.1.0/threads-posix/dwarf/ - 64-bit (x64) target: https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.1.0/threads-posix/seh/ Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. MSVC is *not* supported at the moment. 4. On Windows, you will also need Python 3.6.2 (technically any versions of 3.6 is fine, but it's not tested): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure the version you download is exactly python-3.6.2. Make sure that specific version of Python comes first in your path. == Setup your environment == == Prepare your directory layout == 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace BUILDROOT with $BUILDROOT or %BUILDROOT whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT cd BUILDROOT git clone git://anongit.kde.org/krita.git 3. Create the build directory mkdir BUILDROOT/b 4. Create the downloads directory mkdir BUILDROOT/d 5. Create the install directory mkdir BUILDROOT/i == Prepare the externals build == 1. enter the BUILDROOT/b directory 2. run cmake: * Linux: export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i * OSX: export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i * Windows 32-bit / 64-bit: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% set PYTHONHOME=%BUILDROOT%/i (only if you want to build your own python) set PATH=BUILDROOT\i\bin\;BUILDROOT\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" - If you want to build Qt and some other dependencies with parallel jobs, add `-DSUBMAKE_JOBS=` to the cmake command where is the number of jobs to run (if your PC has 4 CPU cores, you might want to set it to 5). 3. build the packages: With a judicious application of DEPENDS statements, it's possible to build it all in one go, but in my experience that fails always, so it's better to build the dependencies independently. On Windows: cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico cmake --build . --config RelWithDebInfo --target ext_gettext On OSX: cmake --build . --config RelWithDebInfo --target ext_gettext On all operating systems: cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 On all operating systems cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr Note for OSX: On OSX, you need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups On All operating systems: cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw On Windows cmake --build . --config RelWithDebInfo --target ext_freetype cmake --build . --config RelWithDebInfo --target ext_poppler On Linux cmake --build . --config RelWithDebInfo --target ext_kcrash Everywhere else: cmake --build . --config RelWithDebInfo --target ext_kwindowsystem On Windows, if you want to include DrMingw for dumping backtrace on crash: cmake --build . --config RelWithDebInfo --target ext_drmingw On Windows, if you want to include Python scripting: cmake --build . --config RelWithDebInfo --target ext_python cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt 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 +Note 2: if you want to build a release, you need to get the binary gettext archives from files.kde.org/krita/build/dependencies: http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-32.zip http://files.kde.org/krita/build/dependencies/gettext0.19.8.1-iconv1.14-shared-64.zip Take care, these zips contain a libstdc++-6.dll that you don't want in your path when building. == Build Krita == 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Run On Windows Depending on what you want to use, run this command for MSBuild: cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 Or this to use jom (faster compiling, uses all cores, ships with QtCreator/pre-built Qt binaries): cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 On Linux cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfobg On OSX cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo 4. Run On Linux and OSX make make install On Windows Either use MSBuild to build (-- /m tells msbuild to use all your cores): cmake --build . --config RelWithDebInfo --target INSTALL -- /m Or use jom which should be in a path similar to C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe. So, from the same folder, instead of running cmake run: "C:\Qt\Qt5.6.0\Tools\QtCreator\bin\jom.exe" install 6. Run krita: On Linux BUILDROOT/i/bin/krita On Windows BUILDROOT\i\bin\krita.exe On OSX BUILDROOT/i/bin/krita.app/Contents/MacOS/krita == Packaging a Windows Build == If you want to create a stripped down version of Krita to distribute, after building everything just copy the package_2.cmd file from the "windows" folder inside krita root source folder to BUILDROOT and run it (most likely C:\dev\). That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. >>>>>>> origin/master diff --git a/3rdparty/ext_pthreads/CMakeLists.txt b/3rdparty/ext_pthreads/CMakeLists.txt deleted file mode 100755 index 42d539bab1..0000000000 --- a/3rdparty/ext_pthreads/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -SET(PREFIX_ext_pthreads "${EXTPREFIX}" ) -ExternalProject_Add( ext_pthreads - DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} - URL http://files.kde.org/krita/build/dependencies/pthreads-w32-2-9-1-release.tar.gz - URL_MD5 36ba827d6aa0fa9f9ae740a35626e2e3 - - INSTALL_DIR ${PREFIX_ext_pthreads} - PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/pthreads-w32-2-8-0-release-20110729.diff - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_pthreads} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} - - UPDATE_COMMAND "" - DEPENDS ext_patch -) diff --git a/3rdparty/ext_pthreads/pthreads-w32-2-9-1-release-20130901.diff b/3rdparty/ext_pthreads/pthreads-w32-2-9-1-release-20130901.diff deleted file mode 100644 index 432f236c1a..0000000000 --- a/3rdparty/ext_pthreads/pthreads-w32-2-9-1-release-20130901.diff +++ /dev/null @@ -1,593 +0,0 @@ -diff -Nru -x '*~' pthreads-w32-2-9-1-release.orig/CMakeLists.txt pthreads-w32-2-9-1-release/CMakeLists.txt ---- pthreads-w32-2-9-1-release.orig/CMakeLists.txt 1970-01-01 01:00:00.000000000 +0100 -+++ pthreads-w32-2-9-1-release/CMakeLists.txt 2013-09-01 13:00:26.055970400 +0200 -@@ -0,0 +1,224 @@ -+project(pthreads) -+ -+cmake_minimum_required(VERSION 2.8) -+ -+option(BUILD_STATIC "build pthreads as a static library" OFF) -+option(BUILD_INLINED "build pthreads as with inlined functions" ON) -+option(BUILD_TESTS "build test executables for pthreads" ON) -+ -+if(BUILD_TESTS) -+ enable_testing() -+endif(BUILD_TESTS) -+ -+if(MSVC) -+ add_definitions(-D_CRT_SECURE_NO_WARNINGS) -+endif(MSVC) -+ -+include(ConfigureChecks.cmake) -+ -+set(pthreads_SRCS -+ attr.c -+ barrier.c -+ cancel.c -+ cleanup.c -+ condvar.c -+ create.c -+ dll.c -+ exit.c -+ global.c -+ misc.c -+ mutex.c -+ nonportable.c -+ private.c -+ rwlock.c -+ sched.c -+ semaphore.c -+ spin.c -+ sync.c -+ tsd.c -+) -+ -+if(MSVC) -+ add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/version.rc COMMAND cl /EP ${CMAKE_SOURCE_DIR}/version.rc > ${CMAKE_BINARY_DIR}/version.rc) -+ list(APPEND ${pthreads_SRCS} ${CMAKE_BINARY_DIR}/version.rc) -+endif(MSVC) -+set(pthreads_HDRS -+ sched.h -+ pthread.h -+ semaphore.h -+) -+include_directories(${CMAKE_SOURCE_DIR}) -+ -+if(BUILD_STATIC) -+ set(_SHARED STATIC) -+ add_definitions(-DPTW32_STATIC_LIB) -+else(BUILD_STATIC) -+ set(_SHARED SHARED) -+endif(BUILD_STATIC) -+ -+add_library(pthreads ${_SHARED} ${pthreads_SRCS}) -+if(BUILD_INLINED) -+ set_target_properties(pthreads PROPERTIES COMPILE_FLAGS -DPTW32_BUILD_INLINED) -+endif(BUILD_INLINED) -+target_link_libraries(pthreads ws2_32) -+ -+############################################################################### -+if(BUILD_TESTS) -+set(pthreads_TESTS -+ loadfree -+ self1 -+ mutex5 -+ mutex1 -+ mutex1n -+ mutex1e -+ mutex1r -+ semaphore1 -+ semaphore2 -+ semaphore3 -+ mutex2 -+ mutex3 -+ mutex2r -+ mutex2e -+ mutex3r -+ mutex3e -+ condvar1 -+ condvar1_1 -+ condvar1_2 -+ condvar2 -+ condvar2_1 -+ exit1 -+ create1 -+ create2 -+ reuse1 -+ reuse2 -+ equal1 -+ kill1 -+ valid1 -+ valid2 -+ exit2 -+ exit3 -+ exit4 -+ exit5 -+ join0 -+ join1 -+ detach1 -+ join2 -+ join3 -+ mutex4 -+ mutex6 -+ mutex6n -+ mutex6e -+ mutex6r -+ mutex6s -+ mutex6es -+ mutex6rs -+ mutex7 -+ mutex7n -+ mutex7e -+ mutex7r -+ mutex8 -+ mutex8n -+ mutex8e -+ mutex8r -+ count1 -+ once1 -+ once2 -+ once3 -+ once4 -+ self2 -+ cancel1 -+ cancel2 -+ semaphore4 -+ semaphore4t -+# semaphore5 -+ barrier1 -+ barrier2 -+ barrier3 -+ barrier4 -+ barrier5 -+ tsd1 -+ tsd2 -+ delay1 -+ delay2 -+ eyal1 -+ condvar3 -+ condvar3_1 -+ condvar3_2 -+ condvar3_3 -+ condvar4 -+ condvar5 -+ condvar6 -+ condvar7 -+ condvar8 -+ condvar9 -+ errno1 -+ rwlock1 -+ rwlock2 -+ rwlock3 -+ rwlock4 -+ rwlock5 -+ rwlock6 -+ rwlock7 -+ rwlock8 -+ rwlock2_t -+ rwlock3_t -+ rwlock4_t -+ rwlock5_t -+ rwlock6_t -+ rwlock6_t2 -+ context1 -+ cancel3 -+ cancel4 -+ cancel5 -+ cancel6a -+ cancel6d -+ cancel7 -+ cancel8 -+ cleanup0 -+ cleanup1 -+ cleanup2 -+ cleanup3 -+ priority1 -+ priority2 -+ inherit1 -+ spin1 -+ spin2 -+ spin3 -+ spin4 -+ exception1 -+ exception2 -+ exception3 -+ cancel9 -+ create3 -+ stress1 -+) -+ -+foreach(_test ${pthreads_TESTS}) -+ add_executable(${_test} tests/${_test}.c) -+ target_link_libraries(${_test} pthreads) -+ add_test(${_test} ${_test}) -+endforeach(_test ${pthreads_TESTS}) -+ -+set(pthreads_ADD_TESTS -+ benchtest1 -+ benchtest2 -+ benchtest3 -+ benchtest4 -+ benchtest5 -+) -+ -+add_library(bench STATIC tests/benchlib.c) -+foreach(_test ${pthreads_ADD_TESTS}) -+ add_executable(${_test} tests/${_test}.c) -+ target_link_libraries(${_test} pthreads bench) -+ add_test(${_test} ${_test}) -+endforeach(_test ${pthreads_ADD_TESTS}) -+ -+endif(BUILD_TESTS) -+ -+############################################################################### -+install(TARGETS pthreads RUNTIME DESTINATION bin -+ LIBRARY DESTINATION lib -+ ARCHIVE DESTINATION lib) -+ -+install(FILES ${pthreads_HDRS} DESTINATION include) -\ Kein Zeilenumbruch am Dateiende. -diff -Nru -x '*~' pthreads-w32-2-9-1-release.orig/config.h pthreads-w32-2-9-1-release/config.h ---- pthreads-w32-2-9-1-release.orig/config.h 2011-05-30 02:31:00.000000000 +0200 -+++ pthreads-w32-2-9-1-release/config.h 1970-01-01 01:00:00.000000000 +0100 -@@ -1,153 +0,0 @@ --/* config.h */ -- --#ifndef PTW32_CONFIG_H --#define PTW32_CONFIG_H -- --/********************************************************************* -- * Defaults: see target specific redefinitions below. -- *********************************************************************/ -- --/* We're building the pthreads-win32 library */ --#define PTW32_BUILD -- --/* Do we know about the C type sigset_t? */ --#undef HAVE_SIGSET_T -- --/* Define if you have the header file. */ --#undef HAVE_SIGNAL_H -- --/* Define if you have the Borland TASM32 or compatible assembler. */ --#undef HAVE_TASM32 -- --/* Define if you don't have Win32 DuplicateHandle. (eg. WinCE) */ --#undef NEED_DUPLICATEHANDLE -- --/* Define if you don't have Win32 _beginthreadex. (eg. WinCE) */ --#undef NEED_CREATETHREAD -- --/* Define if you don't have Win32 errno. (eg. WinCE) */ --#undef NEED_ERRNO -- --/* Define if you don't have Win32 calloc. (eg. WinCE) */ --#undef NEED_CALLOC -- --/* Define if you don't have Win32 ftime. (eg. WinCE) */ --#undef NEED_FTIME -- --/* Define if you don't have Win32 semaphores. (eg. WinCE 2.1 or earlier) */ --#undef NEED_SEM -- --/* Define if you need to convert string parameters to unicode. (eg. WinCE) */ --#undef NEED_UNICODE_CONSTS -- --/* Define if your C (not C++) compiler supports "inline" functions. */ --#undef HAVE_C_INLINE -- --/* Do we know about type mode_t? */ --#undef HAVE_MODE_T -- --/* -- * Define if GCC has atomic builtins, i.e. __sync_* intrinsics -- * __sync_lock_* is implemented in mingw32 gcc 4.5.2 at least -- * so this define does not turn those on or off. If you get an -- * error from __sync_lock* then consider upgrading your gcc. -- */ --#undef HAVE_GCC_ATOMIC_BUILTINS -- --/* Define if you have the timespec struct */ --#undef HAVE_STRUCT_TIMESPEC -- --/* Define if you don't have the GetProcessAffinityMask() */ --#undef NEED_PROCESS_AFFINITY_MASK -- --/* Define if your version of Windows TLSGetValue() clears WSALastError -- * and calling SetLastError() isn't enough restore it. You'll also need to -- * link against wsock32.lib (or libwsock32.a for MinGW). -- */ --#undef RETAIN_WSALASTERROR -- --/* --# ---------------------------------------------------------------------- --# The library can be built with some alternative behaviour to better --# facilitate development of applications on Win32 that will be ported --# to other POSIX systems. --# --# Nothing described here will make the library non-compliant and strictly --# compliant applications will not be affected in any way, but --# applications that make assumptions that POSIX does not guarantee are --# not strictly compliant and may fail or misbehave with some settings. --# --# PTW32_THREAD_ID_REUSE_INCREMENT --# Purpose: --# POSIX says that applications should assume that thread IDs can be --# recycled. However, Solaris (and some other systems) use a [very large] --# sequence number as the thread ID, which provides virtual uniqueness. --# This provides a very high but finite level of safety for applications --# that are not meticulous in tracking thread lifecycles e.g. applications --# that call functions which target detached threads without some form of --# thread exit synchronisation. --# --# Usage: --# Set to any value in the range: 0 <= value < 2^wordsize. --# Set to 0 to emulate reusable thread ID behaviour like Linux or *BSD. --# Set to 1 for unique thread IDs like Solaris (this is the default). --# Set to some factor of 2^wordsize to emulate smaller word size types --# (i.e. will wrap sooner). This might be useful to emulate some embedded --# systems. --# --# define PTW32_THREAD_ID_REUSE_INCREMENT 0 --# --# ---------------------------------------------------------------------- -- */ --#undef PTW32_THREAD_ID_REUSE_INCREMENT -- -- --/********************************************************************* -- * Target specific groups -- * -- * If you find that these are incorrect or incomplete please report it -- * to the pthreads-win32 maintainer. Thanks. -- *********************************************************************/ --#if defined(WINCE) --#define NEED_DUPLICATEHANDLE --#define NEED_CREATETHREAD --#define NEED_ERRNO --#define NEED_CALLOC --#define NEED_FTIME --/* #define NEED_SEM */ --#define NEED_UNICODE_CONSTS --#define NEED_PROCESS_AFFINITY_MASK --/* This may not be needed */ --#define RETAIN_WSALASTERROR --#endif -- --#if defined(_UWIN) --#define HAVE_MODE_T --#define HAVE_STRUCT_TIMESPEC --#endif -- --#if defined(__GNUC__) --#define HAVE_C_INLINE --#endif -- --#if defined(__MINGW64__) --#define HAVE_MODE_T --#define HAVE_STRUCT_TIMESPEC --#elif defined(__MINGW32__) --#define HAVE_MODE_T --#endif -- --#if defined(__BORLANDC__) --#endif -- --#if defined(__WATCOMC__) --#endif -- --#if defined(__DMC__) --#define HAVE_SIGNAL_H --#define HAVE_C_INLINE --#endif -- -- -- --#endif -diff -Nru -x '*~' pthreads-w32-2-9-1-release.orig/config.h.cmake pthreads-w32-2-9-1-release/config.h.cmake ---- pthreads-w32-2-9-1-release.orig/config.h.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ pthreads-w32-2-9-1-release/config.h.cmake 2013-09-01 13:19:57.134952300 +0200 -@@ -0,0 +1,148 @@ -+/* config.h */ -+ -+#ifndef PTW32_CONFIG_H -+#define PTW32_CONFIG_H -+ -+/********************************************************************* -+ * Defaults: see target specific redefinitions below. -+ *********************************************************************/ -+ -+/* We're building the pthreads-win32 library */ -+#define PTW32_BUILD -+ -+/* Do we know about the C type sigset_t? */ -+#undef HAVE_SIGSET_T -+ -+/* Define if you have the header file. */ -+#undef HAVE_SIGNAL_H -+ -+/* Define if you have the Borland TASM32 or compatible assembler. */ -+#undef HAVE_TASM32 -+ -+/* Define if you don't have Win32 DuplicateHandle. (eg. WinCE) */ -+#undef NEED_DUPLICATEHANDLE -+ -+/* Define if you don't have Win32 _beginthreadex. (eg. WinCE) */ -+#undef NEED_CREATETHREAD -+ -+/* Define if you don't have Win32 errno. (eg. WinCE) */ -+#undef NEED_ERRNO -+ -+/* Define if you don't have Win32 calloc. (eg. WinCE) */ -+#undef NEED_CALLOC -+ -+/* Define if you don't have Win32 ftime. (eg. WinCE) */ -+#undef NEED_FTIME -+ -+/* Define if you don't have Win32 semaphores. (eg. WinCE 2.1 or earlier) */ -+#undef NEED_SEM -+ -+/* Define if you need to convert string parameters to unicode. (eg. WinCE) */ -+#undef NEED_UNICODE_CONSTS -+ -+/* Define if your C (not C++) compiler supports "inline" functions. */ -+#undef HAVE_C_INLINE -+ -+/* Do we know about type mode_t? */ -+#undef HAVE_MODE_T -+ -+/* -+ * Define if GCC has atomic builtins, i.e. __sync_* intrinsics -+ * __sync_lock_* is implemented in mingw32 gcc 4.5.2 at least -+ * so this define does not turn those on or off. If you get an -+ * error from __sync_lock* then consider upgrading your gcc. -+ */ -+#undef HAVE_GCC_ATOMIC_BUILTINS -+ -+/* Define if you have the timespec struct */ -+#cmakedefine HAVE_STRUCT_TIMESPEC -+ -+/* Define if you don't have the GetProcessAffinityMask() */ -+#undef NEED_PROCESS_AFFINITY_MASK -+ -+/* Define if your version of Windows TLSGetValue() clears WSALastError -+ * and calling SetLastError() isn't enough restore it. You'll also need to -+ * link against wsock32.lib (or libwsock32.a for MinGW). -+ */ -+#undef RETAIN_WSALASTERROR -+ -+/* -+# ---------------------------------------------------------------------- -+# The library can be built with some alternative behaviour to better -+# facilitate development of applications on Win32 that will be ported -+# to other POSIX systems. -+# -+# Nothing described here will make the library non-compliant and strictly -+# compliant applications will not be affected in any way, but -+# applications that make assumptions that POSIX does not guarantee are -+# not strictly compliant and may fail or misbehave with some settings. -+# -+# PTW32_THREAD_ID_REUSE_INCREMENT -+# Purpose: -+# POSIX says that applications should assume that thread IDs can be -+# recycled. However, Solaris (and some other systems) use a [very large] -+# sequence number as the thread ID, which provides virtual uniqueness. -+# This provides a very high but finite level of safety for applications -+# that are not meticulous in tracking thread lifecycles e.g. applications -+# that call functions which target detached threads without some form of -+# thread exit synchronisation. -+# -+# Usage: -+# Set to any value in the range: 0 <= value < 2^wordsize. -+# Set to 0 to emulate reusable thread ID behaviour like Linux or *BSD. -+# Set to 1 for unique thread IDs like Solaris (this is the default). -+# Set to some factor of 2^wordsize to emulate smaller word size types -+# (i.e. will wrap sooner). This might be useful to emulate some embedded -+# systems. -+# -+# define PTW32_THREAD_ID_REUSE_INCREMENT 0 -+# -+# ---------------------------------------------------------------------- -+ */ -+#undef PTW32_THREAD_ID_REUSE_INCREMENT -+ -+ -+/********************************************************************* -+ * Target specific groups -+ * -+ * If you find that these are incorrect or incomplete please report it -+ * to the pthreads-win32 maintainer. Thanks. -+ *********************************************************************/ -+#ifdef WINCE -+#define NEED_DUPLICATEHANDLE -+#define NEED_CREATETHREAD -+#define NEED_ERRNO -+#define NEED_CALLOC -+#define NEED_FTIME -+//#define NEED_SEM -+#define NEED_UNICODE_CONSTS -+#define NEED_PROCESS_AFFINITY_MASK -+#endif -+ -+#ifdef _UWIN -+#define HAVE_MODE_T -+#define HAVE_STRUCT_TIMESPEC -+#endif -+ -+#ifdef __GNUC__ -+#define HAVE_C_INLINE -+#endif -+ -+#ifdef __MINGW32__ -+#define HAVE_MODE_T -+#endif -+ -+#ifdef __BORLANDC__ -+#endif -+ -+#ifdef __WATCOMC__ -+#endif -+ -+#ifdef __DMC__ -+#define HAVE_SIGNAL_H -+#define HAVE_C_INLINE -+#endif -+ -+ -+ -+#endif -diff -Nru -x '*~' pthreads-w32-2-9-1-release.orig/ConfigureChecks.cmake pthreads-w32-2-9-1-release/ConfigureChecks.cmake ---- pthreads-w32-2-9-1-release.orig/ConfigureChecks.cmake 1970-01-01 01:00:00.000000000 +0100 -+++ pthreads-w32-2-9-1-release/ConfigureChecks.cmake 2013-09-01 13:39:02.982491000 +0200 -@@ -0,0 +1,16 @@ -+# Configure checks for the config.h -+ -+include(CheckIncludeFile) -+include(CheckFunctionExists) -+include(CheckSymbolExists) -+INCLUDE (CheckTypeSize) -+ -+SET(CMAKE_EXTRA_INCLUDE_FILES time.h) -+CHECK_TYPE_SIZE("struct timespec" STRUCT_TIMESPEC) -+SET(CMAKE_EXTRA_INCLUDE_FILES) -+ -+ -+add_definitions(-DHAVE_PTW32_CONFIG_H) -+ -+configure_file(config.h.cmake ${CMAKE_SOURCE_DIR}/config.h) -+ -diff -Nru -x '*~' pthreads-w32-2-9-1-release.orig/create.c pthreads-w32-2-9-1-release/create.c ---- pthreads-w32-2-9-1-release.orig/create.c 2012-03-18 06:11:43.000000000 +0100 -+++ pthreads-w32-2-9-1-release/create.c 2013-09-01 13:00:26.086972100 +0200 -@@ -94,6 +94,11 @@ - int priority; - pthread_t self; - -+ if(!tid) -+ { -+ result = EINVAL; -+ goto FAIL0; -+ } - /* - * Before doing anything, check that tid can be stored through - * without invoking a memory protection error (segfault). -diff -Nru -x '*~' pthreads-w32-2-9-1-release.orig/implement.h pthreads-w32-2-9-1-release/implement.h ---- pthreads-w32-2-9-1-release.orig/implement.h 2012-03-18 06:11:43.000000000 +0100 -+++ pthreads-w32-2-9-1-release/implement.h 2013-09-01 13:00:26.098972800 +0200 -@@ -55,6 +55,7 @@ - * note: ETIMEDOUT is correctly defined in winsock.h - */ - #include -+#include - - /* - * In case ETIMEDOUT hasn't been defined above somehow. -@@ -78,6 +79,8 @@ - /* use local include files during development */ - #include "semaphore.h" - #include "sched.h" -+#include "pthread.h" -+ - - #if defined(HAVE_C_INLINE) || defined(__cplusplus) - #define INLINE inline diff --git a/CMakeLists.txt b/CMakeLists.txt index e6bf2fc789..7a40d63071 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,688 +1,645 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(MIN_QT_VERSION 5.6.0) option(OVERRIDE_QT_VERSION "Use this to make it possible to build with Qt < 5.6.0. There will be bugs." OFF) if (OVERRIDE_QT_VERSION) set(MIN_QT_VERSION 5.4.0) endif() set(MIN_FRAMEWORKS_VERSION 5.7.0) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.9 -Wno-macro-redefined -Wno-deprecated-register) endif() if (LINUX) if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WINDOWS) add_definitions(-Werror=delete-incomplete) endif() endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.0.0-pre-alpha") set(KRITA_STABLE_VERSION_MAJOR 4) # 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MINOR 0) # 0 for 3.0, 1 for 3.1, etc. set(KRITA_VERSION_RELEASE 0) # 88 for pre-alpha, 89 for Alpha, increase for next test releases, set 0 for first Stable, etc. set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC set(KRITA_YEAR 2017) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_revision(GIT_REFSPEC GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1 AND GIT_BRANCH) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) endif() if(NOT DEFINED RELEASE_BUILD) # estimate mode by CMAKE_BUILD_TYPE content if not set on cmdline string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_TOLOWER) set(RELEASE_BUILD_TYPES "release" "relwithdebinfo" "minsizerel") list(FIND RELEASE_BUILD_TYPES "${CMAKE_BUILD_TYPE_TOLOWER}" INDEX) if (INDEX EQUAL -1) set(RELEASE_BUILD FALSE) else() set(RELEASE_BUILD TRUE) endif() endif() message(STATUS "Release build: ${RELEASE_BUILD}") # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") include(MacroJPEG) ########################################################### ## Look for Python3. It is also searched by KF5, ## ## so we should request the correct version in advance ## ########################################################### if(MINGW) # Special check: Building on Windows and ext_python is used find_package(PythonInterp 3.6) if(PYTHONINTERP_FOUND) find_package(PythonLibrary 3.6) if(PYTHONLIBS_FOUND) include("${CMAKE_CURRENT_SOURCE_DIR}/PythonWindowsCheck.cmake") endif(PYTHONLIBS_FOUND) endif(PYTHONINTERP_FOUND) else(MINGW) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.19 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Archive Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem ) # KConfig deprecated authorizeKAction. In order to be warning free, # compile with the updated function when the dependency is new enough. # Remove this (and the uses of the define) when the minimum KF5 # version is >= 5.24.0. if (${KF5Config_VERSION} VERSION_LESS "5.24.0" ) message("Old KConfig (< 5.24.0) found.") add_definitions(-DKCONFIG_BEFORE_5_24) endif() find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent ) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT WIN32 AND NOT APPLE) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "http://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5KIO ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5KIO_FOUND HAVE_KIO) set_package_properties(KF5KIO PROPERTIES DESCRIPTION "KDE's KIO Framework" URL "http://api.kde.org/frameworks-api/frameworks5-apidocs/kio/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used for recent document handling") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) 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 +find_package(Boost 1.55 REQUIRED COMPONENTS system) # for pigment and stage include_directories(${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "http://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) ########################### ############################ ## Optional dependencies ## ############################ ########################### ## ## Check for OpenEXR ## find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "http://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "http://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.remotesensing.org/libtiff" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "http://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "http://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## -find_package(Eigen3 REQUIRED "3.0") +find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## -set(EXIV2_MIN_VERSION "0.16") -find_package(Exiv2 REQUIRED) +find_package(Exiv2 0.16 REQUIRED) set_package_properties(Exiv2 PROPERTIES DESCRIPTION "Image metadata library and tools" URL "http://www.exiv2.org" PURPOSE "Required by Krita") ## ## Test for lcms ## -find_package(LCMS2 REQUIRED "2.4") +find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if( NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-Wabi -ffp-contract=fast -fPIC") elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-Wabi -fabi-version=0 -ffp-contract=fast -fPIC") endif() #Handle Vc master if(Vc_COMPILER_IS_GCC OR Vc_COMPILER_IS_CLANG) AddCompilerFlag("-std=c++11" _ok) if(NOT _ok) AddCompilerFlag("-std=c++0x" _ok) endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) if(WIN32) set(LIB_INSTALL_DIR ${LIB_INSTALL_DIR} RUNTIME DESTINATION ${BIN_INSTALL_DIR} LIBRARY ${INSTALL_TARGETS_DEFAULT_ARGS} ARCHIVE ${INSTALL_TARGETS_DEFAULT_ARGS} ) endif() ## ## Test endianess ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "http://poppler.freedesktop.org" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") -## -## 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") - -## -## Test for KUserFeedback -## - -find_package (KUserFeedback) -macro_bool_to_01(KUserFeedback_FOUND HAVE_KUserFeedback) -set_package_properties(KUserFeedback PROPERTIES - DESCRIPTION "A tool to collect telemetry" - URL "https://api.kde.org/frameworks/kuserfeedback/html/index.html" - TYPE OPTIONAL - PURPOSE "Optionally used") - - ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) 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/HACKING b/HACKING index 8158c34ed7..b4bbdca31f 100644 --- a/HACKING +++ b/HACKING @@ -1,126 +1,126 @@ Since 1999, people have been hacking on Krita. Everyone brought their own coding style, their own code conventions, their own likes and dislikes. Me, (Boudewijn that is), I like indents of four spaces, and no scope prefixes for variables. However, in the interests of consistency, these are the rules new code should adhere to: See also http://techbase.kde.org/Policies/Kdelibs_Coding_Style -- that document is leading. Qt vs STD vs Boost: In general, use the Qt classes wherever possible, even if someone tells you that the STD class is better or whatever. We're dealing with a big project with lots of developers here and we should keep the code as consistent and easy to grasp as possible. Since people need to know Qt in the first place, keep to Qt. Discuss deviations on #krita C++11 and C++14 Yes, but. Avoid lambdas. Avoid the new sig/slot connection syntax _unless_ you are porting all of Krita to the new syntax. Sure, it has some advantages, but having two different ways of doing the same thing is begging for trouble - and comprehension problems. For now, keep using foreach, we're using it all + and comprehension problems. For now, keep using Q_FOREACH, we're using it all over the place. auto is fine, when using in for loops. Don't go overboard using it in other places. Before using other new features, discuss on #krita so we can expand this list. Our minimum gcc version is 4.5, shipped with Ubuntu 12.04 Indentation With four spaces. Use the default kdelibs indentation (http://techbase.kde.org/Policies/Kdelibs_Coding_Style) Includes Avoid as much as possible #includes in header files; use forward declarations of classes. Initializers Avoid as much as possible initializers in the body of the constructor. Use initializer lists instead. Write the initializers as followed: Class(A a, B b) : Subclass(a) , m_b(b) { } Scope prefixes Use only m_ for class-level variables. No other scope prefixes; no g_, l_, no 'p' for pointer variables. Shared pointers Use shared pointers wherever possible. Getter/setter Krita doesn't use Qt's properties -- yet. If you want to introduce use of properties, convert any and all classes in Krita before committing. Getter/setters are named 'x() for getters and setX(int x) for setters. If you come across violations of this rule, change the code. Class naming If you use a well-known design pattern, name the class according to the design pattern. All files should start with 'Kis', all classes with the 'Kis' prefix. This filename should be the same as the classname: KisNewClass.h, KisNewClass. Function naming Functions should be named in camelBackedFashion, to conform to Qt's standards. If you encounter functions in c_style_like_this, feel free to rename. Also: verbNoun -- i.e., rotateLayer, not layer_rotate. The latter is a true c-ism, introduced by a language that needs to prefix the 'class' name to every function in order to have something that not quite OO. Variable/Parameter names Variable/parameter names start with a lower case letter. A name composed of different words is done in camelBackedStyle. Designer Krita has started to use designer. All dialogs and all widgets that have a layout manager must be done in designer. Do not add code or signal/slot connections in designer. Enums All enums should be prefixed with 'enum'. Namespaces Currently, we only use anonymous namespaces for things like undo commands. For the rest, some classes have a 'Kis' prefix, others don't. This should be made consistent, and we might want to use namespaces to keep all of Krita inside. Files and classes It's preferred (and strongly preferred) to have only one class per .h/.cpp file. (Which is logical, because otherwise you won't be able to keep to the naming scheme.) Spaces Keep the source airy and open. In particular, there should be empty lines between function declarations and definitions. Slots and signals Prefix slots with slot and signals with sig: slotUpdateSelection, sigSelectionUpdated. Boolean operators Use the standard !, !=, ==, && etc style, not the "not", "and" etc. style. Keep krita code using one, easily recognizable, C++ style. Boudewijn Rempt diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index cd21452b6b..b900bff745 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,91 +1,91 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ${CMAKE_SOURCE_DIR}/libs/pigment ${CMAKE_SOURCE_DIR}/libs/pigment/compositeops ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${Boost_INCLUDE_DIRS} ) set(LINK_VC_LIB) if(HAVE_VC) include_directories(${Vc_INCLUDE_DIR}) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Vc_DEFINITIONS}") set(LINK_VC_LIB ${Vc_LIBRARIES}) endif() macro_add_unittest_definitions() ########### next target ############### set(kis_datamanager_benchmark_SRCS kis_datamanager_benchmark.cpp) set(kis_hiterator_benchmark_SRCS kis_hline_iterator_benchmark.cpp) set(kis_viterator_benchmark_SRCS kis_vline_iterator_benchmark.cpp) set(kis_random_iterator_benchmark_SRCS kis_random_iterator_benchmark.cpp) set(kis_projection_benchmark_SRCS kis_projection_benchmark.cpp) set(kis_bcontrast_benchmark_SRCS kis_bcontrast_benchmark.cpp) set(kis_blur_benchmark_SRCS kis_blur_benchmark.cpp) set(kis_level_filter_benchmark_SRCS kis_level_filter_benchmark.cpp) set(kis_painter_benchmark_SRCS kis_painter_benchmark.cpp) set(kis_stroke_benchmark_SRCS kis_stroke_benchmark.cpp) set(kis_fast_math_benchmark_SRCS kis_fast_math_benchmark.cpp) set(kis_floodfill_benchmark_SRCS kis_floodfill_benchmark.cpp) set(kis_gradient_benchmark_SRCS kis_gradient_benchmark.cpp) set(kis_mask_generator_benchmark_SRCS kis_mask_generator_benchmark.cpp) set(kis_low_memory_benchmark_SRCS kis_low_memory_benchmark.cpp) set(kis_filter_selections_benchmark_SRCS kis_filter_selections_benchmark.cpp) if (UNIX) - #set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp) +# set(kis_composition_benchmark_SRCS kis_composition_benchmark.cpp) endif() set(kis_thumbnail_benchmark_SRCS kis_thumbnail_benchmark.cpp) krita_add_benchmark(KisDatamanagerBenchmark TESTNAME krita-benchmarks-KisDataManager ${kis_datamanager_benchmark_SRCS}) krita_add_benchmark(KisHLineIteratorBenchmark TESTNAME krita-benchmarks-KisHLineIterator ${kis_hiterator_benchmark_SRCS}) krita_add_benchmark(KisVLineIteratorBenchmark TESTNAME krita-benchmarks-KisVLineIterator ${kis_viterator_benchmark_SRCS}) krita_add_benchmark(KisRandomIteratorBenchmark TESTNAME krita-benchmarks-KisRandomIterator ${kis_random_iterator_benchmark_SRCS}) krita_add_benchmark(KisProjectionBenchmark TESTNAME krita-benchmarks-KisProjectionBenchmark ${kis_projection_benchmark_SRCS}) krita_add_benchmark(KisBContrastBenchmark TESTNAME krita-benchmarks-KisBContrastBenchmark ${kis_bcontrast_benchmark_SRCS}) krita_add_benchmark(KisBlurBenchmark TESTNAME krita-benchmarks-KisBlurBenchmark ${kis_blur_benchmark_SRCS}) krita_add_benchmark(KisLevelFilterBenchmark TESTNAME krita-benchmarks-KisLevelFilterBenchmark ${kis_level_filter_benchmark_SRCS}) krita_add_benchmark(KisPainterBenchmark TESTNAME krita-benchmarks-KisPainterBenchmark ${kis_painter_benchmark_SRCS}) krita_add_benchmark(KisStrokeBenchmark TESTNAME krita-benchmarks-KisStrokeBenchmark ${kis_stroke_benchmark_SRCS}) krita_add_benchmark(KisFastMathBenchmark TESTNAME krita-benchmarks-KisFastMath ${kis_fast_math_benchmark_SRCS}) krita_add_benchmark(KisFloodfillBenchmark TESTNAME krita-benchmarks-KisFloodFill ${kis_floodfill_benchmark_SRCS}) krita_add_benchmark(KisGradientBenchmark TESTNAME krita-benchmarks-KisGradientFill ${kis_gradient_benchmark_SRCS}) krita_add_benchmark(KisMaskGeneratorBenchmark TESTNAME krita-benchmarks-KisMaskGenerator ${kis_mask_generator_benchmark_SRCS}) krita_add_benchmark(KisLowMemoryBenchmark TESTNAME krita-benchmarks-KisLowMemory ${kis_low_memory_benchmark_SRCS}) krita_add_benchmark(KisFilterSelectionsBenchmark TESTNAME krita-image-KisFilterSelectionsBenchmark ${kis_filter_selections_benchmark_SRCS}) if(UNIX) - #krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS}) +# krita_add_benchmark(KisCompositionBenchmark TESTNAME krita-benchmarks-KisComposition ${kis_composition_benchmark_SRCS}) endif() krita_add_benchmark(KisThumbnailBenchmark TESTNAME krita-benchmarks-KisThumbnail ${kis_thumbnail_benchmark_SRCS}) target_link_libraries(KisDatamanagerBenchmark kritaimage Qt5::Test) target_link_libraries(KisHLineIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisVLineIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisRandomIteratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisProjectionBenchmark kritaimage kritaui Qt5::Test) target_link_libraries(KisBContrastBenchmark kritaimage Qt5::Test) target_link_libraries(KisBlurBenchmark kritaimage Qt5::Test) target_link_libraries(KisLevelFilterBenchmark kritaimage Qt5::Test) target_link_libraries(KisPainterBenchmark kritaimage Qt5::Test) target_link_libraries(KisStrokeBenchmark kritaimage Qt5::Test) target_link_libraries(KisFastMathBenchmark kritaimage Qt5::Test) target_link_libraries(KisFloodfillBenchmark kritaimage Qt5::Test) target_link_libraries(KisGradientBenchmark kritaimage Qt5::Test) target_link_libraries(KisLowMemoryBenchmark kritaimage Qt5::Test) target_link_libraries(KisFilterSelectionsBenchmark kritaimage Qt5::Test) if(UNIX) - #target_link_libraries(KisCompositionBenchmark kritaimage Qt5::Test ${LINK_VC_LIB}) - #if(HAVE_VC) - # set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}") - #endif() +# target_link_libraries(KisCompositionBenchmark kritaimage Qt5::Test ${LINK_VC_LIB}) + if(HAVE_VC) +# set_property(TARGET KisCompositionBenchmark APPEND PROPERTY COMPILE_OPTIONS "${Vc_ARCHITECTURE_FLAGS}") + endif() endif() target_link_libraries(KisMaskGeneratorBenchmark kritaimage Qt5::Test) target_link_libraries(KisThumbnailBenchmark kritaimage Qt5::Test) diff --git a/krita/data/templates/comics/a4_waffle_grid.desktop b/krita/data/templates/comics/a4_waffle_grid.desktop index 8cc814294a..7ce8f4c5d8 100644 --- a/krita/data/templates/comics/a4_waffle_grid.desktop +++ b/krita/data/templates/comics/a4_waffle_grid.desktop @@ -1,67 +1,67 @@ [Desktop Entry] Type=Link URL=.source/a4_waffle_grid.kra Icon=template_comics_empty Name=waffle-iron grid Name[bs]=mreža sječenog željeza Name[ca]=graella de ferro Name[ca@valencia]=graella de ferro Name[da]=vaffeljernsgitter Name[de]=Waffeleisengitter Name[el]=waffle-iron κάνναβος Name[en_GB]=waffle-iron grid Name[es]=rejilla de hierro para gofres Name[et]=Vahvlimasina ruudustik Name[eu]=gofreetarako burdinazko sareta Name[fr]=Grille en métal-gaufré Name[gl]=Grade de 3×5 viñetas Name[it]=Griglia a wafer Name[ja]=格子状コマ Name[kk]=торлы көзді Name[nb]=vaffeljern-rutenett Name[nds]=Wafeliesengadder Name[nl]=wafelijzer-raster Name[pl]=siatka gofrownicy Name[pt]=grelha de ferro para 'waffles' Name[pt_BR]=Grade de ferro vazia Name[ru]=Страница с ячейками Name[sk]=vaflovo-železná mriežka Name[sv]=våffelmönster Name[tr]=waffle-çelik ızgara Name[uk]=сітка з комірками Name[wa]=grile di fier a wåfes Name[x-test]=xxwaffle-iron gridxx Name[zh_CN]=华夫铁网格 Name[zh_TW]=鐵模式格線 Comment=300 dpi, A4 waffle-iron grid comic page with ink and color layers Comment[bs]=300 dpi, A4 mreža sječenog željeza stranica stripa s slojevima za tintu i bojemreža sječenog željeza Comment[ca]=300 ppp, pàgina de còmic amb graella de ferro amb capes de tinta i color Comment[ca@valencia]=300 ppp, pàgina de còmic amb graella de ferro amb capes de tinta i color Comment[da]=300 dpi, A4 tegneserieside i vaffeljernsgitter med blæk og farvelag Comment[de]=Comicseite mit Waffeleisengitter-Muster, Tinten- und Farbebenen. Format A4, Auflösung 300 dpi. Comment[el]=300 dpi, σελίδα κόμικ A4 με waffle-iron κάνναβο και στρώματα μελάνης και χρώματος Comment[en_GB]=300 dpi, A4 waffle-iron grid comic page with ink and colour layers Comment[es]=página de cómic con rejilla de hierro para gofres de tamaño A4, a 300 ppp, con tinta y capas de colores Comment[et]=300 DPI A4 vahvlimasina ruudustikuga koomiksilehekülg tindi- ja värvikihiga Comment[eu]=Gofreetarako burdinazko sareta duen 300 dpi-ko A4 komiki-orria, tinta- eta kolore-geruzaduna Comment[fr]=Page de bande dessinée avec Grille en métal-gaufré de 300 dpi, A4 avec encre et calques colorés -Comment[gl]=Páxina de banda deseñada en A4 a 300 dpi con 3×5 viñetas regulares e capas de tinta e cor. +Comment[gl]=Páxina de banda deseñada de grade en A4 a 300 dpi con 3×5 viñetas regulares e capas de tinta e cor. Comment[it]=Pagina di fumetti con griglia a wafer a 300 dpi, A4, con livelli per inchiostro e colore Comment[ja]=300 dpi A4 サイズの、ペン入れレイヤーと彩色レイヤーを備えた格子状コマテンプレート Comment[kk]=300 н/д A4 торлы көзді парақтағы комикс Comment[nb]=300 dpi, A4 tegneserieside med vaffeljern-rutenett, med tusj- og fargelag Comment[nds]=300 dpi, A4 Wafeliesengadder-Comicsiet mit Dint un Klöörlagen. Comment[nl]=300 dpi, A4 wafelijzer-raster strippagina met inkt en kleurlagen Comment[pl]=300 dpi, strona A4 siatki gofrownicy z warstwami tuszu i koloru Comment[pt]=banda desenhada A4, em grelha de 'waffle' a 300 ppp, com camadas de cores e de pinturas Comment[pt_BR]=Página de quadrinhos A4, em grade de ferro a 300 ppp, com camadas de cores e de pinturas Comment[ru]=300 dpi, страница комикса в формате A4 с ячейками и слоями контуров и цветов Comment[sk]=300 dpi, A4 vaflovo železná mriežka komiksovej strany s atramentom a farebnými vrstvami Comment[sv]=300 punkter/tum, A4 våffelmönstrad seriesida med bläck- och färglager Comment[tr]=300 dpi, A4 waffle-çelik ızgara mürekkep ve renk katmanlı çizgi roman sayfası Comment[uk]=300 т/д, сторінка коміксу у форматі A4 з комірками та шарами контурів та кольорів Comment[wa]=Pådje A4 di binde d' imådjes avou on discôpaedje come ene grile di fier a wåfes avou des coûtches d' intche eyet d' coleurs. Comment[x-test]=xx300 dpi, A4 waffle-iron grid comic page with ink and color layersxx Comment[zh_CN]=300 DPI,A4 尺寸华夫铁网格漫画页,带有墨水和色彩图层 Comment[zh_TW]=300 dpi,A4 大小的烘餅鐵模狀的格線,有墨水與顏色圖層 X-Krita-Version=28 diff --git a/krita/krita.action b/krita/krita.action index 758f5363d4..d105555fd8 100644 --- a/krita/krita.action +++ b/krita/krita.action @@ -1,3006 +1,3114 @@ General Open Resources Folder Opens a file browser at the location Krita saves resources such as brushes to. Opens a file browser at the location Krita saves resources such as brushes to. Open Resources Folder 0 0 false Cleanup removed files... Cleanup removed files Cleanup removed files 0 0 false C&ascade Cascade Cascade 10 0 false &Tile Tile Tile 10 0 false Create Resource Bundle... Create Resource Bundle Create Resource Bundle 0 0 false Show File Toolbar Show File Toolbar Show File Toolbar false Show color selector Show color selector Show color selector Shift+I false Show MyPaint shade selector Show MyPaint shade selector Show MyPaint shade selector Shift+M false Show minimal shade selector Show minimal shade selector Show minimal shade selector Shift+N false Show color history Show color history Show color history H false Show common colors Show common colors Show common colors U false Show Tool Options Show Tool Options Show Tool Options \ false Show Brush Editor Show Brush Editor Show Brush Editor F5 false Show Brush Presets Show Brush Presets Show Brush Presets F6 false Toggle Tablet Debugger Toggle Tablet Debugger Toggle Tablet Debugger 0 0 Ctrl+Shift+T false + + + + + + Show system information for bug reports. + + Show system information for bug reports. + Show system information for bug reports. + + false + + + + Rename Composition... Rename Composition Rename Composition 0 0 false Update Composition Update Composition Update Composition 0 0 false Painting Make brush color lighter Make brush color lighter Make brush color lighter 0 0 L false Make brush color darker Make brush color darker Make brush color darker 0 0 K false Make brush color more saturated Make brush color more saturated Make brush color more saturated false Make brush color more desaturated Make brush color more desaturated Make brush color more desaturated false Shift brush color hue clockwise Shift brush color hue clockwise Shift brush color hue clockwise false Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise Shift brush color hue counter-clockwise false Make brush color more red Make brush color more red Make brush color more red false Make brush color more green Make brush color more green Make brush color more green false Make brush color more blue Make brush color more blue Make brush color more blue false Make brush color more yellow Make brush color more yellow Make brush color more yellow false Increase opacity Increase opacity Increase opacity 0 0 O false Decrease opacity Decrease opacity Decrease opacity 0 0 I false draw-eraser Set eraser mode Set eraser mode Set eraser mode 10000 0 E true view-refresh Reload Original Preset Reload Original Preset Reload Original Preset 10000 false transparency-unlocked Preserve Alpha Preserve Alpha Preserve Alpha 10000 true transform_icons_penPressure Use Pen Pressure Use Pen Pressure Use Pen Pressure 10000 true symmetry-horizontal Horizontal Mirror Tool Horizontal Mirror Tool Horizontal Mirror Tool 10000 true symmetry-vertical Vertical Mirror Tool Vertical Mirror Tool Vertical Mirror Tool 10000 true + + + Hide Mirror X Line + + Hide Mirror X Line + Hide Mirror X Line + 10000 + + true + + + + + Hide Mirror Y Line + + Hide Mirror Y Line + Hide Mirror Y Line + 10000 + + true + + + + + Lock + + Lock X Line + Lock X Line + 10000 + + true + + + + + Lock Y Line + + Lock Y Line + Lock Y Line + 10000 + + true + + + + + Move to Canvas Center + + Move to Canvas Center X + Move to Canvas Center X + 10000 + + false + + + + + Move to Canvas Center Y + + Move to Canvas Center Y + Move to Canvas Center Y + 10000 + + false + + &Invert Selection Invert current selection Invert Selection 10000000000 100 Ctrl+Shift+I false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false &Toggle Selection Display Mode Toggle Selection Display Mode Toggle Selection Display Mode 0 0 false Next Favourite Preset Next Favourite Preset Next Favourite Preset , false Previous Favourite Preset Previous Favourite Preset Previous Favourite Preset . false preset-switcher Switch to Previous Preset Switch to Previous Preset Switch to Previous Preset / false Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar Hide Brushes and Stuff Toolbar true Reset Foreground and Background Color Reset Foreground and Background Color Reset Foreground and Background Color D false Swap Foreground and Background Color Swap Foreground and Background Color Swap Foreground and Background Color X false smoothing-weighted Brush Smoothing: Weighted Brush Smoothing: Weighted Brush Smoothing: Weighted false smoothing-no Brush Smoothing: Disabled Brush Smoothing: Disabled Brush Smoothing: Disabled false smoothing-stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer Brush Smoothing: Stabilizer false Decrease Brush Size Decrease Brush Size Decrease Brush Size 0 0 [ false smoothing-basic Brush Smoothing: Basic Brush Smoothing: Basic Brush Smoothing: Basic false Increase Brush Size Increase Brush Size Increase Brush Size 0 0 ] false Toggle Assistant Toggle Assistant ToggleAssistant Ctrl+Shift+L true Undo Polygon Selection Points Undo Polygon Selection Points Undo Polygon Selection Points Shift+Z false Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) Fill with Foreground Color (Opacity) 10000 1 Ctrl+Shift+Backspace false Fill with Background Color (Opacity) Fill with Background Color (Opacity) Fill with Background Color (Opacity) 10000 1 Ctrl+Backspace false Fill with Pattern (Opacity) Fill with Pattern (Opacity) Fill with Pattern (Opacity) 10000 1 false Convert &to Shape Convert to Shape Convert to Shape 10000000000 0 false &Select Opaque Select Opaque Select Opaque 100000 100 false &Show Global Selection Mask Shows global selection as a usual selection mask in <interface>Layers</interface> docker Show Global Selection Mask 100000 100 true Filters color-to-alpha &Color to Alpha... Color to Alpha Color to Alpha 10000 0 false &Top Edge Detection Top Edge Detection Top Edge Detection 10000 0 false &Index Colors... Index Colors Index Colors 10000 0 false Emboss Horizontal &Only Emboss Horizontal Only Emboss Horizontal Only 10000 0 false D&odge Dodge Dodge 10000 0 false &Sharpen Sharpen Sharpen 10000 0 false B&urn Burn Burn 10000 0 false &Mean Removal Mean Removal Mean Removal 10000 0 false &Gaussian Blur... Gaussian Blur Gaussian Blur 10000 0 false Emboss &in All Directions Emboss in All Directions Emboss in All Directions 10000 0 false &Small Tiles... Small Tiles Small Tiles 10000 0 false &Levels... Levels Levels 10000 0 Ctrl+L false &Sobel... Sobel Sobel 10000 0 false &Wave... Wave Wave 10000 0 false &Motion Blur... Motion Blur Motion Blur 10000 0 false &Color Adjustment curves... Color Adjustment curves Color Adjustment curves 10000 0 Ctrl+M false Pi&xelize... Pixelize Pixelize 10000 0 false Emboss (&Laplacian) Emboss (Laplacian) Emboss (Laplacian) 10000 0 false &Left Edge Detection Left Edge Detection Left Edge Detection 10000 0 false &Blur... Blur Blur 10000 0 false &Raindrops... Raindrops Raindrops 10000 0 false &Bottom Edge Detection Bottom Edge Detection Bottom Edge Detection 10000 0 false &Random Noise... Random Noise Random Noise 10000 0 false &Brightness/Contrast curve... Brightness/Contrast curve Brightness/Contrast curve 10000 0 false Colo&r Balance.. Color Balance.. Color Balance.. 10000 0 Ctrl+B false &Phong Bumpmap... Phong Bumpmap Phong Bumpmap 10000 0 false &Desaturate Desaturate Desaturate 10000 0 Ctrl+Shift+U false Color &Transfer... Color Transfer Color Transfer 10000 0 false Emboss &Vertical Only Emboss Vertical Only Emboss Vertical Only 10000 0 false &Lens Blur... Lens Blur Lens Blur 10000 0 false M&inimize Channel Minimize Channel Minimize Channel 10000 0 false M&aximize Channel Maximize Channel Maximize Channel 10000 0 false &Oilpaint... Oilpaint Oilpaint 10000 0 false &Right Edge Detection Right Edge Detection Right Edge Detection 10000 0 false &Auto Contrast Auto Contrast Auto Contrast 10000 0 false &Round Corners... Round Corners Round Corners 10000 0 false &Unsharp Mask... Unsharp Mask Unsharp Mask 10000 0 false &Emboss with Variable Depth... Emboss with Variable Depth Emboss with Variable Depth 10000 0 false Emboss &Horizontal && Vertical Emboss Horizontal & Vertical Emboss Horizontal & Vertical 10000 0 false Random &Pick... Random Pick Random Pick 10000 0 false &Gaussian Noise Reduction... Gaussian Noise Reduction Gaussian Noise Reduction 10000 0 false &Posterize... Posterize Posterize 10000 0 false &Wavelet Noise Reducer... Wavelet Noise Reducer Wavelet Noise Reducer 10000 0 false &HSV Adjustment... HSV Adjustment HSV Adjustment 10000 0 Ctrl+U false Tool Shortcuts Dynamic Brush Tool Dynamic Brush Tool Dynamic Brush Tool false Crop Tool Crop the image to an area Crop the image to an area C false Polygon Tool Polygon Tool. Shift-mouseclick ends the polygon. Polygon Tool. Shift-mouseclick ends the polygon. false References References References false Rectangle Tool Rectangle Tool Rectangle Tool false Multibrush Tool Multibrush Tool Multibrush Tool Q false + + + Lazy Brush Tool + + Lazy Brush Tool + Lazy Brush Tool + + + + + + Smart Patch Tool + + Smart Patch Tool + Smart Patch Tool + + + + + + Pan Tool + + Pan Tool + Pan Tool + + + Shape Manipulation Tool Shape Manipulation Tool Shape Manipulation Tool false Color Picker Select a color from the image or current layer Select a color from the image or current layer P false Text Editing Tool Text editing Text editing false Outline Selection Tool Outline Selection Tool Outline Selection Tool false Artistic Text Tool Artistic text editing Artistic text editing false Bezier Curve Selection Tool Select a Bezier Curve Selection Tool false Similar Color Selection Tool Select a Similar Color Selection Tool false Fill Tool Fill a contiguous area of color with a color, or fill a selection. Fill a contiguous area of color with a color, or fill a selection. F false Line Tool Line Tool Line Tool false Freehand Path Tool Freehand Path Tool Freehand Path Tool false Bezier Curve Tool Bezier Curve Tool. Shift-mouseclick ends the curve. Bezier Curve Tool. Shift-mouseclick ends the curve. false Ellipse Tool Ellipse Tool Ellipse Tool false Freehand Brush Tool Freehand Brush Tool Freehand Brush Tool B false Create object Create object Create object false Elliptical Selection Tool Elliptical Selection Tool Elliptical Selection Tool J false Contiguous Selection Tool Contiguous Selection Tool Contiguous Selection Tool false Pattern editing Pattern editing Pattern editing false Review Review Review false Draw a gradient. Draw a gradient. Draw a gradient. G false Polygonal Selection Tool Polygonal Selection Tool Polygonal Selection Tool false Measurement Tool Measure the distance between two points Measure the distance between two points false Rectangular Selection Tool Rectangular Selection Tool Rectangular Selection Tool Ctrl+R false Move Tool Move a layer Move a layer T false Vector Image Tool Vector Image (EMF/WMF/SVM/SVG) tool Vector Image (EMF/WMF/SVM/SVG) tool false Calligraphy Calligraphy Calligraphy false Path editing Path editing Path editing false Zoom Tool Zoom Tool Zoom Tool false Polyline Tool Polyline Tool. Shift-mouseclick ends the polyline. Polyline Tool. Shift-mouseclick ends the polyline. false Transform Tool Transform a layer or a selection Transform a layer or a selection Ctrl+T false Ruler assistant editor tool Ruler assistant editor tool Ruler assistant editor tool false Text tool Text tool Text tool false Gradient Editing Tool Gradient editing Gradient editing false Blending Modes Select Normal Blending Mode Select Normal Blending Mode Select Normal Blending Mode 0 0 Alt+Shift+N false Select Dissolve Blending Mode Select Dissolve Blending Mode Select Dissolve Blending Mode 0 0 Alt+Shift+I false Select Behind Blending Mode Select Behind Blending Mode Select Behind Blending Mode 0 0 Alt+Shift+Q false Select Clear Blending Mode Select Clear Blending Mode Select Clear Blending Mode 0 0 Alt+Shift+R false Select Darken Blending Mode Select Darken Blending Mode Select Darken Blending Mode 0 0 Alt+Shift+K false Select Multiply Blending Mode Select Multiply Blending Mode Select Multiply Blending Mode 0 0 Alt+Shift+M false Select Color Burn Blending Mode Select Color Burn Blending Mode Select Color Burn Blending Mode 0 0 Alt+Shift+B false Select Linear Burn Blending Mode Select Linear Burn Blending Mode Select Linear Burn Blending Mode 0 0 Alt+Shift+A false Select Lighten Blending Mode Select Lighten Blending Mode Select Lighten Blending Mode 0 0 Alt+Shift+G false Select Screen Blending Mode Select Screen Blending Mode Select Screen Blending Mode 0 0 Alt+Shift+S false Select Color Dodge Blending Mode Select Color Dodge Blending Mode Select Color Dodge Blending Mode 0 0 Alt+Shift+D false Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode Select Linear Dodge Blending Mode 0 0 Alt+Shift+W false Select Overlay Blending Mode Select Overlay Blending Mode Select Overlay Blending Mode 0 0 Alt+Shift+O false Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode Select Hard Overlay Blending Mode 0 0 Alt+Shift+P false Select Soft Light Blending Mode Select Soft Light Blending Mode Select Soft Light Blending Mode 0 0 Alt+Shift+F false Select Hard Light Blending Mode Select Hard Light Blending Mode Select Hard Light Blending Mode 0 0 Alt+Shift+H false Select Vivid Light Blending Mode Select Vivid Light Blending Mode Select Vivid Light Blending Mode 0 0 Alt+Shift+V false Select Linear Light Blending Mode Select Linear Light Blending Mode Select Linear Light Blending Mode 0 0 Alt+Shift+J false Select Pin Light Blending Mode Select Pin Light Blending Mode Select Pin Light Blending Mode 0 0 Alt+Shift+Z false Select Hard Mix Blending Mode Select Hard Mix Blending Mode Select Hard Mix Blending Mode 0 0 Alt+Shift+L false Select Difference Blending Mode Select Difference Blending Mode Select Difference Blending Mode 0 0 Alt+Shift+E false Select Exclusion Blending Mode Select Exclusion Blending Mode Select Exclusion Blending Mode 0 0 Alt+Shift+X false Select Hue Blending Mode Select Hue Blending Mode Select Hue Blending Mode 0 0 Alt+Shift+U false Select Saturation Blending Mode Select Saturation Blending Mode Select Saturation Blending Mode 0 0 Alt+Shift+T false Select Color Blending Mode Select Color Blending Mode Select Color Blending Mode 0 0 Alt+Shift+C false Select Luminosity Blending Mode Select Luminosity Blending Mode Select Luminosity Blending Mode 0 0 Alt+Shift+Y false Animation Previous frame Move to previous frame Move to previous frame 1 0 false Next frame Move to next frame Move to next frame 1 0 false Play / pause animation Play / pause animation Play / pause animation 1 0 false Add blank frame Add blank frame Add blank frame 100000 0 false Copy Frame Add duplicate frame Add duplicate frame 100000 0 false Toggle onion skin Toggle onion skin Toggle onion skin 100000 0 false Previous Keyframe false Next Keyframe false First Frame false Last Frame false Auto Frame Mode true true Add blank frame Add blank frame Add blank frame 100000 0 false Show in Timeline true Layers Activate next layer Activate next layer Activate next layer 1000 0 PgUp false Activate previous layer Activate previous layer Activate previous layer 1000 0 PgDown false Activate previously selected layer Activate previously selected layer Activate previously selected layer 1000 0 ; false groupLayer &Group Layer Group Layer Group Layer 1000 0 false cloneLayer &Clone Layer Clone Layer Clone Layer 1000 0 false vectorLayer &Vector Layer Vector Layer Vector Layer 1000 0 false filterLayer &Filter Layer... Filter Layer Filter Layer 1000 0 false fillLayer &Fill Layer... Fill Layer Fill Layer 1000 0 false fileLayer &File Layer... File Layer File Layer 1000 0 false transparencyMask &Transparency Mask Transparency Mask Transparency Mask 100000 0 false filterMask &Filter Mask... Filter Mask Filter Mask 100000 0 false filterMask &Colorize Mask Colorize Mask Colorize Mask 100000 0 false transformMask &Transform Mask... Transform Mask Transform Mask 100000 0 false selectionMask &Local Selection Local Selection Local Selection 100000 0 false view-filter &Isolate Layer Isolate Layer Isolate Layer 1000 0 true layer-locked &Toggle layer lock Toggle layer lock Toggle layer lock 1000 0 false visible Toggle layer &visibility Toggle layer visibility Toggle layer visibility 1000 0 false transparency-locked Toggle layer &alpha Toggle layer alpha Toggle layer alpha 1000 0 false transparency-enabled Toggle layer alpha &inheritance Toggle layer alpha inheritance Toggle layer alpha inheritance 1000 0 false paintLayer &Paint Layer Paint Layer Paint Layer 1000 0 Insert false &New Layer From Visible New layer from visible New layer from visible 1000 0 false duplicatelayer &Duplicate Layer or Mask Duplicate Layer or Mask Duplicate Layer or Mask 1000 0 Ctrl+J false &Cut Selection to New Layer Cut Selection to New Layer Cut Selection to New Layer 100000000 1 Ctrl+Shift+J false Copy &Selection to New Layer Copy Selection to New Layer Copy Selection to New Layer 100000000 0 Ctrl+Alt+J false Copy Layer Copy layer to clipboard Copy layer to clipboard 10000 0 false Cut Layer Cut layer to clipboard Cut layer to clipboard 10000 0 false Paste Layer Paste layer from clipboard Paste layer from clipboard 10000 0 false Quick Group Create a group layer containing selected layers Quick Group 100000 0 Ctrl+G false Quick Ungroup Remove grouping of the layers or remove one layer out of the group Quick Ungroup 100000 0 Ctrl+Alt+G false Quick Clipping Group Group selected layers and add a layer with clipped alpha channel Quick Clipping Group 100000 0 Ctrl+Shift+G false All Layers Select all layers Select all layers 10000 0 false Visible Layers Select all visible layers Select all visible layers 10000 0 false Locked Layers Select all locked layers Select all locked layers 10000 0 false Invisible Layers Select all invisible layers Select all invisible layers 10000 0 false Unlocked Layers Select all unlocked layers Select all unlocked layers 10000 0 false document-save &Save Layer/Mask... Save Layer/Mask Save Layer/Mask 1000 0 false document-save Save &Group Layers... Save Group Layers Save Group Layers 100000 0 false Convert group to &animated layer Convert child layers into animation frames Convert child layers into animation frames 100000 0 false I&mport Layer... Import Layer Import Layer 100000 0 false paintLayer &as Paint Layer... as Paint Layer as Paint Layer 1000 0 false transparencyMask as &Transparency Mask... as Transparency Mask as Transparency Mask 1000 0 false filterMask as &Filter Mask... as Filter Mask as Filter Mask 1000 0 false selectionMask as &Selection Mask... as Selection Mask as Selection Mask 1000 0 false paintLayer to &Paint Layer to Paint Layer to Paint Layer 1000 0 false transparencyMask to &Transparency Mask to Transparency Mask to Transparency Mask 1000 0 false filterMask to &Filter Mask... to Filter Mask to Filter Mask 1000 0 false selectionMask to &Selection Mask to Selection Mask to Selection Mask 1000 0 false transparencyMask &Alpha into Mask Alpha into Mask Alpha into Mask 100000 10 false transparency-enabled &Write as Alpha Write as Alpha Write as Alpha 1000000 1 false document-save &Save Merged... Save Merged Save Merged 1000000 0 false split-layer Split Layer... Split Layer Split Layer 1000 0 false Wavelet Decompose ... Wavelet Decompose Wavelet Decompose 1000 1 false symmetry-horizontal Mirror Layer Hori&zontally Mirror Layer Horizontally Mirror Layer Horizontally 1000 1 false symmetry-vertical Mirror Layer &Vertically Mirror Layer Vertically Mirror Layer Vertically 1000 1 false &Rotate Layer... Rotate Layer Rotate Layer 1000 1 false object-rotate-right Rotate &Layer 90° to the Right Rotate Layer 90° to the Right Rotate Layer 90° to the Right 1000 1 false object-rotate-left Rotate Layer &90° to the Left Rotate Layer 90° to the Left Rotate Layer 90° to the Left 1000 1 false Rotate Layer &180° Rotate Layer 180° Rotate Layer 180° 1000 1 false Scale &Layer to new Size... Scale Layer to new Size Scale Layer to new Size 100000 1 false &Shear Layer... Shear Layer Shear Layer 1000 1 false &Offset Layer... Offset Layer Offset Layer 100000 1 false Clones &Array... Clones Array Clones Array 100000 0 false &Edit metadata... Edit metadata Edit metadata 100000 1 false &Histogram... Histogram Histogram 100000 0 false &Convert Layer Color Space... Convert Layer Color Space Convert Layer Color Space 100000 1 false merge-layer-below &Merge with Layer Below Merge with Layer Below Merge with Layer Below 100000 0 Ctrl+E false &Flatten Layer Flatten Layer Flatten Layer 100000 0 false Ras&terize Layer Rasterize Layer Rasterize Layer 10000000 1 false Flatten ima&ge Flatten image Flatten image 100000 0 Ctrl+Shift+E false La&yer Style... Layer Style Layer Style 100000 1 false Move into previous group Move into previous group Move into previous group 0 0 false Move into next group Move into next group Move into next group 0 0 false Rename current layer Rename current layer Rename current layer 100000 0 F2 false deletelayer &Remove Layer Remove Layer Remove Layer 1000 1 Shift+Delete false arrowupblr Move Layer or Mask Up Move Layer or Mask Up Ctrl+PgUp false arrowdown Move Layer or Mask Down Move Layer or Mask Down Ctrl+PgDown false properties &Properties... Properties Properties 1000 1 F3 false diff --git a/krita/krita.qrc b/krita/krita.qrc index 82073d47eb..0479a25e9f 100644 --- a/krita/krita.qrc +++ b/krita/krita.qrc @@ -1,75 +1,75 @@ data/kritarc - krita.xmlgui + krita4.xmlgui pics/broken-preset.png pics/dark_layer-locked.png pics/dark_layer-unlocked.png pics/dark_novisible.svg pics/dark_passthrough-disabled.png pics/dark_passthrough-enabled.png pics/dark_selection-mode_ants.png pics/dark_selection-mode_invisible.png pics/dark_selection-mode_mask.png pics/dark_transparency-disabled.png pics/dark_transparency-enabled.png pics/dark_trim-to-image.png pics/dark_visible.svg pics/delete.png pics/dirty-preset.svg pics/height.png pics/height_icon.png pics/hi16-add_dialog.png pics/hi16-palette_library.png pics/icon-kritasketch-256.png pics/layer-style-disabled.png pics/layer-style-enabled.png pics/light_layer-locked.png pics/light_layer-unlocked.png pics/light_novisible.svg pics/light_passthrough-disabled.png pics/light_passthrough-enabled.png pics/light_selection-mode_ants.png pics/light_selection-mode_invisible.png pics/light_selection-mode_mask.png pics/light_transparency-disabled.png pics/light_transparency-enabled.png pics/light_trim-to-image.png pics/light_visible.svg pics/linked.png pics/local_selection_active.png pics/local_selection_inactive.png pics/mirrorAxis-HorizontalMove.png pics/mirrorAxis-VerticalMove.png pics/novisible.svg pics/offset_horizontal.png pics/offset_vertical.png pics/ratio_icon.png pics/select_pixel.png pics/select_shape.png pics/selection_add.png pics/selection_exclude.png pics/selection_intersect.png pics/selection_replace.png pics/selection_subtract.png pics/shade.png pics/shear_horizontal.png pics/shear_vertical.png pics/sidebaricon.png pics/tablet.png pics/tool_screenshot.png pics/transparency-locked.png pics/transparency-unlocked.png pics/unlinked.png pics/visible.svg pics/width.png pics/width_icon.png pics/workspace-chooser.png diff --git a/krita/krita.xmlgui b/krita/krita4.xmlgui similarity index 100% rename from krita/krita.xmlgui rename to krita/krita4.xmlgui diff --git a/krita/main.cc b/krita/main.cc index 83da7ed187..f25fc08202 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,291 +1,278 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #if defined Q_OS_WIN #include #include #include #elif defined HAVE_X11 #include #include #endif #if defined HAVE_KCRASH #include #elif defined USE_DRMINGW namespace { void tryInitDrMingw() { wchar_t path[MAX_PATH]; QString pathStr = QCoreApplication::applicationDirPath().replace(L'/', L'\\') + QStringLiteral("\\exchndl.dll"); if (pathStr.size() > MAX_PATH - 1) { return; } int pathLen = pathStr.toWCharArray(path); path[pathLen] = L'\0'; // toWCharArray doesn't add NULL terminator HMODULE hMod = LoadLibraryW(path); if (!hMod) { return; } // No need to call ExcHndlInit since the crash handler is installed on DllMain auto myExcHndlSetLogFileNameA = reinterpret_cast(GetProcAddress(hMod, "ExcHndlSetLogFileNameA")); if (!myExcHndlSetLogFileNameA) { return; } // Set the log file path to %LocalAppData%\kritacrash.log QString logFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation).replace(L'/', L'\\') + QStringLiteral("\\kritacrash.log"); myExcHndlSetLogFileNameA(logFile.toLocal8Bit()); } } // namespace #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif - /** - * Disable debug output by default. (krita.input enables tablet debugging.) - * Debug logs can be controlled by an environment variable QT_LOGGING_RULES. - * - * As an example, to get full debug output, run the following: - * export QT_LOGGING_RULES="krita*=true"; krita - * - * See: http://doc.qt.io/qt-5/qloggingcategory.html - */ - QLoggingCategory::setFilterRules("krita*.debug=false\n" - "krita*.warning=true\n" - "krita.tabletlog=true"); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita3" + QDesktopServices::storageLocation(QDesktopServices::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); #if QT_VERSION >= 0x050600 if (kritarc.value("EnableHiDPI", false).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } } KisOpenGL::setDefaultFormat(enableOpenGLDebug, openGLDebugSynchronous); KLocalizedString::setApplicationDomain("krita"); // first create the application so we can create a pixmap KisApplication app(key, argc, argv); #ifdef Q_OS_LINUX qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share") + ":" + qgetenv("XDG_DATA_DIRS")); #else qputenv("XDG_DATA_DIRS", QFile::encodeName(KoResourcePaths::getApplicationRoot() + "share")); #endif qDebug() << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); qDebug() << "Available translations" << KLocalizedString::availableApplicationTranslations(); qDebug() << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); // Now that the paths are set, set the language. First check the override from the langage // selection dialog. { QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); QString language = languageoverride.value(qAppName(), "").toString(); qDebug() << "Override language:" << language; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toUtf8()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); qDebug() << "Qt ui languages" << locale.uiLanguages(); } else { // And if there isn't one, check the one set by the system. // XXX: This doesn't work, for some !@#$% reason. QLocale locale = QLocale::system(); if (locale.bcp47Name() != QStringLiteral("en")) { qputenv("LANG", locale.bcp47Name().toLatin1()); KLocalizedString::setLanguages(QStringList() << locale.bcp47Name()); } } } #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); qDebug() << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = (args.print() || args.exportAs() || args.exportAsPdf()); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if defined HAVE_X11 app.installNativeEventFilter(KisXi2EventFilter::instance()); #endif app.installEventFilter(KisQtWidgetsTweaker::instance()); // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm)); } app.setSplashScreen(splash); #if defined Q_OS_WIN KisTabletSupportWin::init(); // app.installNativeEventFilter(new KisTabletSupportWin()); #endif if (!app.start(args)) { return 1; } #if QT_VERSION >= 0x050700 app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); #endif // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } return state; } diff --git a/libs/flake/FlakeDebug.cpp b/libs/flake/FlakeDebug.cpp index 33348f8f47..4cdef2ffff 100644 --- a/libs/flake/FlakeDebug.cpp +++ b/libs/flake/FlakeDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "FlakeDebug.h" const QLoggingCategory &FLAKE_LOG() \ { - static const QLoggingCategory category("krita.lib.flake"); + static const QLoggingCategory category("krita.lib.flake", QtInfoMsg); return category; } diff --git a/libs/flake/KoToolManager.h b/libs/flake/KoToolManager.h index 768da0a5f1..5b21336e8a 100644 --- a/libs/flake/KoToolManager.h +++ b/libs/flake/KoToolManager.h @@ -1,338 +1,338 @@ /* This file is part of the KDE project * Copyright (c) 2005-2006 Boudewijn Rempt * Copyright (C) 2006, 2008 Thomas Zander * Copyright (C) 2006 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 #include 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() override; 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() override; /** * 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; /** * 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. + * Returns the tool for the given tool id. The tool may be 0 * @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 &shapes); /** * Returns the list of toolActions for the current tools. * @returns lists of toolActions for the current tools. */ QList 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); /// 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); /** * 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 &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 > &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 &shapes)) Q_PRIVATE_SLOT(d, void currentLayerChanged(const KoShapeLayer *layer)) QPair createTools(KoCanvasController *controller, ToolHelper *tool); Private *const d; }; #endif diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp index 4b38d9daa0..9e2cd3ba10 100644 --- a/libs/flake/svg/SvgParser.cpp +++ b/libs/flake/svg/SvgParser.cpp @@ -1,1622 +1,1622 @@ /* This file is part of the KDE project * Copyright (C) 2002-2005,2007 Rob Buis * Copyright (C) 2002-2004 Nicolas Goutte * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2009 Jan Hambrecht * Copyright (C) 2005,2007 Thomas Zander * Copyright (C) 2006-2007 Inge Wallin * Copyright (C) 2007-2008,2010 Thorsten Zachmann * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoFilterEffectStack.h" #include "KoFilterEffectLoadingContext.h" #include #include #include #include "SvgUtil.h" #include "SvgShape.h" #include "SvgGraphicContext.h" #include "SvgFilterHelper.h" #include "SvgGradientHelper.h" #include "SvgClipPathHelper.h" #include "parsers/SvgTransformParser.h" #include "kis_pointer_utils.h" #include #include #include "kis_debug.h" #include "kis_global.h" SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager) : m_context(documentResourceManager) , m_documentResourceManager(documentResourceManager) { } SvgParser::~SvgParser() { qDeleteAll(m_symbols); } 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 SvgParser::shapes() const { return m_shapes; } QVector SvgParser::takeSymbols() { QVector symbols = m_symbols; m_symbols.clear(); return symbols; } // Helper functions // --------------------------------------------------------------------------------------- SvgGradientHelper* SvgParser::findGradient(const QString &id) { SvgGradientHelper *result = 0; // check if gradient was already parsed, and return it if (m_gradients.contains(id)) { result = &m_gradients[ id ]; } // check if gradient was stored for later parsing if (!result && m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName().contains("Gradient")) { result = parseGradient(m_context.definition(id)); } } return result; } QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape) { QSharedPointer result; // check if gradient was stored for later parsing if (m_context.hasDefinition(id)) { const KoXmlElement &e = m_context.definition(id); if (e.tagName() == "pattern") { result = parsePattern(m_context.definition(id), shape); } } return result; } SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href) { // check if filter was already parsed, and return it if (m_filters.contains(id)) return &m_filters[ id ]; // check if filter was stored for later parsing if (!m_context.hasDefinition(id)) return 0; const KoXmlElement &e = m_context.definition(id); if (KoXml::childNodesCount(e) == 0) { QString mhref = e.attribute("xlink:href").mid(1); if (m_context.hasDefinition(mhref)) return findFilter(mhref, id); else return 0; } else { // ok parse filter now if (! parseFilter(m_context.definition(id), m_context.definition(href))) return 0; } // return successfully parsed filter or 0 QString n; if (href.isEmpty()) n = id; else n = href; if (m_filters.contains(n)) return &m_filters[ n ]; else return 0; } SvgClipPathHelper* SvgParser::findClipPath(const QString &id) { return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0; } // Parsing functions // --------------------------------------------------------------------------------------- qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox) { return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox); } qreal SvgParser::parseUnitX(const QString &unit) { return SvgUtil::parseUnitX(m_context.currentGC(), unit); } qreal SvgParser::parseUnitY(const QString &unit) { return SvgUtil::parseUnitY(m_context.currentGC(), unit); } qreal SvgParser::parseUnitXY(const QString &unit) { return SvgUtil::parseUnitXY(m_context.currentGC(), unit); } qreal SvgParser::parseAngular(const QString &unit) { return SvgUtil::parseUnitAngular(m_context.currentGC(), unit); } SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return 0; SvgGradientHelper gradHelper; QString gradientId = e.attribute("id"); if (gradientId.isEmpty()) return 0; // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.contains(gradientId)) { return &m_gradients[gradientId]; } if (e.hasAttribute("xlink:href")) { // strip the '#' symbol QString href = e.attribute("xlink:href").mid(1); if (!href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) { gradHelper = *pGrad; } } } const QGradientStops defaultStops = gradHelper.gradient()->stops(); if (e.attribute("gradientUnits") == "userSpaceOnUse") { gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse); } m_context.pushGraphicsContext(e); uploadStyleToContext(e); if (e.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")), SvgUtil::fromPercentage(e.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")), SvgUtil::fromPercentage(e.attribute("y2", "0%")))); } else { g->setStart(QPointF(parseUnitX(e.attribute("x1")), parseUnitY(e.attribute("y1")))); g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")), parseUnitY(e.attribute("y2")))); } gradHelper.setGradient(g); } else if (e.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")), SvgUtil::fromPercentage(e.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")), SvgUtil::fromPercentage(e.attribute("fy", "50%")))); } else { g->setCenter(QPointF(parseUnitX(e.attribute("cx")), parseUnitY(e.attribute("cy")))); g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")), parseUnitY(e.attribute("fy")))); g->setRadius(parseUnitXY(e.attribute("r"))); } gradHelper.setGradient(g); } else { qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName(); } // handle spread method QGradient::Spread spreadMethod = QGradient::PadSpread; QString spreadMethodStr = e.attribute("spreadMethod"); if (!spreadMethodStr.isEmpty()) { if (spreadMethodStr == "reflect") { spreadMethod = QGradient::ReflectSpread; } else if (spreadMethodStr == "repeat") { spreadMethod = QGradient::RepeatSpread; } } gradHelper.setSpreadMode(spreadMethod); // Parse the color stops. m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops); if (e.hasAttribute("gradientTransform")) { SvgTransformParser p(e.attribute("gradientTransform")); if (p.isValid()) { gradHelper.setTransform(p.transform()); } } m_context.popGraphicsContext(); m_gradients.insert(gradientId, gradHelper); return &m_gradients[gradientId]; } inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset) { QTransform result = patternTransform * QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) * patternTransform.inverted(); KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate); return QPointF(result.dx(), result.dy()); } QSharedPointer 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 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 pPatt = findPattern(href, shape); if (pPatt) { pattHelper = pPatt; } } } pattHelper->setReferenceCoordinates( KoFlake::coordinatesFromString(e.attribute("patternUnits"), pattHelper->referenceCoordinates())); pattHelper->setContentCoordinates( KoFlake::coordinatesFromString(e.attribute("patternContentUnits"), pattHelper->contentCoordinates())); if (e.hasAttribute("patternTransform")) { SvgTransformParser p(e.attribute("patternTransform")); if (p.isValid()) { pattHelper->setPatternTransform(p.transform()); } } if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) { QRectF referenceRect( SvgUtil::fromPercentage(e.attribute("x", "0%")), SvgUtil::fromPercentage(e.attribute("y", "0%")), SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why! SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } else { QRectF referenceRect( parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")), parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why! parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why! pattHelper->setReferenceRect(referenceRect); } /** * In Krita shapes X,Y coordinates are baked into the the shape global transform, but * the pattern should be painted in "user" coordinates. Therefore, we should handle * this offfset separately. * * TODO: Please also note that this offset is different from extraShapeOffset(), * because A.inverted() * B != A * B.inverted(). I'm not sure which variant is * correct (DK) */ const QTransform dstShapeTransform = shape->absoluteTransformation(0); const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted(); KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate); const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy()); m_context.pushGraphicsContext(e); gc = m_context.currentGC(); gc->workaroundClearInheritedFillProperties(); // HACK! // start building shape tree from scratch gc->matrix = QTransform(); const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/; const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); // WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes! // although we expect the pattern be reusable, but it is not so! // WARNING2: the pattern shapes are stored in *User* coordinate system, although // the "official" content system might be either OBB or User. It means that // this baked transform should be stripped before writing the shapes back // into SVG if (e.hasAttribute("viewBox")) { gc->currentBoundingBox = pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ? relativeToShape.mapRect(pattHelper->referenceRect()) : pattHelper->referenceRect(); applyViewBoxTransform(e); pattHelper->setContentCoordinates(pattHelper->referenceCoordinates()); } else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) { gc->matrix = relativeToShape * gc->matrix; } // We do *not* apply patternTransform here! Here we only bake the untransformed // version of the shape. The transformed one will be done in the very end while rendering. QList patternShapes = parseContainer(e); if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into the pattern shapes const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); Q_FOREACH (KoShape *shape, patternShapes) { shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y())); } } if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) { // In Krita we normalize the shapes, bake this transform into reference rect // NOTE: this is possible *only* when pattern transform is not perspective // (which is always true for SVG) const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset); QRectF ref = pattHelper->referenceRect(); ref.translate(offset); pattHelper->setReferenceRect(ref); } m_context.popGraphicsContext(); gc = m_context.currentGC(); if (!patternShapes.isEmpty()) { pattHelper->setShapes(patternShapes); } return pattHelper; } bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy) { SvgFilterHelper filter; // Use the filter that is referencing, or if there isn't one, the original filter KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; // check if we are referencing another filter if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced filter if found SvgFilterHelper *refFilter = findFilter(href); if (refFilter) filter = *refFilter; } } else { filter.setContent(b); } if (b.attribute("filterUnits") == "userSpaceOnUse") filter.setFilterUnits(KoFlake::UserSpaceOnUse); if (b.attribute("primitiveUnits") == "objectBoundingBox") filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox); // parse filter region rectangle if (filter.filterUnits() == KoFlake::UserSpaceOnUse) { filter.setPosition(QPointF(parseUnitX(b.attribute("x")), parseUnitY(b.attribute("y")))); filter.setSize(QSizeF(parseUnitX(b.attribute("width")), parseUnitY(b.attribute("height")))); } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")), SvgUtil::fromPercentage(b.attribute("y", "-0.1")))); filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")), SvgUtil::fromPercentage(b.attribute("height", "1.2")))); } m_filters.insert(b.attribute("id"), filter); return true; } bool SvgParser::parseMarker(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; QScopedPointer 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(marker.take())); return true; } bool SvgParser::parseSymbol(const KoXmlElement &e) { const QString id = e.attribute("id"); if (id.isEmpty()) return false; KoSvgSymbol *svgSymbol = new KoSvgSymbol(); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e, false); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->currentBoundingBox = QRectF(0.0, 0.0, 1.0, 1.0); QString title = e.firstChildElement("title").toElement().text(); KoShape *symbolShape = parseGroup(e); m_context.popGraphicsContext(); if (!symbolShape) return false; svgSymbol->shape = symbolShape; svgSymbol->title = title; svgSymbol->id = id; if (title.isEmpty()) svgSymbol->title = id; if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) { warnFlake << "Symbol" << id << "seems to be empty, discarding"; delete svgSymbol; return false; } m_symbols << svgSymbol; return true; } bool SvgParser::parseClipPath(const KoXmlElement &e) { SvgClipPathHelper clipPath; const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipPath.setClipPathUnits( KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse)); // ensure that the clip path is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipPath.setShapes({clipShape}); m_clipPaths.insert(id, clipPath); return true; } bool SvgParser::parseClipMask(const KoXmlElement &e) { QSharedPointer clipMask(new KoClipMask); const QString id = e.attribute("id"); if (id.isEmpty()) return false; clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox)); clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse)); QRectF maskRect; if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) { maskRect.setRect( SvgUtil::fromPercentage(e.attribute("x", "-10%")), SvgUtil::fromPercentage(e.attribute("y", "-10%")), SvgUtil::fromPercentage(e.attribute("width", "120%")), SvgUtil::fromPercentage(e.attribute("height", "120%"))); } else { maskRect.setRect( parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case, parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us... parseUnitX(e.attribute("width", "120%")), parseUnitY(e.attribute("height", "120%"))); } clipMask->setMaskRect(maskRect); // ensure that the clip mask is loaded in local coordinates system m_context.pushGraphicsContext(e); m_context.currentGC()->matrix = QTransform(); m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK! KoShape *clipShape = parseGroup(e); m_context.popGraphicsContext(); if (!clipShape) return false; clipMask->setShapes({clipShape}); m_clipMasks.insert(id, clipMask); return true; } void SvgParser::uploadStyleToContext(const KoXmlElement &e) { SvgStyles styles = m_context.styleParser().collectStyles(e); m_context.styleParser().parseFont(styles); m_context.styleParser().parseStyle(styles); } void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { if (!shape) return; SvgGraphicsContext *gc = m_context.currentGC(); KIS_ASSERT(gc); if (!dynamic_cast(shape)) { applyFillStyle(shape); applyStrokeStyle(shape); } if (KoPathShape *pathShape = dynamic_cast(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(obj)) { applyFillStyle(obj); applyStrokeStyle(obj); } if (KoPathShape *pathShape = dynamic_cast(obj)) { applyMarkers(pathShape); } applyFilter(obj); applyClipping(obj, shapeToOriginalUserCoordinates); applyMaskClipping(obj, shapeToOriginalUserCoordinates); if (!gc->display || !gc->visible) { obj->setVisible(false); } obj->setTransparency(1.0 - gc->opacity); } QGradient* prepareGradientForShape(const SvgGradientHelper *gradient, const KoShape *shape, const SvgGraphicsContext *gc, QTransform *transform) { QGradient *resultGradient = 0; KIS_ASSERT(transform); if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) { resultGradient = KoFlake::cloneGradient(gradient->gradient()); *transform = gradient->transform(); } else { if (gradient->gradient()->type() == QGradient::LinearGradient) { /** * Create a converted gradient that looks the same, but linked to the * bounding rect of the shape, so it would be transformed with the shape */ const QRectF boundingRect = shape->outline().boundingRect(); const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(), boundingRect.x(), boundingRect.y()); const QTransform relativeToUser = relativeToShape * shape->transformation() * gc->matrix.inverted(); const QTransform userToRelative = relativeToUser.inverted(); const QLinearGradient *o = static_cast(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(0)); } else if (gc->fillType == SvgGraphicsContext::Solid) { shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } else if (gc->fillType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->fillId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QSharedPointer bg; bg = toQShared(new KoGradientBackground(result)); bg->setTransform(transform); shape->setBackground(bg); } } else { QSharedPointer pattern = findPattern(gc->fillId, shape); if (pattern) { shape->setBackground(pattern); } else { // no referenced fill found, use fallback color shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor))); } } } KoPathShape *path = dynamic_cast(shape); if (path) path->setFillRule(gc->fillRule); } void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke) { const double lineWidth = srcStroke->lineWidth(); QVector dashes = srcStroke->lineDashes(); // apply line width to dashes and dash offset if (dashes.count() && lineWidth > 0.0) { const double dashOffset = srcStroke->dashOffset(); QVector 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()); } } void SvgParser::applyStrokeStyle(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->strokeType == SvgGraphicsContext::None) { shape->setStroke(KoShapeStrokeModelSP()); } else if (gc->strokeType == SvgGraphicsContext::Solid) { KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } else if (gc->strokeType == SvgGraphicsContext::Complex) { // try to find referenced gradient SvgGradientHelper *gradient = findGradient(gc->strokeId); if (gradient) { QTransform transform; QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform); if (result) { QBrush brush = *result; delete result; brush.setTransform(transform); KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); stroke->setLineBrush(brush); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } else { // no referenced stroke found, use fallback color KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke)); applyDashes(gc->stroke, stroke); shape->setStroke(stroke); } } } void SvgParser::applyFilter(KoShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->filterId.isEmpty()) return; SvgFilterHelper *filter = findFilter(gc->filterId); if (! filter) return; KoXmlElement content = filter->content(); // parse filter region QRectF bound(shape->position(), shape->size()); - // work on bounding box without viewbox tranformation applied + // work on bounding box without viewbox transformation applied // so user space coordinates of bounding box and filter region match up bound = gc->viewboxTransform.inverted().mapRect(bound); QRectF filterRegion(filter->position(bound), filter->size(bound)); // convert filter region to boundingbox units QRectF objectFilterRegion; objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound)); objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound)); KoFilterEffectLoadingContext context(m_context.xmlBaseDir()); context.setShapeBoundingBox(bound); // enable units conversion context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse); context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse); KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance(); KoFilterEffectStack *filterStack = 0; QSet stdInputs; stdInputs << "SourceGraphic" << "SourceAlpha"; stdInputs << "BackgroundImage" << "BackgroundAlpha"; stdInputs << "FillPaint" << "StrokePaint"; QMap inputs; // create the filter effects and add them to the shape for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement primitive = n.toElement(); KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context); if (!filterEffect) { debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet"; continue; } const QString input = primitive.attribute("in"); if (!input.isEmpty()) { filterEffect->setInput(0, input); } const QString output = primitive.attribute("result"); if (!output.isEmpty()) { filterEffect->setOutput(output); } QRectF subRegion; // parse subregion if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) { const QString xa = primitive.attribute("x"); const QString ya = primitive.attribute("y"); const QString wa = primitive.attribute("width"); const QString ha = primitive.attribute("height"); if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) { bool hasStdInput = false; bool isFirstEffect = filterStack == 0; // check if one of the inputs is a standard input Q_FOREACH (const QString &input, filterEffect->inputs()) { if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) { hasStdInput = true; break; } } if (hasStdInput || primitive.tagName() == "feImage") { // default to 0%, 0%, 100%, 100% subRegion.setTopLeft(QPointF(0, 0)); subRegion.setSize(QSizeF(1, 1)); } else { // defaults to bounding rect of all referenced nodes Q_FOREACH (const QString &input, filterEffect->inputs()) { if (!inputs.contains(input)) continue; KoFilterEffect *inputFilter = inputs[input]; if (inputFilter) subRegion |= inputFilter->filterRect(); } } } else { const qreal x = parseUnitX(xa); const qreal y = parseUnitY(ya); const qreal w = parseUnitX(wa); const qreal h = parseUnitY(ha); subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound)); subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound)); } } else { // x, y, width, height are in percentages of the object referencing the filter // so we just parse the percentages const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0")); const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0")); const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1")); const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1")); subRegion = QRectF(QPointF(x, y), QSizeF(w, h)); } filterEffect->setFilterRect(subRegion); if (!filterStack) filterStack = new KoFilterEffectStack(); filterStack->appendFilterEffect(filterEffect); inputs[filterEffect->output()] = filterEffect; } if (filterStack) { filterStack->setClipRect(objectFilterRegion); shape->setFilterEffectStack(filterStack); } } void SvgParser::applyMarkers(KoPathShape *shape) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) { shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker); } if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) { shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker); } if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) { shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker); } shape->setAutoFillMarkers(gc->autoFillMarkers); } void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (! gc) return; if (gc->clipPathId.isEmpty()) return; SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId); if (!clipPath || clipPath->isEmpty()) return; QList shapes; Q_FOREACH (KoShape *item, clipPath->shapes()) { KoShape *clonedShape = item->cloneShape(); KIS_ASSERT_RECOVER(clonedShape) { continue; } shapes.append(clonedShape); } if (!shapeToOriginalUserCoordinates.isNull()) { const QTransform t = QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(), shapeToOriginalUserCoordinates.y()); Q_FOREACH(KoShape *s, shapes) { s->applyAbsoluteTransformation(t); } } KoClipPath *clipPathObject = new KoClipPath(shapes, clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ? KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse); shape->setClipPath(clipPathObject); } void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates) { SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return; if (gc->clipMaskId.isEmpty()) return; QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId); if (!originalClipMask || originalClipMask->isEmpty()) return; KoClipMask *clipMask = originalClipMask->clone(); clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates); shape->setClipMask(clipMask); } KoShape* SvgParser::parseUse(const KoXmlElement &e) { KoShape *result = 0; QString id = e.attribute("xlink:href"); if (!id.isEmpty()) { QString key = id.mid(1); if (m_context.hasDefinition(key)) { SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); // 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(); } } return result; } void SvgParser::addToGroup(QList shapes, KoShapeGroup *group) { m_shapes += shapes; if (!group || shapes.isEmpty()) return; // not clipped, but inherit transform KoShapeGroupCommand cmd(group, shapes, false, true, false); cmd.redo(); } QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize) { // check if we are the root svg element const bool isRootSvg = m_context.isRootContext(); // parse 'transform' field if preset SvgGraphicsContext *gc = m_context.pushGraphicsContext(e); applyStyle(0, e, QPointF()); const QString w = e.attribute("width"); const QString h = e.attribute("height"); 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) { *fragmentSize = svgFragmentSize; } gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize); if (!isRootSvg) { // x and y attribute has no meaning for outermost svg elements const qreal x = parseUnit(e.attribute("x", "0")); const qreal y = parseUnit(e.attribute("y", "0")); QTransform move = QTransform::fromTranslate(x, y); gc->matrix = move * gc->matrix; } applyViewBoxTransform(e); QList shapes; // First find the metadata for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { KoXmlElement b = n.toElement(); if (b.isNull()) continue; if (b.tagName() == "title") { m_documentTitle = b.text().trimmed(); } else if (b.tagName() == "desc") { m_documentDescription = b.text().trimmed(); } else if (b.tagName() == "metadata") { // TODO: parse the metadata } } // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy // and as mother makes them -- if mother is inkscape. if (gc->currentBoundingBox.normalized().isValid()) { shapes = parseContainer(e); } m_context.popGraphicsContext(); return shapes; } void SvgParser::applyViewBoxTransform(const KoXmlElement &element) { SvgGraphicsContext *gc = m_context.currentGC(); QRectF viewRect = gc->currentBoundingBox; QTransform viewTransform; if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox, &viewRect, &viewTransform)) { gc->matrix = viewTransform * gc->matrix; gc->currentBoundingBox = viewRect; } } QList > SvgParser::knownMarkers() const { return m_markers.values(); } QString SvgParser::documentTitle() const { return m_documentTitle; } QString SvgParser::documentDescription() const { return m_documentDescription; } void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func) { m_context.setFileFetcher(func); } inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading) { const QTransform shapeToOriginalUserCoordinates = shape->absoluteTransformation(0).inverted() * 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 childShapes; if (!overrideChildrenFrom.isNull()) { // we upload styles from both: and uploadStyleToContext(overrideChildrenFrom); childShapes = parseSingleElement(overrideChildrenFrom); } else { childShapes = parseContainer(b); } // handle id applyId(b.attribute("id"), group); addToGroup(childShapes, group); applyCurrentStyle(group, extraOffset); // apply style to this group after size is set m_context.popGraphicsContext(); return group; } QList SvgParser::parseContainer(const KoXmlElement &e) { QList 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 } } QList currentShapes = parseSingleElement(b); shapes.append(currentShapes); // if we are parsing a switch, stop after the first supported element if (isSwitch && !currentShapes.isEmpty()) break; } return shapes; } QList SvgParser::parseSingleElement(const KoXmlElement &b) { QList shapes; // 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") { // treat svg link 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 (KoXml::childNodesCount(b) > 0) { /** * WARNING: 'defs' are basically 'display:none' style, therefore they should not play * any role in shapes outline calculation. But setVisible(false) shapes do! * Should be fixed in the future! */ KoShape *defsShape = parseGroup(b); defsShape->setVisible(false); delete defsShape; } } else if (b.tagName() == "linearGradient" || b.tagName() == "radialGradient") { } else if (b.tagName() == "pattern") { } else if (b.tagName() == "filter") { parseFilter(b); } else if (b.tagName() == "clipPath") { parseClipPath(b); } else if (b.tagName() == "mask") { parseClipMask(b); } else if (b.tagName() == "marker") { parseMarker(b); } else if (b.tagName() == "symbol") { parseSymbol(b); } else if (b.tagName() == "style") { m_context.addStyleSheet(b); } else if (b.tagName() == "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(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(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(createShape(KoPathShapeId)); if (path) { path->clear(); KoPathShapeLoader loader(path); loader.parseSvg(element.attribute("d"), true); path->setPosition(path->normalize()); QPointF newPosition = QPointF(SvgUtil::fromUserSpace(path->position().x()), SvgUtil::fromUserSpace(path->position().y())); QSizeF newSize = QSizeF(SvgUtil::fromUserSpace(path->size().width()), SvgUtil::fromUserSpace(path->size().height())); path->setSize(newSize); path->setPosition(newPosition); obj = path; } } return obj; } KoShape * SvgParser::createObjectDirect(const KoXmlElement &b) { m_context.pushGraphicsContext(b); uploadStyleToContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); applyCurrentStyle(obj, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createObject(const KoXmlElement &b, const SvgStyles &style) { m_context.pushGraphicsContext(b); KoShape *obj = createShapeFromElement(b, m_context); if (obj) { obj->applyAbsoluteTransformation(m_context.currentGC()->matrix); const QPointF extraOffset = extraShapeOffset(obj, m_context.currentGC()->matrix); SvgStyles objStyle = style.isEmpty() ? m_context.styleParser().collectStyles(b) : style; m_context.styleParser().parseFont(objStyle); applyStyle(obj, objStyle, extraOffset); // handle id applyId(b.attribute("id"), obj); obj->setZIndex(m_context.nextZIndex()); } m_context.popGraphicsContext(); return obj; } KoShape * SvgParser::createShapeFromElement(const KoXmlElement &element, SvgLoadingContext &context) { KoShape *object = 0; const QString tagName = SvgUtil::mapExtendedShapeTag(element.tagName(), element); QList factories = KoShapeRegistry::instance()->factoriesForElement(KoXmlNS::svg, tagName); foreach (KoShapeFactoryBase *f, factories) { KoShape *shape = f->createDefaultShape(m_documentResourceManager); if (!shape) continue; SvgShape *svgShape = dynamic_cast(shape); if (!svgShape) { delete shape; continue; } // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(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 + // reset transformation that might come from the default shape shape->setTransformation(QTransform()); // reset border KoShapeStrokeModelSP oldStroke = shape->stroke(); shape->setStroke(KoShapeStrokeModelSP()); // reset fill shape->setBackground(QSharedPointer(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/global/kis_debug.cpp b/libs/global/kis_debug.cpp index 8f347d50e7..0597ff130f 100644 --- a/libs/global/kis_debug.cpp +++ b/libs/global/kis_debug.cpp @@ -1,115 +1,115 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_debug.h" #include "config-debug.h" #if HAVE_BACKTRACE #include #ifdef __GNUC__ #define HAVE_BACKTRACE_DEMANGLE #include #endif #endif #include #if HAVE_BACKTRACE static QString maybeDemangledName(char *name) { #ifdef HAVE_BACKTRACE_DEMANGLE const int len = strlen(name); QByteArray in = QByteArray::fromRawData(name, len); const int mangledNameStart = in.indexOf("(_"); if (mangledNameStart >= 0) { const int mangledNameEnd = in.indexOf('+', mangledNameStart + 2); if (mangledNameEnd >= 0) { int status; // if we forget about this line and the one that undoes its effect we don't change the // internal data of the QByteArray::fromRawData() ;) name[mangledNameEnd] = 0; char *demangled = abi::__cxa_demangle(name + mangledNameStart + 1, 0, 0, &status); name[mangledNameEnd] = '+'; if (demangled) { QString ret = QString::fromLatin1(name, mangledNameStart + 1) + QString::fromLatin1(demangled) + QString::fromLatin1(name + mangledNameEnd, len - mangledNameEnd); free(demangled); return ret; } } } #endif return QString::fromLatin1(name); } #endif QString kisBacktrace() { QString s; #if HAVE_BACKTRACE void *trace[256]; int n = backtrace(trace, 256); if (!n) { return s; } char **strings = backtrace_symbols(trace, n); s = QLatin1String("[\n"); for (int i = 0; i < n; ++i) s += QString::number(i) + QLatin1String(": ") + maybeDemangledName(strings[i]) + QLatin1Char('\n'); s += QLatin1String("]\n"); if (strings) { free(strings); } #endif return s; } -Q_LOGGING_CATEGORY(_30009, "krita.lib.resources") -Q_LOGGING_CATEGORY(_41000, "krita.general") -Q_LOGGING_CATEGORY(_41001, "krita.core") -Q_LOGGING_CATEGORY(_41002, "krita.registry") -Q_LOGGING_CATEGORY(_41003, "krita.tools") -Q_LOGGING_CATEGORY(_41004, "krita.tiles") -Q_LOGGING_CATEGORY(_41005, "krita.filters") -Q_LOGGING_CATEGORY(_41006, "krita.plugins") -Q_LOGGING_CATEGORY(_41007, "krita.ui") -Q_LOGGING_CATEGORY(_41008, "krita.file") -Q_LOGGING_CATEGORY(_41009, "krita.math") -Q_LOGGING_CATEGORY(_41010, "krita.render") -Q_LOGGING_CATEGORY(_41011, "krita.scripting") -Q_LOGGING_CATEGORY(_41012, "krita.input") -Q_LOGGING_CATEGORY(_41013, "krita.action") -Q_LOGGING_CATEGORY(_41014, "krita.tabletlog") -Q_LOGGING_CATEGORY(_41015, "krita.opengl") +Q_LOGGING_CATEGORY(_30009, "krita.lib.resources", QtInfoMsg) +Q_LOGGING_CATEGORY(_41000, "krita.general", QtInfoMsg) +Q_LOGGING_CATEGORY(_41001, "krita.core", QtInfoMsg) +Q_LOGGING_CATEGORY(_41002, "krita.registry", QtInfoMsg) +Q_LOGGING_CATEGORY(_41003, "krita.tools", QtInfoMsg) +Q_LOGGING_CATEGORY(_41004, "krita.tiles", QtInfoMsg) +Q_LOGGING_CATEGORY(_41005, "krita.filters", QtInfoMsg) +Q_LOGGING_CATEGORY(_41006, "krita.plugins", QtInfoMsg) +Q_LOGGING_CATEGORY(_41007, "krita.ui", QtInfoMsg) +Q_LOGGING_CATEGORY(_41008, "krita.file", QtInfoMsg) +Q_LOGGING_CATEGORY(_41009, "krita.math", QtInfoMsg) +Q_LOGGING_CATEGORY(_41010, "krita.render", QtInfoMsg) +Q_LOGGING_CATEGORY(_41011, "krita.scripting", QtInfoMsg) +Q_LOGGING_CATEGORY(_41012, "krita.input", QtInfoMsg) +Q_LOGGING_CATEGORY(_41013, "krita.action", QtInfoMsg) +Q_LOGGING_CATEGORY(_41014, "krita.tabletlog", QtDebugMsg) +Q_LOGGING_CATEGORY(_41015, "krita.opengl", QtInfoMsg) QString __methodName(const char *_prettyFunction) { std::string prettyFunction(_prettyFunction); size_t colons = prettyFunction.find("::"); size_t begin = prettyFunction.substr(0,colons).rfind(" ") + 1; size_t end = prettyFunction.rfind("(") - begin; return QString(std::string(prettyFunction.substr(begin,end) + "()").c_str()); } diff --git a/libs/image/metadata/kis_meta_data_schema.cc b/libs/image/metadata/kis_meta_data_schema.cc index 32af5ac584..fae173faf4 100644 --- a/libs/image/metadata/kis_meta_data_schema.cc +++ b/libs/image/metadata/kis_meta_data_schema.cc @@ -1,386 +1,386 @@ /* * Copyright (c) 2007,2009 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_meta_data_schema.h" #include #include #include #include #include #include "kis_meta_data_type_info_p.h" #include "kis_meta_data_schema_p.h" #include "kis_meta_data_value.h" using namespace KisMetaData; const QString Schema::TIFFSchemaUri = "http://ns.adobe.com/tiff/1.0/"; const QString Schema::EXIFSchemaUri = "http://ns.adobe.com/exif/1.0/"; const QString Schema::DublinCoreSchemaUri = "http://purl.org/dc/elements/1.1/"; const QString Schema::XMPSchemaUri = "http://ns.adobe.com/xap/1.0/"; const QString Schema::XMPRightsSchemaUri = "http://ns.adobe.com/xap/1.0/rights/"; const QString Schema::XMPMediaManagementUri = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"; const QString Schema::MakerNoteSchemaUri = "http://www.calligra.org/krita/xmp/MakerNote/1.0/"; const QString Schema::IPTCSchemaUri = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"; const QString Schema::PhotoshopSchemaUri = "http://ns.adobe.com/photoshop/1.0/"; bool Schema::Private::load(const QString& _fileName) { dbgImage << "Loading from " << _fileName; QDomDocument document; QString error; int ligne, column; QFile file(_fileName); if (document.setContent(&file, &error, &ligne, &column)) { QDomElement docElem = document.documentElement(); if (docElem.tagName() != "schema") { dbgImage << _fileName << ": invalid root name"; return false; } if (!docElem.hasAttribute("prefix")) { dbgImage << _fileName << ": missing prefix."; return false; } if (!docElem.hasAttribute("uri")) { dbgImage << _fileName << ": missing uri."; return false; } prefix = docElem.attribute("prefix"); uri = docElem.attribute("uri"); dbgImage << ppVar(prefix) << ppVar(uri); QDomNode n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "structures") { parseStructures(e); } else if (e.tagName() == "properties") { parseProperties(e); } } n = n.nextSibling(); } return true; } else { dbgImage << error << " at " << ligne << ", " << column << " in " << _fileName; return false; } } void Schema::Private::parseStructures(QDomElement& elt) { Q_ASSERT(elt.tagName() == "structures"); - dbgImage << "Parse sturctures"; + dbgImage << "Parse structures"; QDomNode n = elt.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { if (e.tagName() == "structure") { parseStructure(e); } else { errImage << "Invalid tag: " << e.tagName() << " in structures section"; } } n = n.nextSibling(); } } void Schema::Private::parseStructure(QDomElement& elt) { Q_ASSERT(elt.tagName() == "structure"); if (!elt.hasAttribute("name")) { errImage << "Name is required for a structure"; return; } QString structureName = elt.attribute("name"); if (structures.contains(structureName)) { errImage << structureName << " is defined twice"; return; } dbgImage << "Parsing structure " << structureName; if (!elt.hasAttribute("prefix")) { errImage << "prefix is required for structure " << structureName; return; } if (!elt.hasAttribute("uri")) { errImage << "uri is required for structure " << structureName; return; } QString structurePrefix = elt.attribute("prefix"); QString structureUri = elt.attribute("uri"); dbgImage << ppVar(structurePrefix) << ppVar(structureUri); Schema* schema = new Schema(structureUri, structurePrefix); QDomNode n = elt.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { EntryInfo info; QString name; if (parseEltType(e, info, name, false, false)) { if (schema->d->types.contains(name)) { errImage << structureName << " already contains a field " << name; } else { schema->d->types[ name ] = info; } } } n = n.nextSibling(); } structures[ structureName ] = TypeInfo::Private::createStructure(schema, structureName); } void Schema::Private::parseProperties(QDomElement& elt) { Q_ASSERT(elt.tagName() == "properties"); dbgImage << "Parse properties"; QDomNode n = elt.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { EntryInfo info; QString name; if (parseEltType(e, info, name, false, false)) { if (types.contains(name)) { errImage << name << " already defined."; } else { types[ name ] = info; } } } n = n.nextSibling(); } } bool Schema::Private::parseEltType(QDomElement& elt, EntryInfo& entryInfo, QString& name, bool ignoreStructure, bool ignoreName) { QString tagName = elt.tagName(); if (!ignoreName && !elt.hasAttribute("name")) { errImage << "Missing name attribute for tag " << tagName; return false; } name = elt.attribute("name"); // TODO parse qualifier if (tagName == "integer") { entryInfo.propertyType = TypeInfo::Private::Integer; return true; } else if (tagName == "boolean") { entryInfo.propertyType = TypeInfo::Private::Boolean; return true; } else if (tagName == "date") { entryInfo.propertyType = TypeInfo::Private::Date; return true; } else if (tagName == "text") { entryInfo.propertyType = TypeInfo::Private::Text; return true; } else if (tagName == "seq") { const TypeInfo* ei = parseAttType(elt, ignoreStructure); if (!ei) { ei = parseEmbType(elt, ignoreStructure); } if (!ei) { errImage << "No type defined for " << name; return false; } entryInfo.propertyType = TypeInfo::Private::orderedArray(ei); return true; } else if (tagName == "bag") { const TypeInfo* ei = parseAttType(elt, ignoreStructure); if (!ei) { ei = parseEmbType(elt, ignoreStructure); } if (!ei) { errImage << "No type defined for " << name; return false; } entryInfo.propertyType = TypeInfo::Private::unorderedArray(ei); return true; } else if (tagName == "alt") { const TypeInfo* ei = parseAttType(elt, ignoreStructure); if (!ei) { ei = parseEmbType(elt, ignoreStructure); } if (!ei) { errImage << "No type defined for " << name; return false; } entryInfo.propertyType = TypeInfo::Private::alternativeArray(ei); return true; } else if (tagName == "lang") { entryInfo.propertyType = TypeInfo::Private::LangArray; return true; } else if (tagName == "rational") { entryInfo.propertyType = TypeInfo::Private::Rational; return true; } else if (tagName == "gpscoordinate") { entryInfo.propertyType = TypeInfo::Private::GPSCoordinate; return true; } else if (tagName == "openedchoice" || tagName == "closedchoice") { entryInfo.propertyType = parseChoice(elt); return true; } else if (!ignoreStructure && structures.contains(tagName)) { entryInfo.propertyType = structures.value(tagName); return true; } errImage << tagName << " isn't a type."; return false; } const TypeInfo* Schema::Private::parseAttType(QDomElement& elt, bool ignoreStructure) { if (!elt.hasAttribute("type")) { return 0; } QString type = elt.attribute("type"); if (type == "integer") { return TypeInfo::Private::Integer; } else if (type == "boolean") { return TypeInfo::Private::Boolean; } else if (type == "date") { return TypeInfo::Private::Date; } else if (type == "text") { return TypeInfo::Private::Text; } else if (type == "rational") { return TypeInfo::Private::Rational; } else if (!ignoreStructure && structures.contains(type)) { return structures[type]; } errImage << "Unsupported type: " << type << " in an attribute"; return 0; } const TypeInfo* Schema::Private::parseEmbType(QDomElement& elt, bool ignoreStructure) { dbgImage << "Parse embbedded type for " << elt.tagName(); QDomNode n = elt.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { QString type = e.tagName(); if (type == "integer") { return TypeInfo::Private::Integer; } else if (type == "boolean") { return TypeInfo::Private::Boolean; } else if (type == "date") { return TypeInfo::Private::Date; } else if (type == "text") { return TypeInfo::Private::Text; } else if (type == "openedchoice" || type == "closedchoice") { return parseChoice(e); } else if (!ignoreStructure && structures.contains(type)) { return structures[type]; } } n = n.nextSibling(); } return 0; } const TypeInfo* Schema::Private::parseChoice(QDomElement& elt) { const TypeInfo* choiceType = parseAttType(elt, true); TypeInfo::PropertyType propertyType; if (elt.tagName() == "openedchoice") { propertyType = TypeInfo::OpenedChoice; } else { Q_ASSERT(elt.tagName() == "closedchoice"); propertyType = TypeInfo::ClosedChoice; } QDomNode n = elt.firstChild(); QList< TypeInfo::Choice > choices; while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { EntryInfo info; QString name; if (parseEltType(e, info, name, true, true)) { if (! choiceType) choiceType = info.propertyType; if (choiceType == info.propertyType) { QString text = e.text(); QVariant var = text; if (choiceType->propertyType() == TypeInfo::IntegerType) { var = var.toInt(); } else if (choiceType->propertyType() == TypeInfo::DateType) { // TODO QVariant date parser isn't very good with XMP date (it doesn't support YYYY and YYYY-MM var = var.toDateTime(); } choices.push_back(TypeInfo::Choice(Value(var), name)); } else { errImage << "All members of a choice need to be of the same type"; } } } n = n.nextSibling(); } return TypeInfo::Private::createChoice(propertyType, choiceType, choices); } Schema::Schema() : d(new Private) { } Schema::Schema(const QString & _uri, const QString & _ns) : d(new Private) { d->uri = _uri; d->prefix = _ns; } Schema::~Schema() { dbgImage << "Deleting schema " << d->uri << " " << d->prefix; dbgImage << kisBacktrace(); delete d; } const TypeInfo* Schema::propertyType(const QString& _propertyName) const { if (d->types.contains(_propertyName)) { return d->types.value(_propertyName).propertyType; } return 0; } const TypeInfo* Schema::structure(const QString& _structureName) const { return d->structures.value(_structureName); } QString Schema::uri() const { return d->uri; } QString Schema::prefix() const { return d->prefix; } QString Schema::generateQualifiedName(const QString & name) const { dbgImage << "generateQualifiedName for " << name; Q_ASSERT(!name.isEmpty() && !name.isNull()); return prefix() + ':' + name; } QDebug operator<<(QDebug debug, const KisMetaData::Schema &c) { debug.nospace() << "Uri = " << c.uri() << " Prefix = " << c.prefix(); return debug.space(); } diff --git a/libs/koplugin/KritaPluginDebug.cpp b/libs/koplugin/KritaPluginDebug.cpp index e69a461e5c..eb0ed8e539 100644 --- a/libs/koplugin/KritaPluginDebug.cpp +++ b/libs/koplugin/KritaPluginDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KritaPluginDebug.h" const QLoggingCategory &PLUGIN_LOG() \ { - static const QLoggingCategory category("krita.lib.plugin"); + static const QLoggingCategory category("krita.lib.plugin", QtInfoMsg); return category; } diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index caa307529a..eebc07d58a 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,604 +1,700 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include #include #include #include #include #include #include #include #include struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { return new Node(d->document->image(), activeNodes.first()); } return new Node(d->document->image(), d->document->image()->root()->firstChild()); } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); return new Node(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString::null; return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), value, d->document->image()->height()); } int Document::xOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().x(); } void Document::setXOffset(int x) { if (!d->document) return; if (!d->document->image()) return; resizeImage(x, d->document->image()->bounds().y(), d->document->image()->width(), d->document->image()->height()); } int Document::yOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().y(); } void Document::setYOffset(int y) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), y, d->document->image()->width(), d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes(); } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes(); } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->deleteLater(); } } KisPart::instance()->removeDocument(d->document); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename); const QByteArray outputFormat = outputFormatString.toLatin1(); - return d->document->exportDocument(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration()); + return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(); } void Document::resizeImage(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc; rc.setX(x); rc.setY(y); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); image->scaleImage(rc.size(), xres, yres, actualStrategy); } void Document::rotateImage(double radians) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->rotateImage(radians); } void Document::shearImage(double angleX, double angleY) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->shear(angleX, angleY); } bool Document::save() { if (!d->document) return false; - return d->document->save(true, 0); + bool retval = d->document->save(true, 0); + d->document->waitForSavingToComplete(); + + return retval; } bool Document::saveAs(const QString &filename) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename); const QByteArray outputFormat = outputFormatString.toLatin1(); - return d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); + bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); + d->document->waitForSavingToComplete(); + + return retval; } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType == "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } +QList Document::horizontalGuides() const +{ + QList lines; + if (!d->document || !d->document->image()) return lines; + KisCoordinatesConverter converter; + converter.setImage(d->document->image()); + QTransform transform = converter.imageToDocumentTransform().inverted(); + QList untransformedLines = d->document->guidesConfig().horizontalGuideLines(); + for (int i = 0; i< untransformedLines.size(); i++) { + qreal line = untransformedLines[i]; + lines.append(transform.map(QPointF(line, line)).x()); + } + return lines; +} + +QList Document::verticalGuides() const +{ + QList lines; + if (!d->document || !d->document->image()) return lines; + KisCoordinatesConverter converter; + converter.setImage(d->document->image()); + QTransform transform = converter.imageToDocumentTransform().inverted(); + QList untransformedLines = d->document->guidesConfig().verticalGuideLines(); + for (int i = 0; i< untransformedLines.size(); i++) { + qreal line = untransformedLines[i]; + lines.append(transform.map(QPointF(line, line)).y()); + } + return lines; +} + +bool Document::guidesVisible() const +{ + return d->document->guidesConfig().lockGuides(); +} + +bool Document::guidesLocked() const +{ + return d->document->guidesConfig().showGuides(); +} + +void Document::setHorizontalGuides(const QList &lines) +{ + if (!d->document) return; + KisGuidesConfig config = d->document->guidesConfig(); + KisCoordinatesConverter converter; + converter.setImage(d->document->image()); + QTransform transform = converter.imageToDocumentTransform(); + QList transformedLines; + for (int i = 0; i< lines.size(); i++) { + qreal line = lines[i]; + transformedLines.append(transform.map(QPointF(line, line)).x()); + } + config.setHorizontalGuideLines(transformedLines); + d->document->setGuidesConfig(config); +} + +void Document::setVerticalGuides(const QList &lines) +{ + if (!d->document) return; + KisGuidesConfig config = d->document->guidesConfig(); + KisCoordinatesConverter converter; + converter.setImage(d->document->image()); + QTransform transform = converter.imageToDocumentTransform(); + QList transformedLines; + for (int i = 0; i< lines.size(); i++) { + qreal line = lines[i]; + transformedLines.append(transform.map(QPointF(line, line)).y()); + } + config.setVerticalGuideLines(transformedLines); + d->document->setGuidesConfig(config); +} + +void Document::setGuidesVisible(bool visible) +{ + if (!d->document) return; + KisGuidesConfig config = d->document->guidesConfig(); + config.setShowGuides(visible); + d->document->setGuidesConfig(config); +} + +void Document::setGuidesLocked(bool locked) +{ + if (!d->document) return; + KisGuidesConfig config = d->document->guidesConfig(); + config.setLockGuides(locked); + d->document->setGuidesConfig(config); +} + QPointer Document::document() const { return d->document; } diff --git a/libs/libkis/Document.h b/libs/libkis/Document.h index 640f3c687c..1ff849207a 100644 --- a/libs/libkis/Document.h +++ b/libs/libkis/Document.h @@ -1,570 +1,622 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef LIBKIS_DOCUMENT_H #define LIBKIS_DOCUMENT_H #include #include "kritalibkis_export.h" #include "libkis.h" class KisDocument; /** * The Document class encapsulates a Krita Document/Image. A Krita document is an Image with * a filename. Libkis does not differentiate between a document and an image, like Krita does * internally. */ class KRITALIBKIS_EXPORT Document : public QObject { Q_OBJECT Q_DISABLE_COPY(Document) public: explicit Document(KisDocument *document, QObject *parent = 0); ~Document() override; bool operator==(const Document &other) const; bool operator!=(const Document &other) const; + /** + * @brief horizontalGuides + * The horizontal guides. + * @return a list of the horizontal positions of guides. + */ + QList horizontalGuides() const; + /** + * @brief verticalGuides + * The vertical guide lines. + * @return a list of vertical guides. + */ + QList verticalGuides() const; + + /** + * @brief guidesVisible + * Returns guide visiiblity. + * @return whether the guides are visible. + */ + bool guidesVisible() const; + /** + * @brief guidesLocked + * Returns guide lockedness. + * @return whether the guides are locked. + */ + bool guidesLocked() const; + public Q_SLOTS: /** * Batchmode means that no actions on the document should show dialogs or popups. * @return true if the document is in batchmode. */ bool batchmode() const; /** * Set batchmode to @param value. If batchmode is true, then there should be no popups * or dialogs shown to the user. */ void setBatchmode(bool value); /** * @brief activeNode retrieve the node that is currently active in the currently active window * @return the active node. If there is no active window, the first child node is returned. */ Node* activeNode() const; /** * @brief setActiveNode make the given node active in the currently active view and window * @param value the node to make active. */ void setActiveNode(Node* value); /** * @brief toplevelNodes return a list with all top level nodes in the image graph */ QList topLevelNodes() const; /** * @brief nodeByName searches the node tree for a node with the given name and returns it * @param name the name of the node * @return the first node with the given name or 0 if no node is found */ Node *nodeByName(const QString &name) const; /** * colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @return the color depth. */ QString colorDepth() const; /** * @brief colorModel retrieve the current color model of this document: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @return the internal color model string. */ QString colorModel() const; /** * @return the name of the current color profile */ QString colorProfile() const; /** * @brief setColorProfile set the color profile of the image to the given profile. The profile has to * be registered with krita and be compatible with the current color model and depth; the image data * is not converted. * @param colorProfile * @return false if the colorProfile name does not correspond to to a registered profile or if assigning * the profile failed. */ bool setColorProfile(const QString &colorProfile); /** * @brief setColorSpace convert the nodes and the image to the given colorspace. The conversion is * done with Perceptual as intent, High Quality and No LCMS Optimizations as flags and no blackpoint * compensation. * * @param colorModel A string describing the color model of the image: *
    *
  • A: Alpha mask
  • *
  • RGBA: RGB with alpha channel (The actual order of channels is most often BGR!)
  • *
  • XYZA: XYZ with alpha channel
  • *
  • LABA: LAB with alpha channel
  • *
  • CMYKA: CMYK with alpha channel
  • *
  • GRAYA: Gray with alpha channel
  • *
  • YCbCrA: YCbCr with alpha channel
  • *
* @param colorDepth A string describing the color depth of the image: *
    *
  • U8: unsigned 8 bits integer, the most common type
  • *
  • U16: unsigned 16 bits integer
  • *
  • F16: half, 16 bits floating point. Only available if Krita was built with OpenEXR
  • *
  • F32: 32 bits floating point
  • *
* @param colorProfile a valid color profile for this color model and color depth combination. * @return false the combination of these arguments does not correspond to a colorspace. */ bool setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile); /** * @brief documentInfo creates and XML document representing document and author information. * @return a string containing a valid XML document with the right information about the document * and author. The DTD can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ * * @code * * * * * My Document * * * * * Unknown * 1 * 35 * 2017-02-27T20:15:09 * 2017-02-27T20:14:33 * * * * Boudewijn Rempt * * * * * * * * * * * * * * * @endcode * */ QString documentInfo() const; /** * @brief setDocumentInfo set the Document information to the information contained in document * @param document A string containing a valid XML document that conforms to the document-info DTD * that can be found here: * * https://phabricator.kde.org/source/krita/browse/master/krita/dtd/ */ void setDocumentInfo(const QString &document); /** * @return the full path to the document, if it has been set. */ QString fileName() const; /** * @brief setFileName set the full path of the document to @param value */ void setFileName(QString value); /** * @return the height of the image in pixels */ int height() const; /** * @brief setHeight resize the document to @param value height. This is a canvas resize, not a scale. */ void setHeight(int value); /** * @return the name of the document. This is the title field in the @see documentInfo */ QString name() const; /** * @brief setName sets the name of the document to @param value. This is the title field in the @see documentInfo */ void setName(QString value); /** * @return the resolution in pixels per inch */ int resolution() const; /** * @brief setResolution set the resolution of the image; this does not scale the image * @param value the resolution in pixels per inch */ void setResolution(int value); /** * @brief rootNode the root node is the invisible group layer that contains the entire node * hierarchy. * @return the root of the image */ Node* rootNode() const; /** * @brief selection Create a Selection object around the global selection, if there is one. * @return the global selection or None if there is no global selection. */ Selection* selection() const; /** * @brief setSelection set or replace the global selection * @param value a valid selection object. */ void setSelection(Selection* value); /** * @return the width of the image in pixels. */ int width() const; /** * @brief setWidth resize the document to @param value width. This is a canvas resize, not a scale. */ void setWidth(int value); /** * @return the left edge of the canvas in pixels. */ int xOffset() const; /** * @brief setXOffset sets the left edge of the canvas to @param x. */ void setXOffset(int x); /** * @return the top edge of the canvas in pixels. */ int yOffset() const; /** * @brief setYOffset sets the top edge of the canvas to @param y. */ void setYOffset(int y); /** * @return xRes the horizontal resolution of the image in pixels per pt (there are 72 pts to an inch) */ double xRes() const; /** * @brief setXRes set the horizontal resolution of the image to xRes in pixels per pt. (there are 72 pts to an inch) */ void setXRes(double xRes) const; /** * @return yRes the vertical resolution of the image in pixels per pt (there are 72 pts to an inch) */ double yRes() const; /** * @brief setYRes set the vertical resolution of the image to yRes in pixels per pt. (there are 72 pts to an inch) */ void setYRes(double yRes) const; /** * @brief pixelData reads the given rectangle from the image projection and returns it as a byte * array. The pixel data starts top-left, and is ordered row-first. * * The byte array can be interpreted as follows: 8 bits images have one byte per channel, * and as many bytes as there are channels. 16 bits integer images have two bytes per channel, * representing an unsigned short. 16 bits float images have two bytes per channel, representing * a half, or 16 bits float. 32 bits float images have four bytes per channel, representing a * float. * * You can read outside the image boundaries; those pixels will be transparent black. * * The order of channels is: * *
    *
  • Integer RGBA: Blue, Green, Red, Alpha *
  • Float RGBA: Red, Green, Blue, Alpha *
  • LabA: L, a, b, Alpha *
  • CMYKA: Cyan, Magenta, Yellow, Key, Alpha *
  • XYZA: X, Y, Z, A *
  • YCbCrA: Y, Cb, Cr, Alpha *
* * The byte array is a copy of the original image data. In Python, you can use bytes, bytearray * and the struct module to interpret the data and construct, for instance, a Pillow Image object. * * @param x x position from where to start reading * @param y y position from where to start reading * @param w row length to read * @param h number of rows to read * @return a QByteArray with the pixel data. The byte array may be empty. */ QByteArray pixelData(int x, int y, int w, int h) const; /** * @brief close Close the document: remove it from Krita's internal list of documents and * close all views. If the document is modified, you should save it first. There will be * no prompt for saving. * @return true if the document is closed. */ bool close(); /** * @brief crop the image to rectangle described by @param x, @param y, * @param w and @param h */ void crop(int x, int y, int w, int h); /** * @brief exportImage export the image, without changing its URL to the given path. * @param filename the full path to which the image is to be saved * @param exportConfiguration a configuration object appropriate to the file format. * An InfoObject will used to that configuration. * * The supported formats have specific configurations that must be used when in * batchmode. They are described below: * *\b png *
    *
  • alpha: bool (True or False) *
  • compression: int (1 to 9) *
  • forceSRGB: bool (True or False) *
  • indexed: bool (True or False) *
  • interlaced: bool (True or False) *
  • saveSRGBProfile: bool (True or False) *
  • transparencyFillcolor: rgb (Ex:[255,255,255]) *
* *\b jpeg *
    *
  • baseline: bool (True or False) *
  • exif: bool (True or False) *
  • filters: bool (['ToolInfo', 'Anonymizer']) *
  • forceSRGB: bool (True or False) *
  • iptc: bool (True or False) *
  • is_sRGB: bool (True or False) *
  • optimize: bool (True or False) *
  • progressive: bool (True or False) *
  • quality: int (0 to 100) *
  • saveProfile: bool (True or False) *
  • smoothing: int (0 to 100) *
  • subsampling: int (0 to 3) *
  • transparencyFillcolor: rgb (Ex:[255,255,255]) *
  • xmp: bool (True or False) *
* @return true if the export succeeded, false if it failed. */ bool exportImage(const QString &filename, const InfoObject &exportConfiguration); /** * @brief flatten all layers in the image */ void flatten(); /** * @brief resizeImage resizes the canvas to the given left edge, top edge, width and height. * Note: This doesn't scale, use scale image for that. * @param x the new left edge * @param y the new top edge * @param w the new width * @param h the new height */ void resizeImage(int x, int y, int w, int h); /** * @brief scaleImage * @param w the new width * @param h the new height * @param xres the new xres * @param yres the new yres * @param strategy the scaling strategy. There's several ones amongst these that aren't available in the regular UI. * The list of filters is extensible and can be retrieved with Krita::filter *
    *
  • Hermite
  • *
  • Bicubic - Adds pixels using the color of surrounding pixels. Produces smoother tonal gradations than Bilinear.
  • *
  • Box - Replicate pixels in the image. Preserves all the original detail, but can produce jagged effects.
  • *
  • Bilinear - Adds pixels averaging the color values of surrounding pixels. Produces medium quality results when the image is scaled from half to two times the original size.
  • *
  • Bell
  • *
  • BSpline
  • *
  • Kanczos3 - Offers similar results than Bicubic, but maybe a little bit sharper. Can produce light and dark halos along strong edges.
  • *
  • Mitchell
  • *
*/ void scaleImage(int w, int h, int xres, int yres, QString strategy); /** * @brief rotateImage * Rotate the image by the given radians. * @param radians the amount you wish to rotate the image in radians */ void rotateImage(double radians); /** * @brief shearImage shear the whole image. * @param angleX the X-angle in degrees to shear by * @param angleY the Y-angle in degrees to shear by */ void shearImage(double angleX, double angleY); /** * @brief save the image to its currently set path. The modified flag of the * document will be reset * @return true if saving succeeded, false otherwise. */ bool save(); /** * @brief saveAs save the document under the @param filename. The document's * filename will be reset to @param filename. * @param filename the new filename (full path) for the document * @return true if saving succeeded, false otherwise. */ bool saveAs(const QString &filename); /** * @brief createNode create a new node of the given type. The node is not added * to the node hierarchy; you need to do that by finding the right parent node, * getting its list of child nodes and adding the node in the right place, then * calling Node::SetChildNodes * * @param name The name of the node * * @param nodeType The type of the node. Valid types are: *
    *
  • paintlayer *
  • grouplayer *
  • filelayer *
  • filterlayer *
  • filllayer *
  • clonelayer *
  • vectorlayer *
  • transparencymask *
  • filtermask *
  • transformmask *
  • selectionmask *
* * When relevant, the new Node will have the colorspace of the image by default; * that can be changed with Node::setColorSpace. * * The settings and selections for relevant layer and mask types can also be set * after the Node has been created. * * @return the new Node. */ Node* createNode(const QString &name, const QString &nodeType); /** * @brief projection creates a QImage from the rendered image or * a cutout rectangle. */ QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; /** * @brief thumbnail create a thumbnail of the given dimensions. * * If the requested size is too big a null QImage is created. * * @return a QImage representing the layer contents. */ QImage thumbnail(int w, int h) const; /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void lock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void unlock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ void waitForDone(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ bool tryBarrierLock(); /** * Why this should be used, When it should be used, How it should be used, * and warnings about when not. */ bool isIdle(); /** * Starts a synchronous recomposition of the projection: everything will * wait until the image is fully recomputed. */ void refreshProjection(); + /** + * @brief setHorizontalGuides + * replace all existing horizontal guides with the entries in the list. + * @param list a list of floats containing the new guides. + */ + void setHorizontalGuides(const QList &lines); + /** + * @brief setVerticalGuides + * replace all existing horizontal guides with the entries in the list. + * @param list a list of floats containing the new guides. + */ + void setVerticalGuides(const QList &lines); + + /** + * @brief setGuidesVisible + * set guides visible on this document. + * @param visible whether or not the guides are visible. + */ + void setGuidesVisible(bool visible); + + /** + * @brief setGuidesLocked + * set guides locked on this document + * @param locked wether or not to lock the guides on this document. + */ + void setGuidesLocked(bool locked); private: friend class Krita; friend class Window; friend class Filter; QPointer document() const; private: struct Private; Private *const d; }; #endif // LIBKIS_DOCUMENT_H diff --git a/libs/libkis/Selection.cpp b/libs/libkis/Selection.cpp index 766a893144..9f6b1bed00 100644 --- a/libs/libkis/Selection.cpp +++ b/libs/libkis/Selection.cpp @@ -1,315 +1,323 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Selection.h" #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include #include #include 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 xPos = d->selection->x(); + if (d->selection->hasPixelSelection()) { + xPos = d->selection->selectedExactRect().x(); + } + return xPos; } int Selection::y() const { if (!d->selection) return 0; - return d->selection->y(); + int yPos = d->selection->y(); + if (d->selection->hasPixelSelection()) { + yPos = d->selection->selectedExactRect().y(); + } + return yPos; } 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/tests/TestDocument.cpp b/libs/libkis/tests/TestDocument.cpp index f27cdd0b5e..8ea49a2fba 100644 --- a/libs/libkis/tests/TestDocument.cpp +++ b/libs/libkis/tests/TestDocument.cpp @@ -1,137 +1,165 @@ /* 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. */ #include "TestDocument.h" #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include 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 } } } +void TestDocument::testCreateAndSave() +{ + KisDocument *kisdoc = KisPart::instance()->createDocument(); + KisImageSP image = new KisImage(0, 5000, 5000, KoColorSpaceRegistry::instance()->rgb16(), "test"); + KisNodeSP layer = new KisPaintLayer(image, "test1", 128); + KisFillPainter gc(layer->paintDevice()); + gc.fillRect(100, 100, 4000, 4000, KoColor(Qt::red, layer->colorSpace())); + image->addNode(layer); + for(int i = 0; i < 10; ++i) { + image->addNode(layer->clone()); + } + kisdoc->setCurrentImage(image); + + Document d(kisdoc); + d.setBatchmode(true); + d.refreshProjection(); + + QString filename = QDir::tempPath() + "/TestDocumentTestCreateAndSave.kra"; + + d.saveAs(filename); + QVERIFY2(QFileInfo(filename).exists(), filename.toUtf8()); + + Document *d2 = Krita::instance()->openDocument(filename); + Q_ASSERT(d2->colorDepth() == "U16"); + +} + QTEST_MAIN(TestDocument) diff --git a/libs/libkis/tests/TestDocument.h b/libs/libkis/tests/TestDocument.h index 65d5584af8..61f36836f6 100644 --- a/libs/libkis/tests/TestDocument.h +++ b/libs/libkis/tests/TestDocument.h @@ -1,35 +1,36 @@ /* 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 class TestDocument : public QObject { Q_OBJECT private Q_SLOTS: void testSetColorSpace(); void testSetColorProfile(); void testPixelData(); void testThumbnail(); + void testCreateAndSave(); }; #endif diff --git a/libs/odf/OdfDebug.cpp b/libs/odf/OdfDebug.cpp index 6caa0f153d..d8a6d69fe4 100644 --- a/libs/odf/OdfDebug.cpp +++ b/libs/odf/OdfDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "OdfDebug.h" const QLoggingCategory &ODF_LOG() \ { - static const QLoggingCategory category("krita.lib.odf"); + static const QLoggingCategory category("krita.lib.odf", QtInfoMsg); return category; } diff --git a/libs/pigment/DebugPigment.cpp b/libs/pigment/DebugPigment.cpp index a75300d8a1..e7e928b037 100644 --- a/libs/pigment/DebugPigment.cpp +++ b/libs/pigment/DebugPigment.cpp @@ -1,26 +1,26 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DebugPigment.h" const QLoggingCategory &PIGMENT_log() \ { - static const QLoggingCategory category("krita.lib.pigment"); + static const QLoggingCategory category("krita.lib.pigment", QtInfoMsg); return category; } diff --git a/libs/pigment/KoColorConversionTransformationFactory.h b/libs/pigment/KoColorConversionTransformationFactory.h index a0059b2648..be0ec5fc1b 100644 --- a/libs/pigment/KoColorConversionTransformationFactory.h +++ b/libs/pigment/KoColorConversionTransformationFactory.h @@ -1,103 +1,103 @@ /* * Copyright (c) 2007 Cyrille Berger * * 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_CONVERSION_TRANSFORMATION_FACTORY_H_ #define _KO_COLOR_CONVERSION_TRANSFORMATION_FACTORY_H_ class KoColorSpace; class KoColorConversionTransformation; #include #include /** * Factory to create a color transformation between two colorsapces. */ class KRITAPIGMENT_EXPORT KoColorConversionTransformationFactory : public KoColorConversionTransformationAbstractFactory { public: /** - * Create a color conversion tranformation factory, that can create + * Create a color conversion transformation factory, that can create * KoColorConversionTrasnformation object between a source colorspace * and a destination colorspace. * * @param _srcModelId id for the source color model * @param _srcDepthId id for the source depth * @param _dstModelId id for the destination color model * @param _dstDepthId id for the destination depth * @param _srcProfile name of the source profile, or empty if any profile * @param _dstProfile name of the destination profile, or empty if any profile */ KoColorConversionTransformationFactory(const QString &_srcModelId, const QString &_srcDepthId, const QString &_srcProfile, const QString &_dstModelId, const QString &_dstDepthId, const QString &_dstProfile); ~KoColorConversionTransformationFactory() override; /** * @return true if this factory creates a color conversion transformation which * conserve color information (typical color transformation that lose that information * is anything to grayscale). */ virtual bool conserveColorInformation() const = 0; /** * @return true if this factory creates a color conversion transformation which * conserve the dynamic range of the color. */ virtual bool conserveDynamicRange() const = 0; public: /** * @return the id of the source color model */ QString srcColorModelId() const; /** * @return the id of the source color depth */ QString srcColorDepthId() const; /** * @return the name of the source profile (note that an empty name * means all profiles can be used) */ QString srcProfile() const; /** * @return the id of the destination color model */ QString dstColorModelId() const; /** * @return the id of the destination color depth */ QString dstColorDepthId() const; /** * @return the name of the destination profile (note that an empty name * means all profiles can be used) */ QString dstProfile() const; protected: /** * @param srcCS source color space * @return true if the color space given as argument can be used as a source colorspace */ bool canBeSource(const KoColorSpace* srcCS) const; /** * @param dstCS destination color space * @return true if the color space given as argument can be used as a destination colorspace */ bool canBeDestination(const KoColorSpace* dstCS) const; private: struct Private; Private* const d; }; #endif diff --git a/libs/pigment/KoColorSpaceFactory.cpp b/libs/pigment/KoColorSpaceFactory.cpp index c71682eacf..27b1533039 100644 --- a/libs/pigment/KoColorSpaceFactory.cpp +++ b/libs/pigment/KoColorSpaceFactory.cpp @@ -1,107 +1,107 @@ /* * Copyright (c) 2010 Cyrille Berger * * 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 "KoColorSpaceFactory.h" #include "DebugPigment.h" #include #include "KoColorProfile.h" #include "KoColorSpace.h" #include "KoColorSpaceRegistry.h" struct Q_DECL_HIDDEN KoColorSpaceFactory::Private { QList colorprofiles; QList colorspaces; QHash availableColorspaces; QMutex mutex; #ifndef NDEBUG QHash stackInformation; #endif }; KoColorSpaceFactory::KoColorSpaceFactory() : d(new Private) { } KoColorSpaceFactory::~KoColorSpaceFactory() { #ifndef NDEBUG // Check that all color spaces have been released int count = 0; count += d->availableColorspaces.size(); for(QHash::const_iterator it = d->stackInformation.constBegin(); it != d->stackInformation.constEnd(); ++it) { errorPigment << "*******************************************"; - errorPigment << it.key()->id() << " still in used, and grabed in: "; + errorPigment << it.key()->id() << " still in used, and grabbed in: "; errorPigment << it.value(); } if( count != d->colorspaces.size()) { errorPigment << (d->colorspaces.size() - count) << " colorspaces are still used"; } Q_ASSERT(count == d->colorspaces.size()); #endif Q_FOREACH (KoColorSpace* cs, d->colorspaces) { delete cs; } Q_FOREACH (KoColorProfile* profile, d->colorprofiles) { KoColorSpaceRegistry::instance()->removeProfile(profile); delete profile; } delete d; } const KoColorProfile *KoColorSpaceFactory::colorProfile(const QByteArray &rawData, KoColorSpaceFactory::ProfileRegistrationInterface *registrationInterface) const { KoColorProfile* colorProfile = createColorProfile(rawData); if (colorProfile && colorProfile->valid()) { if (const KoColorProfile* existingProfile = registrationInterface->profileByName(colorProfile->name())) { delete colorProfile; return existingProfile; } registrationInterface->registerNewProfile(colorProfile); d->colorprofiles.append(colorProfile); } return colorProfile; } const KoColorSpace *KoColorSpaceFactory::grabColorSpace(const KoColorProfile * profile) { QMutexLocker l(&d->mutex); Q_ASSERT(profile); auto it = d->availableColorspaces.find(profile->name()); KoColorSpace* cs; if (it == d->availableColorspaces.end()) { cs = createColorSpace(profile); if (cs) { d->availableColorspaces[profile->name()] = cs; } } else { cs = it.value(); } return cs; } diff --git a/libs/pigment/compositeops/KoOptimizedCompositeOpOver32.h b/libs/pigment/compositeops/KoOptimizedCompositeOpOver32.h index e6f958a2cc..a1b7861968 100644 --- a/libs/pigment/compositeops/KoOptimizedCompositeOpOver32.h +++ b/libs/pigment/compositeops/KoOptimizedCompositeOpOver32.h @@ -1,248 +1,287 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOOPTIMIZEDCOMPOSITEOPOVER32_H_ #define KOOPTIMIZEDCOMPOSITEOPOVER32_H_ #include "KoCompositeOpBase.h" #include "KoCompositeOpRegistry.h" #include "KoStreamedMath.h" +template +struct OptiDiv { + static ALWAYS_INLINE float divScalar(const float& divident, const float& divisor) { +#ifdef __SSE__ + float result; + + __m128 x = _mm_set_ss(divisor); + __m128 y = _mm_set_ss(divident); + x = _mm_rcp_ss(x); + x = _mm_mul_ss(x, y); + + + _mm_store_ss(&result, x); + return result; +#else + return divident / divisor; +#endif + + } + + static ALWAYS_INLINE Vc::float_v divVector(Vc::float_v::AsArg divident, Vc::float_v::AsArg divisor) { +#ifdef __SSE__ + return divident * Vc::reciprocal(divisor); +#else + return divident / divisor; +#endif + + } + +}; + + template struct OverCompositor32 { struct OptionalParams { OptionalParams(const KoCompositeOp::ParameterInfo& params) : channelFlags(params.channelFlags) { } const QBitArray &channelFlags; }; // \see docs in AlphaDarkenCompositor32 template static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { Q_UNUSED(oparams); Vc::float_v src_alpha; Vc::float_v dst_alpha; src_alpha = KoStreamedMath<_impl>::template fetch_alpha_32(src); bool haveOpacity = opacity != 1.0; Vc::float_v opacity_norm_vec(opacity); Vc::float_v uint8Max((float)255.0); Vc::float_v uint8MaxRec1((float)1.0 / 255.0); Vc::float_v zeroValue(Vc::Zero); Vc::float_v oneValue(Vc::One); src_alpha *= opacity_norm_vec; if (haveMask) { Vc::float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask); src_alpha *= mask_vec * uint8MaxRec1; } // The source cannot change the colors in the destination, // since its fully transparent if ((src_alpha == zeroValue).isFull()) { return; } dst_alpha = KoStreamedMath<_impl>::template fetch_alpha_32(dst); Vc::float_v src_c1; Vc::float_v src_c2; Vc::float_v src_c3; Vc::float_v dst_c1; Vc::float_v dst_c2; Vc::float_v dst_c3; KoStreamedMath<_impl>::template fetch_colors_32(src, src_c1, src_c2, src_c3); Vc::float_v src_blend; Vc::float_v new_alpha; if ((dst_alpha == uint8Max).isFull()) { new_alpha = dst_alpha; src_blend = src_alpha * uint8MaxRec1; } else if ((dst_alpha == zeroValue).isFull()) { new_alpha = src_alpha; src_blend = oneValue; } else { /** * The value of new_alpha can have *some* zero values, * which will result in NaN values while division. But * when converted to integers these NaN values will * be converted to zeroes, which is exactly what we need */ new_alpha = dst_alpha + (uint8Max - dst_alpha) * src_alpha * uint8MaxRec1; - src_blend = src_alpha / new_alpha; + + // Optimized version of: + // src_blend = src_alpha / new_alpha; + src_blend = OptiDiv<_impl>::divVector(src_alpha, new_alpha); + } if (!(src_blend == oneValue).isFull()) { KoStreamedMath<_impl>::template fetch_colors_32(dst, dst_c1, dst_c2, dst_c3); dst_c1 = src_blend * (src_c1 - dst_c1) + dst_c1; dst_c2 = src_blend * (src_c2 - dst_c2) + dst_c2; dst_c3 = src_blend * (src_c3 - dst_c3) + dst_c3; } else { if (!haveMask && !haveOpacity) { memcpy(dst, src, 4 * Vc::float_v::size()); return; } else { // opacity has changed the alpha of the source, // so we can't just memcpy the bytes dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } } KoStreamedMath<_impl>::write_channels_32(dst, new_alpha, dst_c1, dst_c2, dst_c3); } template static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { using namespace Arithmetic; const qint32 alpha_pos = 3; const float uint8Rec1 = 1.0 / 255.0; const float uint8Max = 255.0; float srcAlpha = src[alpha_pos]; srcAlpha *= opacity; if (haveMask) { srcAlpha *= float(*mask) * uint8Rec1; } if (srcAlpha != 0.0) { float dstAlpha = dst[alpha_pos]; float srcBlendNorm; if (dstAlpha == uint8Max) { srcBlendNorm = srcAlpha * uint8Rec1; } else if (dstAlpha == 0.0) { dstAlpha = srcAlpha; srcBlendNorm = 1.0; if (!allChannelsFlag) { pixel_type *d = reinterpret_cast(dst); *d = 0; // dstAlpha is already null } } else { dstAlpha += (uint8Max - dstAlpha) * srcAlpha * uint8Rec1; - srcBlendNorm = srcAlpha / dstAlpha; + // Optimized version of: + // srcBlendNorm = srcAlpha / dstAlpha); + srcBlendNorm = OptiDiv<_impl>::divScalar(srcAlpha, dstAlpha); + } if(allChannelsFlag) { if (srcBlendNorm == 1.0) { if (!alphaLocked) { const pixel_type *s = reinterpret_cast(src); pixel_type *d = reinterpret_cast(dst); *d = *s; } else { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; } } else if (srcBlendNorm != 0.0){ dst[0] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[0], src[0], srcBlendNorm); dst[1] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[1], src[1], srcBlendNorm); dst[2] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[2], src[2], srcBlendNorm); } } else { const QBitArray &channelFlags = oparams.channelFlags; if (srcBlendNorm == 1.0) { if(channelFlags.at(0)) dst[0] = src[0]; if(channelFlags.at(1)) dst[1] = src[1]; if(channelFlags.at(2)) dst[2] = src[2]; } else if (srcBlendNorm != 0.0) { if(channelFlags.at(0)) dst[0] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[0], src[0], srcBlendNorm); if(channelFlags.at(1)) dst[1] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[1], src[1], srcBlendNorm); if(channelFlags.at(2)) dst[2] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[2], src[2], srcBlendNorm); } } if (!alphaLocked) { dst[alpha_pos] = KoStreamedMath<_impl>::round_float_to_uint(dstAlpha); } } } }; /** * An optimized version of a composite op for the use in 4 byte * colorspaces with alpha channel placed at the last byte of * the pixel: C1_C2_C3_A. */ template class KoOptimizedCompositeOpOver32 : public KoCompositeOp { public: KoOptimizedCompositeOpOver32(const KoColorSpace* cs) : KoCompositeOp(cs, COMPOSITE_OVER, i18n("Normal"), KoCompositeOp::categoryMix()) {} using KoCompositeOp::composite; virtual void composite(const KoCompositeOp::ParameterInfo& params) const { if(params.maskRowStart) { composite(params); } else { composite(params); } } template inline void composite(const KoCompositeOp::ParameterInfo& params) const { if (params.channelFlags.isEmpty() || params.channelFlags == QBitArray(4, true)) { KoStreamedMath<_impl>::template genericComposite32 >(params); } else { const bool allChannelsFlag = params.channelFlags.at(0) && params.channelFlags.at(1) && params.channelFlags.at(2); const bool alphaLocked = !params.channelFlags.at(3); if (allChannelsFlag && alphaLocked) { KoStreamedMath<_impl>::template genericComposite32_novector >(params); } else if (!allChannelsFlag && !alphaLocked) { KoStreamedMath<_impl>::template genericComposite32_novector >(params); } else /*if (!allChannelsFlag && alphaLocked) */{ KoStreamedMath<_impl>::template genericComposite32_novector >(params); } } } }; #endif // KOOPTIMIZEDCOMPOSITEOPOVER32_H_ diff --git a/libs/pigment/tests/CCSGraph.cpp b/libs/pigment/tests/CCSGraph.cpp index a1f57ca2f2..efdb0e2c8e 100644 --- a/libs/pigment/tests/CCSGraph.cpp +++ b/libs/pigment/tests/CCSGraph.cpp @@ -1,119 +1,119 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * * 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 #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColorConversionSystem.h" #include #include #include struct FriendOfColorSpaceRegistry { static QString toDot() { return KoColorSpaceRegistry::instance()->colorConversionSystem()->toDot(); } static QString bestPathToDot(const QString &srcKey, const QString &dstKey) { return KoColorSpaceRegistry::instance()->colorConversionSystem()->bestPathToDot(srcKey, dstKey); } }; int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCommandLineParser parser; parser.addVersionOption(); parser.addHelpOption(); // Initialize the list of options parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graphs"), i18n("return the list of available graphs"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("graph"), i18n("specify the type of graph (see --graphs to get the full list, the default is full)"), QLatin1String("type"), QLatin1String("full"))); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the source color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("key"), i18n("specify the key of the destination color space"), QLatin1String("key"), QString())); parser.addOption(QCommandLineOption(QStringList() << QLatin1String("output"), i18n("specify the output (can be ps or dot, the default is ps)"), QLatin1String("type"), QLatin1String("ps"))); parser.addPositionalArgument(QLatin1String("outputfile"), i18n("name of the output file")); parser.process(app); // PORTING SCRIPT: move this to after any parser.addOption if (parser.isSet("graphs")) { // Don't change those lines to use dbgPigment derivatives, they need to be outputed // to stdout not stderr. std::cout << "full : show all the connection on the graph" << std::endl; std::cout << "bestpath : show the best path for a given transformation" << std::endl; exit(EXIT_SUCCESS); } QString graphType = parser.value("graph"); QString outputType = parser.value("output"); if (parser.positionalArguments().count() != 1) { errorPigment << "No output file name specified"; parser.showHelp(); exit(EXIT_FAILURE); } QString outputFileName = parser.positionalArguments()[0]; // Generate the graph QString dot; if (graphType == "full") { dot = FriendOfColorSpaceRegistry::toDot(); } else if (graphType == "bestpath") { QString srcKey = parser.value("src-key"); QString dstKey = parser.value("dst-key"); if (srcKey.isEmpty() || dstKey.isEmpty()) { errorPigment << "src-key and dst-key must be specified for the graph bestpath"; exit(EXIT_FAILURE); } else { dot = FriendOfColorSpaceRegistry::bestPathToDot(srcKey, dstKey); } } else { - errorPigment << "Unknow graph type : " << graphType.toLatin1(); + errorPigment << "Unknown graph type : " << graphType.toLatin1(); exit(EXIT_FAILURE); } if (outputType == "dot") { QFile file(outputFileName); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) exit(EXIT_FAILURE); QTextStream out(&file); out << dot; } else if (outputType == "ps" || outputType == "svg") { QTemporaryFile file; if (!file.open()) { exit(EXIT_FAILURE); } QTextStream out(&file); out << dot; QString cmd = QString("dot -T%1 %2 -o %3").arg(outputType).arg(file.fileName()).arg(outputFileName); file.close(); if (QProcess::execute(cmd) != 0) { errorPigment << "An error has occurred when executing : '" << cmd << "' the most likely cause is that 'dot' command is missing, and that you should install graphviz (from http://www.graphiz.org)"; } } else { - errorPigment << "Unknow output type : " << outputType; + errorPigment << "Unknown output type : " << outputType; exit(EXIT_FAILURE); } } diff --git a/libs/store/StoreDebug.cpp b/libs/store/StoreDebug.cpp index b0ca6330c3..336fe45f5f 100644 --- a/libs/store/StoreDebug.cpp +++ b/libs/store/StoreDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "StoreDebug.h" const QLoggingCategory &STORE_LOG() \ { - static const QLoggingCategory category("krita.lib.store"); + static const QLoggingCategory category("krita.lib.store", QtInfoMsg); return category; } diff --git a/libs/ui/KisAutoSaveRecoveryDialog.cpp b/libs/ui/KisAutoSaveRecoveryDialog.cpp index a1c3660c51..2769ecef88 100644 --- a/libs/ui/KisAutoSaveRecoveryDialog.cpp +++ b/libs/ui/KisAutoSaveRecoveryDialog.cpp @@ -1,279 +1,279 @@ /* This file is part of the KDE project Copyright (C) 2012 Boudewijn Rempt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisAutoSaveRecoveryDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct FileItem { FileItem() : checked(true) {} QImage thumbnail; QString name; QString date; bool checked; }; class FileItemDelegate : public KWidgetItemDelegate { public: FileItemDelegate(QAbstractItemView *itemView, KisAutoSaveRecoveryDialog *dlg) : KWidgetItemDelegate(itemView, dlg) , m_parent(dlg) { } QList createItemWidgets(const QModelIndex& index) const override { // a lump of coal and a piece of elastic will get you through the world QWidget *page = new QWidget; QHBoxLayout *layout = new QHBoxLayout(page); QCheckBox *checkBox = new QCheckBox; checkBox->setProperty("fileitem", index.data()); connect(checkBox, SIGNAL(toggled(bool)), m_parent, SLOT(toggleFileItem(bool))); QLabel *thumbnail = new QLabel; QLabel *filename = new QLabel; QLabel *dateModified = new QLabel; layout->addWidget(checkBox); layout->addWidget(thumbnail); layout->addWidget(filename); layout->addWidget(dateModified); page->setFixedSize(600, 200); return QList() << page; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { FileItem *fileItem = (FileItem*)index.data().value(); QWidget* page= widgets[0]; QHBoxLayout* layout = qobject_cast(page->layout()); QCheckBox *checkBox = qobject_cast(layout->itemAt(0)->widget()); QLabel *thumbnail = qobject_cast(layout->itemAt(1)->widget()); QLabel *filename = qobject_cast(layout->itemAt(2)->widget()); QLabel *modified = qobject_cast(layout->itemAt(3)->widget()); checkBox->setChecked(fileItem->checked); thumbnail->setPixmap(QPixmap::fromImage(fileItem->thumbnail)); filename->setText(fileItem->name); modified->setText(fileItem->date); - // move the page _up_ otherwise it will draw relative to the actual postion + // move the page _up_ otherwise it will draw relative to the actual position page->setGeometry(option.rect.translated(0, -option.rect.y())); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const override { //paint background for selected or hovered item QStyleOptionViewItem opt = option; itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, 0); } QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const override { return QSize(600, 200); } KisAutoSaveRecoveryDialog *m_parent; }; class KisAutoSaveRecoveryDialog::FileItemModel : public QAbstractListModel { public: FileItemModel(QList fileItems, QObject *parent) : QAbstractListModel(parent) , m_fileItems(fileItems){} ~FileItemModel() override { qDeleteAll(m_fileItems); m_fileItems.clear(); } int rowCount(const QModelIndex &/*parent*/) const override { return m_fileItems.size(); } Qt::ItemFlags flags(const QModelIndex& /*index*/) const override { Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; return flags; } QVariant data(const QModelIndex& index, int role) const override { if (index.isValid() && index.row() < m_fileItems.size()) { FileItem *item = m_fileItems.at(index.row()); switch (role) { case Qt::DisplayRole: { return QVariant::fromValue((void*)item); } case Qt::SizeHintRole: return QSize(600, 200); } } return QVariant(); } bool setData(const QModelIndex& index, const QVariant& /*value*/, int role) override { if (index.isValid() && index.row() < m_fileItems.size()) { if (role == Qt::CheckStateRole) { m_fileItems.at(index.row())->checked = !m_fileItems.at(index.row())->checked; return true; } } return false; } QList m_fileItems; }; KisAutoSaveRecoveryDialog::KisAutoSaveRecoveryDialog(const QStringList &filenames, QWidget *parent) : KoDialog(parent) { setCaption(i18nc("@title:window", "Recover Files")); setButtons( KoDialog::Ok | KoDialog::Cancel | KoDialog::User1 ); setButtonText(KoDialog::User1, i18n("Discard All")); setMinimumSize(650, 500); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); if (filenames.size() == 1) { layout->addWidget(new QLabel(i18n("The following autosave file can be recovered:"))); } else { layout->addWidget(new QLabel(i18n("The following autosave files can be recovered:"))); } m_listView = new QListView(); m_listView->setAcceptDrops(false); KWidgetItemDelegate *delegate = new FileItemDelegate(m_listView, this); m_listView->setItemDelegate(delegate); QList fileItems; Q_FOREACH (const QString &filename, filenames) { FileItem *file = new FileItem(); file->name = filename; #ifdef Q_OS_WIN QString path = QDir::tempPath() + "/" + filename; #else QString path = QDir::homePath() + "/" + filename; #endif // get thumbnail -- almost all Krita-supported formats save a thumbnail KoStore* store = KoStore::createStore(path, KoStore::Read); if (store) { if(store->open(QString("Thumbnails/thumbnail.png")) || store->open(QString("preview.png"))) { // Hooray! No long delay for the user... QByteArray bytes = store->read(store->size()); store->close(); QImage img; img.loadFromData(bytes); file->thumbnail = img.scaled(QSize(200,200), Qt::KeepAspectRatio, Qt::SmoothTransformation); } delete store; } // get the date QDateTime date = QFileInfo(path).lastModified(); file->date = "(" + date.toString(Qt::LocalDate) + ")"; fileItems.append(file); } m_model = new FileItemModel(fileItems, m_listView); m_listView->setModel(m_model); layout->addWidget(m_listView); layout->addWidget(new QLabel(i18n("If you select Cancel, all recoverable files will be kept.\nIf you press OK, selected files will be recovered, the unselected files discarded."))); setMainWidget(page); setAttribute(Qt::WA_DeleteOnClose, false); connect( this, SIGNAL( user1Clicked() ), this, SLOT( slotDeleteAll() ) ); } KisAutoSaveRecoveryDialog::~KisAutoSaveRecoveryDialog() { delete m_listView->itemDelegate(); delete m_model; delete m_listView; } void KisAutoSaveRecoveryDialog::slotDeleteAll() { foreach(FileItem* fileItem, m_model->m_fileItems) { fileItem->checked = false; } accept(); } QStringList KisAutoSaveRecoveryDialog::recoverableFiles() { QStringList files; Q_FOREACH (FileItem* fileItem, m_model->m_fileItems) { if (fileItem->checked) { files << fileItem->name; } } return files; } void KisAutoSaveRecoveryDialog::toggleFileItem(bool toggle) { // I've made better man from a piece of putty and matchstick! QVariant v = sender()->property("fileitem") ; if (v.isValid()) { FileItem *fileItem = (FileItem*)v.value(); fileItem->checked = toggle; } } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 50af901602..0d720e85f0 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,589 +1,591 @@ /* This file is part of the Krita project * * Copyright (C) 2014 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeBasedDocumentBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPaintingAssistant; class KisPart; class KisGridConfig; class KisGuidesConfig; class QDomDocument; class KisPart; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); +private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); +public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); private Q_SLOTS: void finishExportInBackground(); void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); /** @internal */ void setModified(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeBasedDocumentBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; QList assistants() const; void setAssistants(const QList value); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp index 27bb552a6e..aaba1f28c0 100644 --- a/libs/ui/KisImportExportFilter.cpp +++ b/libs/ui/KisImportExportFilter.cpp @@ -1,289 +1,289 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisImportExportFilter.h" #include #include #include #include "KisImportExportManager.h" #include #include #include #include #include "KoUpdater.h" #include #include "kis_config.h" const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency"; const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID"; const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID"; const QString KisImportExportFilter::sRGBTag = "sRGB"; class Q_DECL_HIDDEN KisImportExportFilter::Private { public: QPointer updater; QByteArray mime; QString filename; QString realFilename; bool batchmode; QMap capabilities; Private() : updater(0), mime("") , batchmode(false) {} ~Private() { qDeleteAll(capabilities); } }; KisImportExportFilter::KisImportExportFilter(QObject *parent) : QObject(parent) , d(new Private) { } KisImportExportFilter::~KisImportExportFilter() { if (d->updater) { d->updater->setProgress(100); } delete d; } QString KisImportExportFilter::filename() const { return d->filename; } QString KisImportExportFilter::realFilename() const { return d->realFilename; } bool KisImportExportFilter::batchMode() const { return d->batchmode; } void KisImportExportFilter::setBatchMode(bool batchmode) { d->batchmode = batchmode; } void KisImportExportFilter::setFilename(const QString &filename) { d->filename = filename; } void KisImportExportFilter::setRealFilename(const QString &filename) { d->realFilename = filename; } void KisImportExportFilter::setMimeType(const QString &mime) { d->mime = mime.toLatin1(); } QByteArray KisImportExportFilter::mimeType() const { return d->mime; } QString KisImportExportFilter::conversionStatusString(ConversionStatus status) { QString msg; switch (status) { case OK: break; case FilterCreationError: msg = i18n("Krita does not support this file format"); break; case CreationError: msg = i18n("Could not create the output document"); break; case FileNotFound: msg = i18n("File not found"); break; case StorageCreationError: msg = i18n("Cannot create storage"); break; case BadMimeType: msg = i18n("Bad MIME type"); break; case WrongFormat: msg = i18n("Format not recognized"); break; case NotImplemented: msg = i18n("Not implemented"); break; case ParsingError: msg = i18n("Parsing error"); break; case InvalidFormat: msg = i18n("Invalid file format"); break; case InternalError: case UsageError: msg = i18n("Internal error"); break; case ProgressCancelled: msg = i18n("Cancelled by user"); break; case BadConversionGraph: msg = i18n("Unknown file type"); break; case UnsupportedVersion: msg = i18n("Unsupported file version"); break; case UserCancelled: // intentionally we do not prompt the error message here break; default: msg = i18n("Unknown error"); break; } return msg; } KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); const QString filterConfig = KisConfig().exportConfiguration(to); - if (!filterConfig.isEmpty()) { + if (cfg && !filterConfig.isEmpty()) { cfg->fromXML(filterConfig, false); } return cfg; } KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } QMap KisImportExportFilter::exportChecks() { qDeleteAll(d->capabilities); initializeCapabilities(); return d->capabilities; } void KisImportExportFilter::setUpdater(QPointer updater) { d->updater = updater; } void KisImportExportFilter::setProgress(int value) { if (d->updater) { d->updater->setValue(value); } } void KisImportExportFilter::initializeCapabilities() { // XXX: Initialize everything to fully supported? } void KisImportExportFilter::addCapability(KisExportCheckBase *capability) { d->capabilities[capability->id()] = capability; } void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level) { Q_ASSERT(level != KisExportCheckBase::SUPPORTED); QString layerMessage; QString imageMessage; QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorModelID, allColorModels) { QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorDepthID, allColorDepths) { KisExportCheckFactory *colorModelCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id()); KisExportCheckFactory *colorModelPerLayerCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id()); if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) { qDebug() << "No factory for" << colorModelID << colorDepthID; continue; } if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) { addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED)); addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED)); } else { if (level == KisExportCheckBase::PARTIALLY) { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will be converted." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be converted or skipped." ,name, colorModelID.name(), colorDepthID.name()); } else { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will not be saved." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be skipped." , name, colorModelID.name(), colorDepthID.name()); } addCapability(colorModelCheckFactory->create(level, imageMessage)); addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage)); } } } } diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h index 8cd2dac4fa..879a7c194f 100644 --- a/libs/ui/KisImportExportFilter.h +++ b/libs/ui/KisImportExportFilter.h @@ -1,183 +1,183 @@ /* This file is part of the Calligra libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; 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 #include #include #include #include #include #include #include #include #include #include #include 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 * @todo the class has no constructor and therefore cannot initialize its private class */ class KRITAUI_EXPORT KisImportExportFilter : public QObject { Q_OBJECT public: static const QString ImageContainsTransparencyTag; static const QString ColorModelIDTag; static const QString ColorDepthIDTag; static const QString sRGBTag; 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 }; ~KisImportExportFilter() override; void setBatchMode(bool batchmode); void setFilename(const QString &filename); void setRealFilename(const QString &filename); void setMimeType(const QString &mime); void setUpdater(QPointer 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; + 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 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 > 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/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 379ab89fce..c021fc5740 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2504 +1,2504 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see 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 // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KIO #include #endif #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #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 #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 "dialogs/kis_dlg_send_telemetry.h" #include "kis_telemetry_instance.h" #include "kis_telemetry_abstract.h" #include "kis_telemetry_instance.h" #include #ifdef Q_OS_WIN #include #endif class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); dockWidget->setTabEnabled(false); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent) : q(parent) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; bool noCleanup {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *sendInfo{0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QString lastExportLocation; QMap dockWidgetsMap; QMap 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 > tabSwitchCompressor; QMutex savingEntryMutex; QScopedPointer telemetry; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow() : KXmlGuiWindow() , d(new Private(this)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(false); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); KisConfig cfg; d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); setAcceptDrops(true); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_OSX setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap 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 >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >))); 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(observer); if (mainwindowObserver) { mainwindowObserver->setMainWindow(d->viewManager); } } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); setCentralWidget(d->mdiArea); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); setAutoSaveSettings("krita", false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet 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"); + setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); + setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(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 callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } //check fatal asserts try { KisTelemetryInstance::instance()->sendData("asserts"); } catch (std::exception e){ Q_UNUSED(e); } } void KisMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // dbgKrita << "", "").replace("", "") // << "iconText=" << action->iconText().replace("&", "&") // << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString() // << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString() // << "isCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "statusTip=" << action->statusTip() // << "/>" ; // } // else { // dbgKrita << "Got a QAction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); if (d->noCleanup) return; delete d->viewManager; delete d; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg; subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("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 koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(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(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::sendInfo() { qDebug()<<"send_enfo"<<"\n"; d->telemetry.reset(new KisDlgSendTelemtry(this->viewManager())); if(d->telemetry->exec()== QDialog::Accepted){ KisTelemetryInstance::instance()->sendData("install"); KisTelemetryInstance::instance()->sendData("tools"); KisTelemetryInstance::instance()->sendData("imageProperties"); KisTelemetryInstance::instance()->sendData("actions"); }; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { dbgUI << "KisMainWindow::addRecentURL url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) if (path.contains(*it)) ok = false; // it's in the tmp resource #ifdef HAVE_KIO if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); } #endif } #ifdef HAVE_KIO else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } #endif if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KMainWindow* window, KMainWindow::memberList()) { /** * FIXME: this is a hacking approach of reloading the updated recent files list. * Sometimes, the result of reading from KConfig right after doing 'sync()' still * returns old values of the recent files. Reading the same files a bit later * returns correct "updated" files. I couldn't find the cause of it (DK). */ KisMainWindow *mw = static_cast(window); if (mw != this) { mw->reloadRecentFileList(); } } } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries( KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } if (doc->isRecovered()) { caption += ' ' + i18n("[RECOVERED]"); } caption += "[*]"; d->activeView->setWindowTitle(caption); d->activeView->setWindowModified(doc->isModified()); updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KisMainWindow::updateCaption(const QString & caption, bool mod) { dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")"; #ifdef KRITA_ALPHA setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod); return; #endif #ifdef KRITA_BETA setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod); return; #endif #ifdef KRITA_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!flags && BatchMode) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::mimeFilter(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } bool reset_url; if (document->url().isEmpty()) { reset_url = true; saveas = true; } else { reset_url = false; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &))); QUrl oldURL = document->url(); QString oldFile = document->localFilePath(); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first().remove("*."); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QDesktopServices::storageLocation(QDesktopServices::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile()); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; document->setUrl(oldURL); document->setLocalFilePath(oldFile); } } if (ret && !isExporting) { document->setRecovered(false); } if (!ret && reset_url) document->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->undoAction()->trigger(); d->undo->setText(activeView()->undoAction()->text()); } } void KisMainWindow::redo() { if (activeView()) { activeView()->redoAction()->trigger(); d->redo->setText(activeView()->redoAction()->text()); } } void KisMainWindow::closeEvent(QCloseEvent *e) { d->mdiArea->closeAllSubWindows(); QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); { KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); } QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if (d->noCleanup) return; if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency Q_FOREACH (QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); actionCollection()->action("edit_undo")->setText(activeView()->undoAction()->text()); actionCollection()->action("edit_redo")->setText(activeView()->redoAction()->text()); d->viewManager->setCurrentView(view); } void KisMainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls() || event->mimeData()->hasFormat("application/x-krita-node") || event->mimeData()->hasFormat("application/x-qt-image")) { event->accept(); } } void KisMainWindow::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) { Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { openDocument(url, None); } } } void KisMainWindow::dragMoveEvent(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/) { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::mimeFilter(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); KisConfig cfg; int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } KoCanvasResourceManager *KisMainWindow::resourceManager() const { return d->viewManager->resourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } bool KisMainWindow::restoreWorkspace(const QByteArray &state) { QByteArray oldState = saveState(); const bool showTitlebars = KisConfig().showDockerTitleBars(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->hide(); dock->titleBarWidget()->setVisible(showTitlebars); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(showTitlebars || dock->isFloating()); } } return false; } Q_FOREACH (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { const bool isCollapsed = (dock->widget() && dock->widget()->isHidden()) || !dock->widget(); dock->titleBarWidget()->setVisible(showTitlebars || (dock->isFloating() && isCollapsed)); } } return success; } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { qDebug()<<"~~KisPart sendTelemetry()"; KisTelemetryInstance::instance()->sendData("install"); KisTelemetryInstance::instance()->sendData("tools"); KisTelemetryInstance::instance()->sendData("imageProperties"); KisTelemetryInstance::instance()->sendData("actions"); 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 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 layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { QString msg = KisImportExportFilter::conversionStatusString(status); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(KSharedConfig::openConfig()->group("krita")); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { KConfigGroup group = KSharedConfig::openConfig()->group("krita"); saveMainWindowSettings(group); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg; cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } KoDockWidgetTitleBar *titleBar = dynamic_cast(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(group.readEntry("DockArea", static_cast(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 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 KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(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(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::setToolbarList(QList toolbarList) { qDeleteAll(d->toolbarList); d->toolbarList = toolbarList; } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = doc->url().toDisplayString(); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer(false)->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); auto ds = w->dockerState(); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(ds); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->resourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(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(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(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("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QBrush brush(cfg.getMDIBackgroundColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisPart::instance()->createMainWindow()->show(); } void KisMainWindow::closeCurrentWindow() { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointerKisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_OSX w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); KisConfig cfg; actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo ->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); d->toggleDockers = actionManager->createAction("view_toggledockers"); cfg.showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDockerTitleBars = actionManager->createAction("view_toggledockertitlebars"); d->toggleDockerTitleBars->setChecked(cfg.showDockerTitleBars()); connect(d->toggleDockerTitleBars, SIGNAL(toggled(bool)), SLOT(showDockerTitleBars(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createAction("file_close"); connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow())); d->sendInfo = actionManager->createAction("send_info"); connect(d->sendInfo, SIGNAL(triggered()), SLOT(sendInfo())); 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 diff --git a/libs/ui/canvas/kis_display_filter.h b/libs/ui/canvas/kis_display_filter.h index 104ec1adca..a2ae5ab584 100644 --- a/libs/ui/canvas/kis_display_filter.h +++ b/libs/ui/canvas/kis_display_filter.h @@ -1,51 +1,54 @@ /* * Copyright (c) 2012 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DISPLAY_FILTER_H #define KIS_DISPLAY_FILTER_H #include #include #include struct KisExposureGammaCorrectionInterface; /** * @brief The KisDisplayFilter class is the base class for filters that * are applied by the canvas to the projection before displaying. */ class KRITAUI_EXPORT KisDisplayFilter : public QObject { Q_OBJECT public: explicit KisDisplayFilter(QObject *parent = 0); virtual QString program() const = 0; virtual GLuint lutTexture() const = 0; virtual void filter(quint8 *pixels, quint32 numPixels) = 0; virtual void approximateInverseTransformation(quint8 *pixels, quint32 numPixels) = 0; virtual void approximateForwardTransformation(quint8 *pixels, quint32 numPixels) = 0; virtual bool useInternalColorManagement() const = 0; virtual KisExposureGammaCorrectionInterface *correctionInterface() const = 0; virtual bool lockCurrentColorVisualRepresentation() const = 0; - virtual void updateShader() = 0; + /** + * @return true if the shader should be recompiled + */ + virtual bool updateShader() = 0; }; #endif diff --git a/libs/ui/dialogs/kis_dlg_filter.cpp b/libs/ui/dialogs/kis_dlg_filter.cpp index 17284fa533..26c790d1e4 100644 --- a/libs/ui/dialogs/kis_dlg_filter.cpp +++ b/libs/ui/dialogs/kis_dlg_filter.cpp @@ -1,239 +1,240 @@ /* * Copyright (c) 2007 Cyrille Berger * Copyright (c) 2008 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_dlg_filter.h" #include #include #include #include #include #include #include #include #include #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())); + connect(d->uiFilterDialog.pushButtonCreateMaskEffect,SIGNAL(pressed()),SLOT(close())); d->uiFilterDialog.filterGalleryToggle->setChecked(d->uiFilterDialog.filterSelection->isFilterGalleryVisible()); d->uiFilterDialog.filterGalleryToggle->setIcon(QPixmap(":/pics/sidebaricon.png")); d->uiFilterDialog.filterGalleryToggle->setMaximumWidth(d->uiFilterDialog.filterGalleryToggle->height()); connect(d->uiFilterDialog.filterSelection, SIGNAL(sigFilterGalleryToggled(bool)), d->uiFilterDialog.filterGalleryToggle, SLOT(setChecked(bool))); connect(d->uiFilterDialog.filterGalleryToggle, SIGNAL(toggled(bool)), d->uiFilterDialog.filterSelection, SLOT(showFilterGallery(bool))); connect(d->uiFilterDialog.filterSelection, SIGNAL(sigSizeChanged()), this, SLOT(slotFilterWidgetSizeChanged())); if (node->inherits("KisMask")) { d->uiFilterDialog.pushButtonCreateMaskEffect->setVisible(false); } d->uiFilterDialog.filterSelection->setPaintDevice(true, d->node->original()); connect(d->uiFilterDialog.buttonBox, SIGNAL(accepted()), SLOT(accept())); connect(d->uiFilterDialog.buttonBox, SIGNAL(rejected()), SLOT(reject())); connect(d->uiFilterDialog.checkBoxPreview, SIGNAL(toggled(bool)), SLOT(enablePreviewToggled(bool))); connect(d->uiFilterDialog.filterSelection, SIGNAL(configurationChanged()), SLOT(filterSelectionChanged())); connect(this, SIGNAL(accepted()), SLOT(slotOnAccept())); connect(this, SIGNAL(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(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(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/input/kis_input_manager.cpp b/libs/ui/input/kis_input_manager.cpp index 8b16c87bd9..9c172918ce 100644 --- a/libs/ui/input/kis_input_manager.cpp +++ b/libs/ui/input/kis_input_manager.cpp @@ -1,611 +1,612 @@ /* This file is part of the KDE project * * Copyright (C) 2012 Arjen Hiemstra * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_input_manager.h" #include #include #include #include #include #include #include #include "kis_tool_proxy.h" #include #include #include #include #include #include #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 #include #include "kis_extended_modifiers_mapper.h" #include "kis_input_manager_p.h" template uint qHash(QPointer value) { return reinterpret_cast(value.data()); } KisInputManager::KisInputManager(QObject *parent) : QObject(parent), d(new Private(this)) { d->setupActions(); connect(KoToolManager::instance(), SIGNAL(aboutToChangeTool(KoCanvasController*)), SLOT(slotAboutToChangeTool())); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), SLOT(slotToolChanged())); connect(&d->moveEventCompressor, SIGNAL(timeout()), SLOT(slotCompressedMoveEvent())); QApplication::instance()-> installEventFilter(new Private::ProximityNotifier(d, this)); } KisInputManager::~KisInputManager() { delete d; } void KisInputManager::addTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.addCanvas(canvas); } void KisInputManager::removeTrackedCanvas(KisCanvas2 *canvas) { d->canvasSwitcher.removeCanvas(canvas); } void KisInputManager::toggleTabletLogger() { KisTabletDebugger::instance()->toggleDebugging(); } void KisInputManager::attachPriorityEventFilter(QObject *filter, int priority) { Private::PriorityList::iterator begin = d->priorityEventFilter.begin(); Private::PriorityList::iterator it = begin; Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(begin, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) return; it = std::find_if(begin, end, [priority] (const Private::PriorityPair &a) { return a.first > priority; }); d->priorityEventFilter.insert(it, qMakePair(priority, filter)); d->priorityEventFilterSeqNo++; } void KisInputManager::detachPriorityEventFilter(QObject *filter) { Private::PriorityList::iterator it = d->priorityEventFilter.begin(); Private::PriorityList::iterator end = d->priorityEventFilter.end(); it = std::find_if(it, end, [filter] (const Private::PriorityPair &a) { return a.second == filter; }); if (it != end) { d->priorityEventFilter.erase(it); } } void KisInputManager::setupAsEventFilter(QObject *receiver) { if (d->eventsReceiver) { d->eventsReceiver->removeEventFilter(this); } d->eventsReceiver = receiver; if (d->eventsReceiver) { d->eventsReceiver->installEventFilter(this); } } void KisInputManager::stopIgnoringEvents() { d->allowMouseEvents(); } #if defined (__clang__) #pragma GCC diagnostic ignored "-Wswitch" #endif bool KisInputManager::eventFilter(QObject* object, QEvent* event) { if (object != d->eventsReceiver) return false; if (d->eventEater.eventFilter(object, event)) return false; if (!d->matcher.hasRunningShortcut()) { int savedPriorityEventFilterSeqNo = d->priorityEventFilterSeqNo; for (auto it = d->priorityEventFilter.begin(); it != d->priorityEventFilter.end(); /*noop*/) { const QPointer &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 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::value || std::is_same::value, "event should be a mouse or a tablet event"); bool retval = false; /** * Compress the events if the tool doesn't need high resolution input */ if ((event->type() == QEvent::MouseMove || event->type() == QEvent::TabletMove) && (!d->matcher.supportsHiResInputEvents() || d->testingCompressBrushEvents)) { d->compressedMoveEvent.reset(new Event(*event)); d->moveEventCompressor.start(); /** * On Linux Qt eats the rest of unneeded events if we * ignore the first of the chunk of tablet events. So * generally we should never activate this feature. Only * for testing purposes! */ if (d->testingAcceptCompressedTabletEvents) { event->setAccepted(true); } retval = true; } else { slotCompressedMoveEvent(); retval = d->handleCompressedTabletEvent(event); } return retval; } bool KisInputManager::eventFilterImpl(QEvent * event) { bool retval = false; if (event->type() != QEvent::Wheel) { d->accumulatedScrollDelta = 0; } switch (event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: { d->debugEvent(event); //Block mouse press events on Genius tablets if (d->tabletActive) break; if (d->ignoringQtCursorEvents()) break; if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(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(event); if (d->ignoringQtCursorEvents()) break; if (d->touchHasBlockedPressEvents) break; QMouseEvent *mouseEvent = static_cast(event); retval = d->matcher.buttonReleased(mouseEvent->button(), mouseEvent); event->setAccepted(retval); break; } case QEvent::ShortcutOverride: { d->debugEvent(event); QKeyEvent *keyEvent = static_cast(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(event); QKeyEvent *keyEvent = static_cast(event); if (!keyEvent->isAutoRepeat()) { Qt::Key key = KisExtendedModifiersMapper::workaroundShiftAltMetaHell(keyEvent); retval = d->matcher.keyReleased(key); } break; } case QEvent::MouseMove: { d->debugEvent(event); if (d->ignoringQtCursorEvents()) break; QMouseEvent *mouseEvent = static_cast(event); retval = compressMoveEventCommon(mouseEvent); break; } case QEvent::Wheel: { d->debugEvent(event); QWheelEvent *wheelEvent = static_cast(event); d->accumulatedScrollDelta += wheelEvent->delta(); KisSingleActionShortcut::WheelAction action; /** * Ignore delta 0 events on OSX, since they are triggered by tablet * proximity when using Wacom devices. */ #ifdef Q_OS_OSX if(wheelEvent->delta() == 0) { retval = true; break; } #endif if (wheelEvent->orientation() == Qt::Horizontal) { if(wheelEvent->delta() < 0) { action = KisSingleActionShortcut::WheelRight; } else { action = KisSingleActionShortcut::WheelLeft; } } else { if(wheelEvent->delta() > 0) { action = KisSingleActionShortcut::WheelUp; } else { action = KisSingleActionShortcut::WheelDown; } } if (qAbs(d->accumulatedScrollDelta) >= QWheelEvent::DefaultDeltasPerStep) { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.wheelEvent(action, wheelEvent); d->accumulatedScrollDelta = 0; } else { retval = true; } break; } case QEvent::Enter: d->debugEvent(event); d->containsPointer = true; //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.enterEvent(); break; case QEvent::Leave: d->debugEvent(event); d->containsPointer = false; /** * We won't get a TabletProximityLeave event when the tablet * is hovering above some other widget, so restore cursor * events processing right now. */ d->allowMouseEvents(); d->touchHasBlockedPressEvents = false; d->matcher.leaveEvent(); break; case QEvent::FocusIn: d->debugEvent(event); KisAbstractInputAction::setInputManager(this); //Clear all state so we don't have half-matched shortcuts dangling around. d->matcher.reinitialize(); { // Emulate pressing of the key that are already pressed KisExtendedModifiersMapper mapper; Qt::KeyboardModifiers modifiers = mapper.queryStandardModifiers(); Q_FOREACH (Qt::Key key, mapper.queryExtendedModifiers()) { QKeyEvent kevent(QEvent::ShortcutOverride, key, modifiers); eventFilterImpl(&kevent); } } d->allowMouseEvents(); break; case QEvent::FocusOut: { d->debugEvent(event); KisAbstractInputAction::setInputManager(this); QPointF currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); d->matcher.lostFocusEvent(currentLocalPos); break; } case QEvent::TabletPress: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); if (d->tryHidePopupPalette()) { retval = true; } else { //Make sure the input actions know we are active. KisAbstractInputAction::setInputManager(this); retval = d->matcher.buttonPressed(tabletEvent->button(), tabletEvent); } event->setAccepted(true); retval = true; d->blockMouseEvents(); //Reset signal compressor to prevent processing events before press late d->resetCompressor(); d->eatOneMousePress(); break; } case QEvent::TabletMove: { d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(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) */ d->blockMouseEvents(); break; } case QEvent::TabletRelease: { #ifdef Q_OS_MAC d->allowMouseEvents(); #endif if (d->touchHasBlockedPressEvents) break; d->debugEvent(event); QTabletEvent *tabletEvent = static_cast(event); retval = d->matcher.buttonReleased(tabletEvent->button(), tabletEvent); retval = true; event->setAccepted(true); break; } case QEvent::TouchBegin: { QTouchEvent *tevent = static_cast(event); d->touchHasBlockedPressEvents = KisConfig().disableTouchOnCanvas(); // Touch rejection: if touch is disabled on canvas, no need to block mouse press events if (KisConfig().disableTouchOnCanvas()) d->eatOneMousePress(); if (d->tryHidePopupPalette()) { retval = true; } else { KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchBeginEvent(tevent); event->accept(); } break; } case QEvent::TouchUpdate: { QTouchEvent *tevent = static_cast(event); #ifdef Q_OS_MAC int count = 0; Q_FOREACH (const QTouchEvent::TouchPoint &point, tevent->touchPoints()) { if (point.state() != Qt::TouchPointReleased) { count++; } } if (count < 2 && tevent->touchPoints().length() > count) { d->touchHasBlockedPressEvents = false; retval = d->matcher.touchEndEvent(tevent); } else { #endif d->touchHasBlockedPressEvents = KisConfig().disableTouchOnCanvas(); KisAbstractInputAction::setInputManager(this); retval = d->matcher.touchUpdateEvent(tevent); #ifdef Q_OS_OSX } #endif event->accept(); break; } case QEvent::TouchEnd: { QTouchEvent *tevent = static_cast(event); d->touchHasBlockedPressEvents = false; retval = d->matcher.touchEndEvent(tevent); event->accept(); break; } default: break; } return !retval ? d->processUnhandledEvent(event) : true; } void KisInputManager::slotCompressedMoveEvent() { if (d->compressedMoveEvent) { // d->touchHasBlockedPressEvents = false; (void) d->handleCompressedTabletEvent(d->compressedMoveEvent.data()); d->compressedMoveEvent.reset(); dbgKrita << "Compressed move event received."; } else { dbgKrita << "Unexpected empty move event"; } } KisCanvas2* KisInputManager::canvas() const { return d->canvas; } KisToolProxy* KisInputManager::toolProxy() const { return d->toolProxy; } void KisInputManager::slotAboutToChangeTool() { - QPointF currentLocalPos = - canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); - + QPointF currentLocalPos; + if (canvas() && canvas()->canvasWidget()) { + currentLocalPos = canvas()->canvasWidget()->mapFromGlobal(QCursor::pos()); + } d->matcher.lostFocusEvent(currentLocalPos); } void KisInputManager::slotToolChanged() { KoToolManager *toolManager = KoToolManager::instance(); KoToolBase *tool = toolManager->toolById(canvas(), toolManager->activeToolId()); - d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); - if (tool && tool->isInTextMode()) { - d->forwardAllEventsToTool = true; - d->matcher.suppressAllActions(true); - } else { - d->forwardAllEventsToTool = false; - d->matcher.suppressAllActions(false); + if (tool) { + d->setMaskSyntheticEvents(tool->maskSyntheticEvents()); + if (tool->isInTextMode()) { + d->forwardAllEventsToTool = true; + d->matcher.suppressAllActions(true); + } else { + d->forwardAllEventsToTool = false; + d->matcher.suppressAllActions(false); + } } - - } void KisInputManager::profileChanged() { d->matcher.clearShortcuts(); KisInputProfile *profile = KisInputProfileManager::instance()->currentProfile(); if (profile) { const QList 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/kis_painting_assistant.cc b/libs/ui/kis_painting_assistant.cc index 37dfac43d0..7617852d40 100644 --- a/libs/ui/kis_painting_assistant.cc +++ b/libs/ui/kis_painting_assistant.cc @@ -1,693 +1,682 @@ /* * Copyright (c) 2008,2011 Cyrille Berger * Copyright (c) 2010 Geoffry Song * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "kis_painting_assistant.h" #include "kis_coordinates_converter.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance) struct KisPaintingAssistantHandle::Private { QList assistants; char handle_type; }; KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs) : QPointF(rhs) , KisShared() , d(new Private) { } KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt) { setX(pt.x()); setY(pt.y()); return *this; } void KisPaintingAssistantHandle::setType(char type) { d->handle_type = type; } char KisPaintingAssistantHandle::handleType() { return d->handle_type; } KisPaintingAssistantHandle::~KisPaintingAssistantHandle() { Q_ASSERT(d->assistants.empty()); delete d; } void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant) { Q_ASSERT(!d->assistants.contains(assistant)); d->assistants.append(assistant); } void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant) { d->assistants.removeOne(assistant); Q_ASSERT(!d->assistants.contains(assistant)); } bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) { return d->assistants.contains(assistant); } void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle) { if(this->handleType()=='S' || handle.data()->handleType()== 'S') return; Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) { if (!assistant->handles().contains(this)) { assistant->replaceHandle(handle, this); } } } -QList KisPaintingAssistantHandle::split() -{ - QList newHandles; - Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { - KisPaintingAssistantHandleSP newHandle(new KisPaintingAssistantHandle(*this)); - newHandles.append(newHandle); - assistant->replaceHandle(this, newHandle); - } - return newHandles; -} - void KisPaintingAssistantHandle::uncache() { Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { assistant->uncache(); } } struct KisPaintingAssistant::Private { QString id; QString name; bool snapping; bool outlineVisible; QList handles,sideHandles; QPixmapCache::Key cached; QRect cachedRect; // relative to boundingRect().topLeft() KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle; struct TranslationInvariantTransform { qreal m11, m12, m21, m22; TranslationInvariantTransform() { } TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { } bool operator==(const TranslationInvariantTransform& b) { return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22; } } cachedTransform; }; KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) { d->id = id; d->name = name; d->snapping=true; d->outlineVisible=true; } bool KisPaintingAssistant::snapping() const { return d->snapping; } void KisPaintingAssistant::setSnapping(bool set) { d->snapping=set; } bool KisPaintingAssistant::outline() const { return d->outlineVisible; } void KisPaintingAssistant::setOutline(bool set) { d->outlineVisible=set; } void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool drawActive) { int alpha=100; if (!drawActive) { alpha=20; } painter.save(); QPen pen_a(QColor(0, 0, 0, alpha), 2); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); QPen pen_b(QColor(255, 255, 255, alpha), 0.9); pen_b.setCosmetic(true); painter.setPen(pen_b); painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path) { painter.save(); QPen pen_a(QColor(0, 0, 0, 50), 1); pen_a.setStyle(Qt::SolidLine); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); QPen pen_b(QColor(255, 255, 255, 50), 1); pen_b.setStyle(Qt::DotLine); pen_b.setCosmetic(true); painter.setPen(pen_b); painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::initHandles(QList _handles) { Q_ASSERT(d->handles.isEmpty()); d->handles = _handles; Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) { handle->registerAssistant(this); } } KisPaintingAssistant::~KisPaintingAssistant() { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) { handle->unregisterAssistant(this); } if(!d->sideHandles.isEmpty()) { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { handle->unregisterAssistant(this); } } delete d; } const QString& KisPaintingAssistant::id() const { return d->id; } const QString& KisPaintingAssistant::name() const { return d->name; } void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with) { Q_ASSERT(d->handles.contains(_handle)); d->handles.replace(d->handles.indexOf(_handle), _with); Q_ASSERT(!d->handles.contains(_handle)); _handle->unregisterAssistant(this); _with->registerAssistant(this); } void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle) { Q_ASSERT(!d->handles.contains(handle)); d->handles.append(handle); handle->registerAssistant(this); handle.data()->setType('H'); } void KisPaintingAssistant::addSideHandle(KisPaintingAssistantHandleSP handle) { Q_ASSERT(!d->sideHandles.contains(handle)); d->sideHandles.append(handle); handle->registerAssistant(this); handle.data()->setType('S'); } void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache,KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { Q_UNUSED(updateRect); Q_UNUSED(canvas); Q_UNUSED(previewVisible); findHandleLocation(); if (!useCache) { gc.save(); drawCache(gc, converter, assistantVisible); gc.restore(); return; } const QRect bound = boundingRect(); if (bound.isEmpty()) return; const QTransform transform = converter->documentToWidgetTransform(); const QRect widgetBound = transform.mapRect(bound); const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport()); if (paintRect.isEmpty()) return; QPixmap cached; bool found = QPixmapCache::find(d->cached, &cached); if (!(found && d->cachedTransform == transform && d->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound); Q_ASSERT(!cacheRect.isEmpty()); if (cached.isNull() || cached.size() != cacheRect.size()) { cached = QPixmap(cacheRect.size()); } cached.fill(Qt::transparent); QPainter painter(&cached); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(cacheRect); drawCache(painter, converter, assistantVisible); painter.end(); d->cachedTransform = transform; d->cachedRect = cacheRect.translated(-widgetBound.topLeft()); d->cached = QPixmapCache::insert(cached); } gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->cachedRect.topLeft())); } void KisPaintingAssistant::uncache() { d->cached = QPixmapCache::Key(); } QRect KisPaintingAssistant::boundingRect() const { QRectF r; Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) { r = r.united(QRectF(*h, QSizeF(1,1))); } return r.adjusted(-2, -2, 2, 2).toAlignedRect(); } QByteArray KisPaintingAssistant::saveXml(QMap &handleMap) { QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("assistant"); xml.writeAttribute("type",d->id); xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { int id = handleMap.size(); if (!handleMap.contains(handle)){ handleMap.insert(handle, id); } id = handleMap.value(handle); xml.writeStartElement("handle"); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); return data; } void KisPaintingAssistant::loadXml(KoStore* store, QMap &handleMap, QString path) { int id; double x,y ; store->open(path); QByteArray data = store->read(store->size()); QXmlStreamReader xml(data); while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { id = strId.toInt(); x = strX.toDouble(); y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } } addHandle(handleMap.value(id)); } break; default: break; } } store->close(); } void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count) { if (d->id == "ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ellipse"); assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "spline"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "spline"); assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "perspective"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "perspective"); assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "vanishing point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "vanishing point"); assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "infinite ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "infinite ruler"); assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "parallel ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "parallel ruler"); assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "concentric ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "concentric ellipse"); assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "fisheye-point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "fisheye-point"); assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->id == "ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ruler"); assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } } void KisPaintingAssistant::findHandleLocation() { QList hHandlesList; QList vHandlesList; uint vHole = 0,hHole = 0; KisPaintingAssistantHandleSP oppHandle; if (d->handles.size() == 4 && d->id == "perspective") { //get the handle opposite to the first handle oppHandle = oppHandleOne(); //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively. Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) { hHandlesList.append(handle); hHole = hHandlesList.size() - 1; vHandlesList.append(handle); vHole = vHandlesList.size() - 1; /* sort handles on the basis of X-coordinate */ while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) { hHandlesList.swap(hHole-1, hHole); hHole = hHole - 1; } /* sort handles on the basis of Y-coordinate */ while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) { vHandlesList.swap(vHole-1, vHole); vHole = vHole - 1; } } /* give the handles their respective positions */ if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) { d->topLeft = vHandlesList.at(1); d->topRight= vHandlesList.at(0); } else { d->topLeft = vHandlesList.at(0); d->topRight = vHandlesList.at(1); } if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) { d->bottomLeft = vHandlesList.at(3); d->bottomRight = vHandlesList.at(2); } else { d->bottomLeft= vHandlesList.at(2); d->bottomRight = vHandlesList.at(3); } /* find if the handles that should be opposite are actually oppositely positioned */ if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) || (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) {} else { if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) { d->topLeft = hHandlesList.at(1); d->bottomLeft= hHandlesList.at(0); } else { d->topLeft = hHandlesList.at(0); d->bottomLeft = hHandlesList.at(1); } if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) { d->topRight = hHandlesList.at(3); d->bottomRight = hHandlesList.at(2); } else { d->topRight= hHandlesList.at(2); d->bottomRight = hHandlesList.at(3); } } /* Setting the middle handles as needed */ if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) { d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5); d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); addSideHandle(d->rightMiddle.data()); addSideHandle(d->leftMiddle.data()); addSideHandle(d->bottomMiddle.data()); addSideHandle(d->topMiddle.data()); } else { d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); } } } KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne() { QPointF intersection(0,0); if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) { return d->handles.at(1); } else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) { return d->handles.at(2); } else { return d->handles.at(3); } } KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() { return d->topLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const { return d->topLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() { return d->bottomLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const { return d->bottomLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() { return d->topRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const { return d->topRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() { return d->bottomRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const { return d->bottomRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() { return d->topMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const { return d->topMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() { return d->bottomMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const { return d->bottomMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() { return d->rightMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const { return d->rightMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() { return d->leftMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const { return d->leftMiddle; } const QList& KisPaintingAssistant::handles() const { return d->handles; } QList KisPaintingAssistant::handles() { return d->handles; } const QList& KisPaintingAssistant::sideHandles() const { return d->sideHandles; } QList KisPaintingAssistant::sideHandles() { return d->sideHandles; } KisPaintingAssistantFactory::KisPaintingAssistantFactory() { } KisPaintingAssistantFactory::~KisPaintingAssistantFactory() { } KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry() { } KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry() { Q_FOREACH (const QString &id, keys()) { delete get(id); } dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry "; } KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance() { return s_instance; } diff --git a/libs/ui/kis_painting_assistant.h b/libs/ui/kis_painting_assistant.h index 8e8765a148..56b23f2aa2 100644 --- a/libs/ui/kis_painting_assistant.h +++ b/libs/ui/kis_painting_assistant.h @@ -1,175 +1,174 @@ /* * Copyright (c) 2008 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PAINTING_ASSISTANT_H_ #define _KIS_PAINTING_ASSISTANT_H_ #include #include #include #include #include #include #include class QPainter; class QRect; class QRectF; class KoStore; class KisCoordinatesConverter; class KisCanvas2; class QDomDocument; class QDomElement; #include #include class KisPaintingAssistantHandle; typedef KisSharedPtr KisPaintingAssistantHandleSP; class KisPaintingAssistant; class QPainterPath; /** * Represent an handle of the assistant, used to edit the parameters * of an assistants. Handles can be shared between assistants. */ class KRITAUI_EXPORT KisPaintingAssistantHandle : public QPointF, public KisShared { friend class KisPaintingAssistant; public: KisPaintingAssistantHandle(double x, double y); explicit KisPaintingAssistantHandle(QPointF p); KisPaintingAssistantHandle(const KisPaintingAssistantHandle&); ~KisPaintingAssistantHandle(); void mergeWith(KisPaintingAssistantHandleSP); - QList split(); void uncache(); KisPaintingAssistantHandle& operator=(const QPointF&); void setType(char type); char handleType(); private: void registerAssistant(KisPaintingAssistant*); void unregisterAssistant(KisPaintingAssistant*); bool containsAssistant(KisPaintingAssistant*); private: struct Private; Private* const d; }; /** * A KisPaintingAssistant is an object that assist the drawing on the canvas. * With this class you can implement virtual equivalent to ruler or compas. */ class KRITAUI_EXPORT KisPaintingAssistant { public: KisPaintingAssistant(const QString& id, const QString& name); virtual ~KisPaintingAssistant(); const QString& id() const; const QString& name() const; bool snapping() const;//this returns whether or not the snapping is/should be active. void setSnapping(bool set); bool outline() const;//this returns whether or not the preview is/should be active. void setOutline(bool set); /** * Adjust the position given in parameter. * @param point the coordinates in point in the document reference * @param strokeBegin the coordinates of the beginning of the stroke */ virtual QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) = 0; virtual void endStroke() { } virtual QPointF buttonPosition() const = 0; virtual int numHandles() const = 0; void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with); void addHandle(KisPaintingAssistantHandleSP handle); void addSideHandle(KisPaintingAssistantHandleSP handle); virtual void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, bool cached = true,KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true); void uncache(); const QList& handles() const; QList handles(); const QList& sideHandles() const; QList sideHandles(); QByteArray saveXml( QMap &handleMap); void loadXml(KoStore *store, QMap &handleMap, QString path); void saveXmlList(QDomDocument& doc, QDomElement& ssistantsElement, int count); void findHandleLocation(); KisPaintingAssistantHandleSP oppHandleOne(); /** * Get the topLeft, bottomLeft, topRight and BottomRight corners of the assistant */ const KisPaintingAssistantHandleSP topLeft() const; KisPaintingAssistantHandleSP topLeft(); const KisPaintingAssistantHandleSP topRight() const; KisPaintingAssistantHandleSP topRight(); const KisPaintingAssistantHandleSP bottomLeft() const; KisPaintingAssistantHandleSP bottomLeft(); const KisPaintingAssistantHandleSP bottomRight() const; KisPaintingAssistantHandleSP bottomRight(); const KisPaintingAssistantHandleSP topMiddle() const; KisPaintingAssistantHandleSP topMiddle(); const KisPaintingAssistantHandleSP rightMiddle() const; KisPaintingAssistantHandleSP rightMiddle(); const KisPaintingAssistantHandleSP leftMiddle() const; KisPaintingAssistantHandleSP leftMiddle(); const KisPaintingAssistantHandleSP bottomMiddle() const; KisPaintingAssistantHandleSP bottomMiddle(); public: /** * This will paint a path using a white and black colors. */ static void drawPath(QPainter& painter, const QPainterPath& path, bool drawActive=true); static void drawPreview(QPainter& painter, const QPainterPath& path); protected: virtual QRect boundingRect() const; virtual void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) = 0; void initHandles(QList _handles); QList m_handles; private: struct Private; Private* const d; - + }; /** * Allow to create a painting assistant. */ class KRITAUI_EXPORT KisPaintingAssistantFactory { public: KisPaintingAssistantFactory(); virtual ~KisPaintingAssistantFactory(); virtual QString id() const = 0; virtual QString name() const = 0; virtual KisPaintingAssistant* createPaintingAssistant() const = 0; }; class KRITAUI_EXPORT KisPaintingAssistantFactoryRegistry : public KoGenericRegistry { public: KisPaintingAssistantFactoryRegistry(); ~KisPaintingAssistantFactoryRegistry() override; static KisPaintingAssistantFactoryRegistry* instance(); - + }; #endif diff --git a/libs/ui/kis_paintop_box.cc b/libs/ui/kis_paintop_box.cc index 799baf0846..79a7c8a2be 100644 --- a/libs/ui/kis_paintop_box.cc +++ b/libs/ui/kis_paintop_box.cc @@ -1,1290 +1,1276 @@ /* * kis_paintop_box.cc - part of KImageShop/Krayon/Krita * * Copyright (c) 2004 Boudewijn Rempt (boud@valdyas.org) * Copyright (c) 2009-2011 Sven Langkamp (sven.langkamp@gmail.com) * Copyright (c) 2010 Lukáš Tvrdý * Copyright (C) 2011 Silvio Heinrich * Copyright (C) 2011 Srikanth Tiyyagura * Copyright (c) 2014 Mohit Goyal * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_paintop_box.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_node_manager.h" #include "KisViewManager.h" #include "kis_canvas_resource_provider.h" #include "kis_resource_server_provider.h" #include "kis_favorite_resource_manager.h" #include "kis_config.h" #include "widgets/kis_popup_button.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_popup.h" #include "widgets/kis_tool_options_popup.h" #include "widgets/kis_paintop_presets_chooser_popup.h" #include "widgets/kis_workspace_chooser.h" #include "widgets/kis_paintop_list_widget.h" #include "widgets/kis_slider_spin_box.h" #include "widgets/kis_cmb_composite.h" #include "widgets/kis_widget_chooser.h" #include "tool/kis_tool.h" #include "kis_signals_blocker.h" #include "kis_action_manager.h" #include "kis_highlighted_button.h" typedef KoResourceServerSimpleConstruction > KisPaintOpPresetResourceServer; typedef KoResourceServerAdapter > KisPaintOpPresetResourceServerAdapter; KisPaintopBox::KisPaintopBox(KisViewManager *view, QWidget *parent, const char *name) : QWidget(parent) , m_resourceProvider(view->resourceProvider()) , m_optionWidget(0) , m_toolOptionsPopupButton(0) , m_brushEditorPopupButton(0) , m_presetSelectorPopupButton(0) , m_toolOptionsPopup(0) , m_viewManager(view) , m_previousNode(0) , m_currTabletToolID(KoInputDevice::invalid()) , m_presetsEnabled(true) , m_blockUpdate(false) , m_dirtyPresetsEnabled(false) , m_eraserBrushSizeEnabled(false) , m_eraserBrushOpacityEnabled(false) { Q_ASSERT(view != 0); setObjectName(name); KisConfig cfg; m_dirtyPresetsEnabled = cfg.useDirtyPresets(); m_eraserBrushSizeEnabled = cfg.useEraserBrushSize(); m_eraserBrushOpacityEnabled = cfg.useEraserBrushOpacity(); KAcceleratorManager::setNoAccel(this); setWindowTitle(i18n("Painter's Toolchest")); m_favoriteResourceManager = new KisFavoriteResourceManager(this); KConfigGroup grp = KSharedConfig::openConfig()->group("krita").group("Toolbar BrushesAndStuff"); int iconsize = grp.readEntry("IconSize", 32); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopupButton = new KisPopupButton(this); m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); m_toolOptionsPopupButton->setToolTip(i18n("Tool Settings")); m_toolOptionsPopupButton->setFixedSize(iconsize, iconsize); } m_brushEditorPopupButton = new KisPopupButton(this); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_brushEditorPopupButton->setToolTip(i18n("Edit brush settings")); m_brushEditorPopupButton->setFixedSize(iconsize, iconsize); m_presetSelectorPopupButton = new KisPopupButton(this); m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_presetSelectorPopupButton->setToolTip(i18n("Choose brush preset")); m_presetSelectorPopupButton->setFixedSize(iconsize, iconsize); m_eraseModeButton = new KisHighlightedToolButton(this); m_eraseModeButton->setFixedSize(iconsize, iconsize); m_eraseModeButton->setCheckable(true); m_eraseAction = m_viewManager->actionManager()->createAction("erase_action"); m_eraseModeButton->setDefaultAction(m_eraseAction); m_reloadButton = new QToolButton(this); m_reloadButton->setFixedSize(iconsize, iconsize); m_reloadAction = m_viewManager->actionManager()->createAction("reload_preset_action"); m_reloadButton->setDefaultAction(m_reloadAction); m_alphaLockButton = new KisHighlightedToolButton(this); m_alphaLockButton->setFixedSize(iconsize, iconsize); m_alphaLockButton->setCheckable(true); KisAction* alphaLockAction = m_viewManager->actionManager()->createAction("preserve_alpha"); m_alphaLockButton->setDefaultAction(alphaLockAction); // pen pressure m_disablePressureButton = new KisHighlightedToolButton(this); m_disablePressureButton->setFixedSize(iconsize, iconsize); m_disablePressureButton->setCheckable(true); m_disablePressureAction = m_viewManager->actionManager()->createAction("disable_pressure"); m_disablePressureButton->setDefaultAction(m_disablePressureAction); // horizontal and vertical mirror toolbar buttons // mirror tool options for the X Mirror QMenu *toolbarMenuXMirror = new QMenu(); - KisAction* hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); - hideCanvasDecorationsX->setCheckable(true); - hideCanvasDecorationsX->setText(i18n("Hide Mirror Line")); + hideCanvasDecorationsX = m_viewManager->actionManager()->createAction("mirrorX-hideDecorations"); toolbarMenuXMirror->addAction(hideCanvasDecorationsX); - KisAction* lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); - lockActionX->setText(i18n("Lock")); - lockActionX->setCheckable(true); + lockActionX = m_viewManager->actionManager()->createAction("mirrorX-lock"); toolbarMenuXMirror->addAction(lockActionX); - KisAction* moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); - moveToCenterActionX->setCheckable(false); - moveToCenterActionX->setText(i18n("Move to Canvas Center")); + moveToCenterActionX = m_viewManager->actionManager()->createAction("mirrorX-moveToCenter"); toolbarMenuXMirror->addAction(moveToCenterActionX); - // mirror tool options for the Y Mirror QMenu *toolbarMenuYMirror = new QMenu(); - KisAction* hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); - hideCanvasDecorationsY->setCheckable(true); - hideCanvasDecorationsY->setText(i18n("Hide Mirror Line")); + hideCanvasDecorationsY = m_viewManager->actionManager()->createAction("mirrorY-hideDecorations"); toolbarMenuYMirror->addAction(hideCanvasDecorationsY); - KisAction* lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); - lockActionY->setText(i18n("Lock")); - lockActionY->setCheckable(true); + lockActionY = m_viewManager->actionManager()->createAction("mirrorY-lock"); toolbarMenuYMirror->addAction(lockActionY); - KisAction* moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); - moveToCenterActionY->setCheckable(false); - moveToCenterActionY->setText(i18n("Move to Canvas Center")); + moveToCenterActionY = m_viewManager->actionManager()->createAction("mirrorY-moveToCenter"); toolbarMenuYMirror->addAction(moveToCenterActionY); - // create horizontal and vertical mirror buttons m_hMirrorButton = new KisHighlightedToolButton(this); int menuPadding = 10; m_hMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_hMirrorButton->setCheckable(true); m_hMirrorAction = m_viewManager->actionManager()->createAction("hmirror_action"); m_hMirrorButton->setDefaultAction(m_hMirrorAction); m_hMirrorButton->setMenu(toolbarMenuXMirror); m_hMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); m_vMirrorButton = new KisHighlightedToolButton(this); m_vMirrorButton->setFixedSize(iconsize + menuPadding, iconsize); m_vMirrorButton->setCheckable(true); m_vMirrorAction = m_viewManager->actionManager()->createAction("vmirror_action"); m_vMirrorButton->setDefaultAction(m_vMirrorAction); m_vMirrorButton->setMenu(toolbarMenuYMirror); m_vMirrorButton->setPopupMode(QToolButton::MenuButtonPopup); // add connections for horizontal and mirrror buttons connect(lockActionX, SIGNAL(toggled(bool)), this, SLOT(slotLockXMirrorToggle(bool))); connect(lockActionY, SIGNAL(toggled(bool)), this, SLOT(slotLockYMirrorToggle(bool))); connect(moveToCenterActionX, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorX())); connect(moveToCenterActionY, SIGNAL(triggered(bool)), this, SLOT(slotMoveToCenterMirrorY())); connect(hideCanvasDecorationsX, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorX(bool))); connect(hideCanvasDecorationsY, SIGNAL(toggled(bool)), this, SLOT(slotHideDecorationMirrorY(bool))); const bool sliderLabels = cfg.sliderLabels(); int sliderWidth; if (sliderLabels) { sliderWidth = 150 * logicalDpiX() / 96; } else { sliderWidth = 120 * logicalDpiX() / 96; } for (int i = 0; i < 3; ++i) { m_sliderChooser[i] = new KisWidgetChooser(i + 1); KisDoubleSliderSpinBox* slOpacity; KisDoubleSliderSpinBox* slFlow; KisDoubleSliderSpinBox* slSize; if (sliderLabels) { slOpacity = m_sliderChooser[i]->addWidget("opacity"); slFlow = m_sliderChooser[i]->addWidget("flow"); slSize = m_sliderChooser[i]->addWidget("size"); slOpacity->setPrefix(QString("%1 ").arg(i18n("Opacity:"))); slFlow->setPrefix(QString("%1 ").arg(i18n("Flow:"))); slSize->setPrefix(QString("%1 ").arg(i18n("Size:"))); } else { slOpacity = m_sliderChooser[i]->addWidget("opacity", i18n("Opacity:")); slFlow = m_sliderChooser[i]->addWidget("flow", i18n("Flow:")); slSize = m_sliderChooser[i]->addWidget("size", i18n("Size:")); } slOpacity->setRange(0.0, 1.0, 2); slOpacity->setValue(1.0); slOpacity->setSingleStep(0.05); slOpacity->setMinimumWidth(qMax(sliderWidth, slOpacity->sizeHint().width())); slOpacity->setFixedHeight(iconsize); slOpacity->setBlockUpdateSignalOnDrag(true); slFlow->setRange(0.0, 1.0, 2); slFlow->setValue(1.0); slFlow->setSingleStep(0.05); slFlow->setMinimumWidth(qMax(sliderWidth, slFlow->sizeHint().width())); slFlow->setFixedHeight(iconsize); slFlow->setBlockUpdateSignalOnDrag(true); slSize->setRange(0, cfg.readEntry("maximumBrushSize", 1000), 2); slSize->setValue(100); slSize->setSingleStep(1); slSize->setExponentRatio(3.0); slSize->setSuffix(i18n(" px")); slSize->setMinimumWidth(qMax(sliderWidth, slSize->sizeHint().width())); slSize->setFixedHeight(iconsize); slSize->setBlockUpdateSignalOnDrag(true); m_sliderChooser[i]->chooseWidget(cfg.toolbarSlider(i + 1)); } m_cmbCompositeOp = new KisCompositeOpComboBox(); m_cmbCompositeOp->setFixedHeight(iconsize); Q_FOREACH (KisAction * a, m_cmbCompositeOp->blendmodeActions()) { m_viewManager->actionManager()->addAction(a->text(), a); } m_workspaceWidget = new KisPopupButton(this); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_workspaceWidget->setToolTip(i18n("Choose workspace")); m_workspaceWidget->setFixedSize(iconsize, iconsize); m_workspaceWidget->setPopupWidget(new KisWorkspaceChooser(view)); QHBoxLayout* baseLayout = new QHBoxLayout(this); m_paintopWidget = new QWidget(this); baseLayout->addWidget(m_paintopWidget); baseLayout->setSpacing(4); baseLayout->setContentsMargins(0, 0, 0, 0); m_layout = new QHBoxLayout(m_paintopWidget); if (!cfg.toolOptionsInDocker()) { m_layout->addWidget(m_toolOptionsPopupButton); } m_layout->addWidget(m_brushEditorPopupButton); m_layout->addWidget(m_presetSelectorPopupButton); m_layout->setSpacing(4); m_layout->setContentsMargins(0, 0, 0, 0); QWidget* compositeActions = new QWidget(this); QHBoxLayout* compositeLayout = new QHBoxLayout(compositeActions); compositeLayout->addWidget(m_cmbCompositeOp); compositeLayout->addWidget(m_eraseModeButton); compositeLayout->addWidget(m_alphaLockButton); compositeLayout->setSpacing(4); compositeLayout->setContentsMargins(0, 0, 0, 0); compositeLayout->addWidget(m_reloadButton); QWidgetAction * action; action = new QWidgetAction(this); view->actionCollection()->addAction("composite_actions", action); action->setText(i18n("Brush composite")); action->setDefaultWidget(compositeActions); QWidget* compositePressure = new QWidget(this); QHBoxLayout* pressureLayout = new QHBoxLayout(compositePressure); pressureLayout->addWidget(m_disablePressureButton); pressureLayout->setSpacing(4); pressureLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); view->actionCollection()->addAction("pressure_action", action); action->setText(i18n("Pressure usage (small button)")); action->setDefaultWidget(compositePressure); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider1", action); view->actionCollection()->addAction("brushslider1", action); action->setDefaultWidget(m_sliderChooser[0]); connect(action, SIGNAL(triggered()), m_sliderChooser[0], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[0], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider2", action); view->actionCollection()->addAction("brushslider2", action); action->setDefaultWidget(m_sliderChooser[1]); connect(action, SIGNAL(triggered()), m_sliderChooser[1], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[1], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("brushslider3", action); view->actionCollection()->addAction("brushslider3", action); action->setDefaultWidget(m_sliderChooser[2]); connect(action, SIGNAL(triggered()), m_sliderChooser[2], SLOT(showPopupWidget())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_sliderChooser[2], SLOT(updateThemedIcons())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("next_favorite_preset", action); view->actionCollection()->addAction("next_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotNextFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_favorite_preset", action); view->actionCollection()->addAction("previous_favorite_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotPreviousFavoritePreset())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("previous_preset", action); view->actionCollection()->addAction("previous_preset", action); connect(action, SIGNAL(triggered()), this, SLOT(slotSwitchToPreviousPreset())); if (!cfg.toolOptionsInDocker()) { action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_tool_options", action); view->actionCollection()->addAction("show_tool_options", action); connect(action, SIGNAL(triggered()), m_toolOptionsPopupButton, SLOT(showPopupWidget())); } action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_editor", action); view->actionCollection()->addAction("show_brush_editor", action); connect(action, SIGNAL(triggered()), m_brushEditorPopupButton, SLOT(showPopupWidget())); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("show_brush_presets", action); view->actionCollection()->addAction("show_brush_presets", action); connect(action, SIGNAL(triggered()), m_presetSelectorPopupButton, SLOT(showPopupWidget())); QWidget* mirrorActions = new QWidget(this); QHBoxLayout* mirrorLayout = new QHBoxLayout(mirrorActions); mirrorLayout->addWidget(m_hMirrorButton); mirrorLayout->addWidget(m_vMirrorButton); mirrorLayout->setSpacing(4); mirrorLayout->setContentsMargins(0, 0, 0, 0); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("mirror_actions", action); action->setDefaultWidget(mirrorActions); view->actionCollection()->addAction("mirror_actions", action); action = new QWidgetAction(this); KisActionRegistry::instance()->propertizeAction("workspaces", action); view->actionCollection()->addAction("workspaces", action); action->setDefaultWidget(m_workspaceWidget); if (!cfg.toolOptionsInDocker()) { m_toolOptionsPopup = new KisToolOptionsPopup(); m_toolOptionsPopupButton->setPopupWidget(m_toolOptionsPopup); m_toolOptionsPopup->switchDetached(false); } m_savePresetWidget = new KisPresetSaveWidget(this); m_presetsPopup = new KisPaintOpPresetsPopup(m_resourceProvider, m_favoriteResourceManager, m_savePresetWidget); m_brushEditorPopupButton->setPopupWidget(m_presetsPopup); m_presetsPopup->parentWidget()->setWindowTitle(i18n("Brush Editor")); connect(m_presetsPopup, SIGNAL(brushEditorShown()), SLOT(slotUpdateOptionsWidgetPopup())); connect(m_viewManager->mainWindow(), SIGNAL(themeChanged()), m_presetsPopup, SLOT(updateThemedIcons())); m_presetsChooserPopup = new KisPaintOpPresetsChooserPopup(); m_presetSelectorPopupButton->setPopupWidget(m_presetsChooserPopup); m_currCompositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); slotNodeChanged(view->activeNode()); // Get all the paintops QList keys = KisPaintOpRegistry::instance()->keys(); QList factoryList; Q_FOREACH (const QString & paintopId, keys) { factoryList.append(KisPaintOpRegistry::instance()->get(paintopId)); } m_presetsPopup->setPaintOpList(factoryList); connect(m_presetsPopup , SIGNAL(paintopActivated(QString)) , SLOT(slotSetPaintop(QString))); connect(m_presetsPopup , SIGNAL(defaultPresetClicked()) , SLOT(slotSetupDefaultPreset())); connect(m_presetsPopup , SIGNAL(signalResourceSelected(KoResource*)), SLOT(resourceSelected(KoResource*))); connect(m_presetsPopup , SIGNAL(reloadPresetClicked()) , SLOT(slotReloadPreset())); connect(m_presetsPopup , SIGNAL(dirtyPresetToggled(bool)) , SLOT(slotDirtyPresetToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushSizeToggled(bool)) , SLOT(slotEraserBrushSizeToggled(bool))); connect(m_presetsPopup , SIGNAL(eraserBrushOpacityToggled(bool)) , SLOT(slotEraserBrushOpacityToggled(bool))); connect(m_presetsChooserPopup, SIGNAL(resourceSelected(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_presetsChooserPopup, SIGNAL(resourceClicked(KoResource*)) , SLOT(resourceSelected(KoResource*))); connect(m_resourceProvider , SIGNAL(sigNodeChanged(const KisNodeSP)) , SLOT(slotNodeChanged(const KisNodeSP))); connect(m_cmbCompositeOp , SIGNAL(currentIndexChanged(int)) , SLOT(slotSetCompositeMode(int))); connect(m_eraseAction , SIGNAL(toggled(bool)) , SLOT(slotToggleEraseMode(bool))); connect(alphaLockAction , SIGNAL(toggled(bool)) , SLOT(slotToggleAlphaLockMode(bool))); connect(m_disablePressureAction , SIGNAL(toggled(bool)) , SLOT(slotDisablePressureMode(bool))); m_disablePressureAction->setChecked(true); connect(m_hMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotHorizontalMirrorChanged(bool))); connect(m_vMirrorAction , SIGNAL(toggled(bool)) , SLOT(slotVerticalMirrorChanged(bool))); connect(m_reloadAction , SIGNAL(triggered()) , SLOT(slotReloadPreset())); connect(m_sliderChooser[0]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[0]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider1Changed())); connect(m_sliderChooser[1]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[1]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider2Changed())); connect(m_sliderChooser[2]->getWidget("opacity"), SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("flow") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); connect(m_sliderChooser[2]->getWidget("size") , SIGNAL(valueChanged(qreal)), SLOT(slotSlider3Changed())); //Needed to connect canvas to favorite resource manager connect(m_viewManager->resourceProvider(), SIGNAL(sigFGColorChanged(KoColor)), SLOT(slotUnsetEraseMode())); connect(m_resourceProvider, SIGNAL(sigFGColorUsed(KoColor)), m_favoriteResourceManager, SLOT(slotAddRecentColor(KoColor))); connect(m_resourceProvider, SIGNAL(sigFGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotChangeFGColorSelector(KoColor))); connect(m_resourceProvider, SIGNAL(sigBGColorChanged(KoColor)), m_favoriteResourceManager, SLOT(slotSetBGColor(KoColor))); // cold initialization m_favoriteResourceManager->slotChangeFGColorSelector(m_resourceProvider->fgColor()); m_favoriteResourceManager->slotSetBGColor(m_resourceProvider->bgColor()); connect(m_favoriteResourceManager, SIGNAL(sigSetFGColor(KoColor)), m_resourceProvider, SLOT(slotSetFGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigSetBGColor(KoColor)), m_resourceProvider, SLOT(slotSetBGColor(KoColor))); connect(m_favoriteResourceManager, SIGNAL(sigEnableChangeColor(bool)), m_resourceProvider, SLOT(slotResetEnableFGChange(bool))); connect(view->mainWindow(), SIGNAL(themeChanged()), this, SLOT(slotUpdateSelectionIcon())); slotInputDeviceChanged(KoToolManager::instance()->currentInputDevice()); } KisPaintopBox::~KisPaintopBox() { KisConfig cfg; QMapIterator iter(m_tabletToolMap); while (iter.hasNext()) { iter.next(); //qDebug() << "Writing last used preset for" << iter.key().pointer << iter.key().uniqueID << iter.value().preset->name(); if ((iter.key().pointer) == QTabletEvent::Eraser) { cfg.writeEntry(QString("LastEraser_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } else { cfg.writeEntry(QString("LastPreset_%1").arg(iter.key().uniqueID) , iter.value().preset->name()); } } // Do not delete the widget, since it it is global to the application, not owned by the view m_presetsPopup->setPaintOpSettingsWidget(0); qDeleteAll(m_paintopOptionWidgets); delete m_favoriteResourceManager; for (int i = 0; i < 3; ++i) { delete m_sliderChooser[i]; } } void KisPaintopBox::restoreResource(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); //qDebug() << "restoreResource" << resource << preset; if (preset) { setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::newOptionWidgets(const QList > &optionWidgetList) { if (m_toolOptionsPopup) { m_toolOptionsPopup->newOptionWidgets(optionWidgetList); } } void KisPaintopBox::resourceSelected(KoResource* resource) { KisPaintOpPreset* preset = dynamic_cast(resource); if (preset && preset != m_resourceProvider->currentPreset()) { if (!preset->settings()->isLoadable()) return; if (!m_dirtyPresetsEnabled) { KisSignalsBlocker blocker(m_optionWidget); if (!preset->load()) { warnKrita << "failed to load the preset."; } } //qDebug() << "resourceSelected" << resource->name(); setCurrentPaintop(preset); m_presetsPopup->setPresetImage(preset->image()); m_presetsPopup->resourceSelected(resource); } } void KisPaintopBox::setCurrentPaintop(const KoID& paintop) { KisPaintOpPresetSP preset = activePreset(paintop); Q_ASSERT(preset && preset->settings()); //qDebug() << "setCurrentPaintop();" << paintop << preset; setCurrentPaintop(preset); } void KisPaintopBox::setCurrentPaintop(KisPaintOpPresetSP preset) { //qDebug() << "setCurrentPaintop(); " << preset->name(); if (preset == m_resourceProvider->currentPreset()) { if (preset == m_tabletToolMap[m_currTabletToolID].preset) { return; } } Q_ASSERT(preset); const KoID& paintop = preset->paintOp(); m_presetConnections.clear(); if (m_resourceProvider->currentPreset()) { m_resourceProvider->setPreviousPaintOpPreset(m_resourceProvider->currentPreset()); if (m_optionWidget) { m_optionWidget->hide(); } } if (!m_paintopOptionWidgets.contains(paintop)) m_paintopOptionWidgets[paintop] = KisPaintOpRegistry::instance()->get(paintop.id())->createConfigWidget(this); m_optionWidget = m_paintopOptionWidgets[paintop]; KisSignalsBlocker b(m_optionWidget); preset->setOptionsWidget(m_optionWidget); m_optionWidget->setImage(m_viewManager->image()); m_optionWidget->setNode(m_viewManager->activeNode()); m_presetsPopup->setPaintOpSettingsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); Q_ASSERT(m_optionWidget && m_presetSelectorPopupButton); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotGuiChangedCurrentPreset())); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigSaveLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP))); m_presetConnections.addConnection(m_optionWidget, SIGNAL(sigDropLockedConfig(KisPropertiesConfigurationSP)), this, SLOT(slotDropLockedOption(KisPropertiesConfigurationSP))); // load the current brush engine icon for the brush editor toolbar button KisPaintOpFactory* paintOp = KisPaintOpRegistry::instance()->get(paintop.id()); QString pixFilename = KoResourcePaths::findResource("kis_images", paintOp->pixmap()); m_brushEditorPopupButton->setIcon(QIcon(pixFilename)); m_presetsPopup->setCurrentPaintOpId(paintop.id()); ////qDebug() << "\tsetting the new preset for" << m_currTabletToolID.uniqueID << "to" << preset->name(); m_paintOpPresetMap[m_resourceProvider->currentPreset()->paintOp()] = preset; m_tabletToolMap[m_currTabletToolID].preset = preset; m_tabletToolMap[m_currTabletToolID].paintOpID = preset->paintOp(); if (m_presetsPopup->currentPaintOpId() != paintop.id()) { // Must change the paintop as the current one is not supported // by the new colorspace. dbgKrita << "current paintop " << paintop.name() << " was not set, not supported by colorspace"; } } void KisPaintopBox::slotUpdateOptionsWidgetPopup() { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); KIS_SAFE_ASSERT_RECOVER_RETURN(preset); KIS_SAFE_ASSERT_RECOVER_RETURN(m_optionWidget); m_optionWidget->setConfigurationSafe(preset->settings()); m_presetsPopup->resourceSelected(preset.data()); m_presetsPopup->updateViewSettings(); // the m_viewManager->image() is set earlier, but the reference will be missing when the stamp button is pressed // need to later do some research on how and when we should be using weak shared pointers (WSP) that creates this situation m_optionWidget->setImage(m_viewManager->image()); } KisPaintOpPresetSP KisPaintopBox::defaultPreset(const KoID& paintOp) { QString defaultName = paintOp.id() + ".kpp"; QString path = KoResourcePaths::findResource("kis_defaultpresets", defaultName); KisPaintOpPresetSP preset = new KisPaintOpPreset(path); if (!preset->load()) { preset = KisPaintOpRegistry::instance()->defaultPreset(paintOp); } Q_ASSERT(preset); Q_ASSERT(preset->valid()); return preset; } KisPaintOpPresetSP KisPaintopBox::activePreset(const KoID& paintOp) { if (m_paintOpPresetMap[paintOp] == 0) { m_paintOpPresetMap[paintOp] = defaultPreset(paintOp); } return m_paintOpPresetMap[paintOp]; } void KisPaintopBox::updateCompositeOp(QString compositeOpID) { if (!m_optionWidget) return; KisSignalsBlocker blocker(m_optionWidget); KisNodeSP node = m_resourceProvider->currentNode(); if (node && node->paintDevice()) { if (!node->paintDevice()->colorSpace()->hasCompositeOp(compositeOpID)) compositeOpID = KoCompositeOpRegistry::instance().getDefaultCompositeOp().id(); { KisSignalsBlocker b1(m_cmbCompositeOp); m_cmbCompositeOp->selectCompositeOp(KoID(compositeOpID)); } if (compositeOpID != m_currCompositeOpID) { m_currCompositeOpID = compositeOpID; } if (compositeOpID == COMPOSITE_ERASE) { m_eraseModeButton->setChecked(true); } else { m_eraseModeButton->setChecked(false); } } } void KisPaintopBox::setWidgetState(int flags) { if (flags & (ENABLE_COMPOSITEOP | DISABLE_COMPOSITEOP)) { m_cmbCompositeOp->setEnabled(flags & ENABLE_COMPOSITEOP); m_eraseModeButton->setEnabled(flags & ENABLE_COMPOSITEOP); } if (flags & (ENABLE_PRESETS | DISABLE_PRESETS)) { m_presetSelectorPopupButton->setEnabled(flags & ENABLE_PRESETS); m_brushEditorPopupButton->setEnabled(flags & ENABLE_PRESETS); } for (int i = 0; i < 3; ++i) { if (flags & (ENABLE_OPACITY | DISABLE_OPACITY)) m_sliderChooser[i]->getWidget("opacity")->setEnabled(flags & ENABLE_OPACITY); if (flags & (ENABLE_FLOW | DISABLE_FLOW)) m_sliderChooser[i]->getWidget("flow")->setEnabled(flags & ENABLE_FLOW); if (flags & (ENABLE_SIZE | DISABLE_SIZE)) m_sliderChooser[i]->getWidget("size")->setEnabled(flags & ENABLE_SIZE); } } void KisPaintopBox::setSliderValue(const QString& sliderID, qreal value) { for (int i = 0; i < 3; ++i) { KisDoubleSliderSpinBox* slider = m_sliderChooser[i]->getWidget(sliderID); KisSignalsBlocker b(slider); slider->setValue(value); } } void KisPaintopBox::slotSetPaintop(const QString& paintOpId) { if (KisPaintOpRegistry::instance()->get(paintOpId) != 0) { KoID id(paintOpId, KisPaintOpRegistry::instance()->get(paintOpId)->name()); //qDebug() << "slotsetpaintop" << id; setCurrentPaintop(id); } } void KisPaintopBox::slotInputDeviceChanged(const KoInputDevice& inputDevice) { TabletToolMap::iterator toolData = m_tabletToolMap.find(inputDevice); //qDebug() << "slotInputDeviceChanged()" << inputDevice.device() << inputDevice.uniqueTabletId(); m_currTabletToolID = TabletToolID(inputDevice); if (toolData == m_tabletToolMap.end()) { KisConfig cfg; KisPaintOpPresetResourceServer *rserver = KisResourceServerProvider::instance()->paintOpPresetServer(false); KisPaintOpPresetSP preset; if (inputDevice.pointer() == QTabletEvent::Eraser) { preset = rserver->resourceByName(cfg.readEntry(QString("LastEraser_%1").arg(inputDevice.uniqueTabletId()), "Eraser_circle")); } else { preset = rserver->resourceByName(cfg.readEntry(QString("LastPreset_%1").arg(inputDevice.uniqueTabletId()), "Basic_tip_default")); //if (preset) //qDebug() << "found stored preset " << preset->name() << "for" << inputDevice.uniqueTabletId(); //else //qDebug() << "no preset fcound for" << inputDevice.uniqueTabletId(); } if (!preset) { preset = rserver->resourceByName("Basic_tip_default"); } if (preset) { //qDebug() << "inputdevicechanged 1" << preset; setCurrentPaintop(preset); } } else { if (toolData->preset) { //qDebug() << "inputdevicechanged 2" << toolData->preset; setCurrentPaintop(toolData->preset); } else { //qDebug() << "inputdevicechanged 3" << toolData->paintOpID; setCurrentPaintop(toolData->paintOpID); } } } void KisPaintopBox::slotCanvasResourceChanged(int key, const QVariant &value) { if (m_viewManager) { sender()->blockSignals(true); KisPaintOpPresetSP preset = m_viewManager->resourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentPaintOpPreset).value(); if (preset && m_resourceProvider->currentPreset()->name() != preset->name()) { QString compositeOp = preset->settings()->getString("CompositeOp"); updateCompositeOp(compositeOp); resourceSelected(preset.data()); } /** * Update currently selected preset in both the popup widgets */ m_presetsChooserPopup->canvasResourceChanged(preset); m_presetsPopup->currentPresetChanged(preset); if (key == KisCanvasResourceProvider::CurrentCompositeOp) { if (m_resourceProvider->currentCompositeOp() != m_currCompositeOpID) { updateCompositeOp(m_resourceProvider->currentCompositeOp()); } } if (key == KisCanvasResourceProvider::Size) { setSliderValue("size", m_resourceProvider->size()); } if (key == KisCanvasResourceProvider::Opacity) { setSliderValue("opacity", m_resourceProvider->opacity()); } if (key == KisCanvasResourceProvider::Flow) { setSliderValue("flow", m_resourceProvider->flow()); } if (key == KisCanvasResourceProvider::EraserMode) { m_eraseAction->setChecked(value.toBool()); } if (key == KisCanvasResourceProvider::DisablePressure) { m_disablePressureAction->setChecked(value.toBool()); } sender()->blockSignals(false); } } void KisPaintopBox::slotUpdatePreset() { if (!m_resourceProvider->currentPreset()) return; // block updates of avoid some over updating of the option widget m_blockUpdate = true; setSliderValue("size", m_resourceProvider->size()); { qreal opacity = m_resourceProvider->currentPreset()->settings()->paintOpOpacity(); m_resourceProvider->setOpacity(opacity); setSliderValue("opacity", opacity); setWidgetState(ENABLE_OPACITY); } { setSliderValue("flow", m_resourceProvider->currentPreset()->settings()->paintOpFlow()); setWidgetState(ENABLE_FLOW); } { updateCompositeOp(m_resourceProvider->currentPreset()->settings()->paintOpCompositeOp()); setWidgetState(ENABLE_COMPOSITEOP); } m_blockUpdate = false; } void KisPaintopBox::slotSetupDefaultPreset() { KisPaintOpPresetSP preset = defaultPreset(m_resourceProvider->currentPreset()->paintOp()); preset->setOptionsWidget(m_optionWidget); m_resourceProvider->setPaintOpPreset(preset); } void KisPaintopBox::slotNodeChanged(const KisNodeSP node) { if (m_previousNode.isValid() && m_previousNode->paintDevice()) disconnect(m_previousNode->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); // Reconnect colorspace change of node if (node && node->paintDevice()) { connect(node->paintDevice().data(), SIGNAL(colorSpaceChanged(const KoColorSpace*)), this, SLOT(slotColorSpaceChanged(const KoColorSpace*))); m_resourceProvider->setCurrentCompositeOp(m_currCompositeOpID); m_previousNode = node; slotColorSpaceChanged(node->colorSpace()); } if (m_optionWidget) { m_optionWidget->setNode(node); } } void KisPaintopBox::slotColorSpaceChanged(const KoColorSpace* colorSpace) { m_cmbCompositeOp->validate(colorSpace); } void KisPaintopBox::slotToggleEraseMode(bool checked) { const bool oldEraserMode = m_resourceProvider->eraserMode(); m_resourceProvider->setEraserMode(checked); if (oldEraserMode != checked && m_eraserBrushSizeEnabled) { const qreal currentSize = m_resourceProvider->size(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush size. set the eraser size to the normal brush size if not set if (checked) { settings->setSavedBrushSize(currentSize); if (qFuzzyIsNull(settings->savedEraserSize())) { settings->setSavedEraserSize(currentSize); } } else { settings->setSavedEraserSize(currentSize); if (qFuzzyIsNull(settings->savedBrushSize())) { settings->setSavedBrushSize(currentSize); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newSize = checked ? settings->savedEraserSize() : settings->savedBrushSize(); m_resourceProvider->setSize(newSize); } if (oldEraserMode != checked && m_eraserBrushOpacityEnabled) { const qreal currentOpacity = m_resourceProvider->opacity(); KisPaintOpSettingsSP settings = m_resourceProvider->currentPreset()->settings(); // remember brush opacity. set the eraser opacity to the normal brush opacity if not set if (checked) { settings->setSavedBrushOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedEraserOpacity())) { settings->setSavedEraserOpacity(currentOpacity); } } else { settings->setSavedEraserOpacity(currentOpacity); if (qFuzzyIsNull(settings->savedBrushOpacity())) { settings->setSavedBrushOpacity(currentOpacity); } } //update value in UI (this is the main place the value is 'stored' in memory) qreal newOpacity = checked ? settings->savedEraserOpacity() : settings->savedBrushOpacity(); m_resourceProvider->setOpacity(newOpacity); } } void KisPaintopBox::slotSetCompositeMode(int index) { Q_UNUSED(index); QString compositeOp = m_cmbCompositeOp->selectedCompositeOp().id(); m_resourceProvider->setCurrentCompositeOp(compositeOp); } void KisPaintopBox::slotHorizontalMirrorChanged(bool value) { m_resourceProvider->setMirrorHorizontal(value); } void KisPaintopBox::slotVerticalMirrorChanged(bool value) { m_resourceProvider->setMirrorVertical(value); } void KisPaintopBox::sliderChanged(int n) { if (!m_optionWidget) // widget will not exist if the are no documents open return; KisSignalsBlocker blocker(m_optionWidget); qreal opacity = m_sliderChooser[n]->getWidget("opacity")->value(); qreal flow = m_sliderChooser[n]->getWidget("flow")->value(); qreal size = m_sliderChooser[n]->getWidget("size")->value(); setSliderValue("opacity", opacity); setSliderValue("flow" , flow); setSliderValue("size" , size); if (m_presetsEnabled) { // IMPORTANT: set the PaintOp size before setting the other properties // it wont work the other way // TODO: why?! m_resourceProvider->setSize(size); m_resourceProvider->setOpacity(opacity); m_resourceProvider->setFlow(flow); KisLockedPropertiesProxySP propertiesProxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(m_resourceProvider->currentPreset()->settings()); propertiesProxy->setProperty("OpacityValue", opacity); propertiesProxy->setProperty("FlowValue", flow); m_optionWidget->setConfigurationSafe(m_resourceProvider->currentPreset()->settings().data()); } else { m_resourceProvider->setOpacity(opacity); } m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); } void KisPaintopBox::slotSlider1Changed() { sliderChanged(0); } void KisPaintopBox::slotSlider2Changed() { sliderChanged(1); } void KisPaintopBox::slotSlider3Changed() { sliderChanged(2); } void KisPaintopBox::slotToolChanged(KoCanvasController* canvas, int toolId) { Q_UNUSED(canvas); Q_UNUSED(toolId); if (!m_viewManager->canvasBase()) return; QString id = KoToolManager::instance()->activeToolId(); KisTool* tool = dynamic_cast(KoToolManager::instance()->toolById(m_viewManager->canvasBase(), id)); if (tool) { int flags = tool->flags(); if (flags & KisTool::FLAG_USES_CUSTOM_COMPOSITEOP) { setWidgetState(ENABLE_COMPOSITEOP | ENABLE_OPACITY); } else { setWidgetState(DISABLE_COMPOSITEOP | DISABLE_OPACITY); } if (flags & KisTool::FLAG_USES_CUSTOM_PRESET) { setWidgetState(ENABLE_PRESETS); slotUpdatePreset(); m_presetsEnabled = true; } else { setWidgetState(DISABLE_PRESETS); m_presetsEnabled = false; } if (flags & KisTool::FLAG_USES_CUSTOM_SIZE) { setWidgetState(ENABLE_SIZE | ENABLE_FLOW); } else { setWidgetState(DISABLE_SIZE | DISABLE_FLOW); } } else setWidgetState(DISABLE_ALL); } void KisPaintopBox::slotPreviousFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset() && m_resourceProvider->currentPreset()->name() == preset->name()) { if (i > 0) { m_favoriteResourceManager->slotChangeActivePaintop(i - 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(m_favoriteResourceManager->numFavoritePresets() - 1); } return; } i++; } } void KisPaintopBox::slotNextFavoritePreset() { if (!m_favoriteResourceManager) return; int i = 0; Q_FOREACH (KisPaintOpPresetSP preset, m_favoriteResourceManager->favoritePresetList()) { if (m_resourceProvider->currentPreset()->name() == preset->name()) { if (i < m_favoriteResourceManager->numFavoritePresets() - 1) { m_favoriteResourceManager->slotChangeActivePaintop(i + 1); } else { m_favoriteResourceManager->slotChangeActivePaintop(0); } return; } i++; } } void KisPaintopBox::slotSwitchToPreviousPreset() { if (m_resourceProvider->previousPreset()) { //qDebug() << "slotSwitchToPreviousPreset();" << m_resourceProvider->previousPreset(); setCurrentPaintop(m_resourceProvider->previousPreset()); } } void KisPaintopBox::slotUnsetEraseMode() { m_eraseAction->setChecked(false); } void KisPaintopBox::slotToggleAlphaLockMode(bool checked) { if (checked) { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-locked")); } else { m_alphaLockButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transparency-unlocked")); } m_resourceProvider->setGlobalAlphaLock(checked); } void KisPaintopBox::slotDisablePressureMode(bool checked) { if (checked) { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->actions()[0]->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } m_resourceProvider->setDisablePressure(checked); } void KisPaintopBox::slotReloadPreset() { KisSignalsBlocker blocker(m_optionWidget); //Here using the name and fetching the preset from the server was the only way the load was working. Otherwise it was not loading. KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); KisPaintOpPresetSP preset = rserver->resourceByName(m_resourceProvider->currentPreset()->name()); if (preset) { preset->load(); } } void KisPaintopBox::slotGuiChangedCurrentPreset() // Called only when UI is changed and not when preset is changed { KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { /** * Here we postpone all the settings updates events until thye entire writing * operation will be finished. As soon as it is finished, the updates will be * emitted happily (if there were any). */ KisPaintOpPreset::UpdatedPostponer postponer(preset.data()); m_optionWidget->writeConfigurationSafe(const_cast(preset->settings().data())); } // we should also update the preset strip to update the status of the "dirty" mark m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); // TODO!!!!!!!! //m_presetsPopup->updateViewSettings(); } void KisPaintopBox::slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p) { QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); m_resourceProvider->currentPreset()->settings()->setProperty(i.key(), QVariant(i.value())); if (m_resourceProvider->currentPreset()->settings()->hasProperty(i.key() + "_previous")) { m_resourceProvider->currentPreset()->settings()->removeProperty(i.key() + "_previous"); } } slotGuiChangedCurrentPreset(); } void KisPaintopBox::slotDropLockedOption(KisPropertiesConfigurationSP p) { KisSignalsBlocker blocker(m_optionWidget); KisPaintOpPresetSP preset = m_resourceProvider->currentPreset(); { KisPaintOpPreset::DirtyStateSaver dirtySaver(preset.data()); QMapIterator i(p->getProperties()); while (i.hasNext()) { i.next(); if (preset->settings()->hasProperty(i.key() + "_previous")) { preset->settings()->setProperty(i.key(), preset->settings()->getProperty(i.key() + "_previous")); preset->settings()->removeProperty(i.key() + "_previous"); } } } //slotUpdatePreset(); } void KisPaintopBox::slotDirtyPresetToggled(bool value) { if (!value) { slotReloadPreset(); m_presetsPopup->resourceSelected(m_resourceProvider->currentPreset().data()); m_presetsPopup->updateViewSettings(); } m_dirtyPresetsEnabled = value; KisConfig cfg; cfg.setUseDirtyPresets(m_dirtyPresetsEnabled); } void KisPaintopBox::slotEraserBrushSizeToggled(bool value) { m_eraserBrushSizeEnabled = value; KisConfig cfg; cfg.setUseEraserBrushSize(m_eraserBrushSizeEnabled); } void KisPaintopBox::slotEraserBrushOpacityToggled(bool value) { m_eraserBrushOpacityEnabled = value; KisConfig cfg; cfg.setUseEraserBrushOpacity(m_eraserBrushOpacityEnabled); } void KisPaintopBox::slotUpdateSelectionIcon() { m_hMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-horizontal")); m_vMirrorAction->setIcon(KisIconUtils::loadIcon("symmetry-vertical")); KisConfig cfg; if (!cfg.toolOptionsInDocker() && m_toolOptionsPopupButton) { m_toolOptionsPopupButton->setIcon(KisIconUtils::loadIcon("configure")); } m_presetSelectorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_01")); m_brushEditorPopupButton->setIcon(KisIconUtils::loadIcon("paintop_settings_02")); m_workspaceWidget->setIcon(KisIconUtils::loadIcon("view-choose")); m_eraseAction->setIcon(KisIconUtils::loadIcon("draw-eraser")); m_reloadAction->setIcon(KisIconUtils::loadIcon("view-refresh")); if (m_disablePressureAction->isChecked()) { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure")); } else { m_disablePressureButton->setIcon(KisIconUtils::loadIcon("transform_icons_penPressure_locked")); } } void KisPaintopBox::slotLockXMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorHorizontalLock(toggleLock); } void KisPaintopBox::slotLockYMirrorToggle(bool toggleLock) { m_resourceProvider->setMirrorVerticalLock(toggleLock); } void KisPaintopBox::slotHideDecorationMirrorX(bool toggled) { m_resourceProvider->setMirrorHorizontalHideDecorations(toggled); } void KisPaintopBox::slotHideDecorationMirrorY(bool toggled) { m_resourceProvider->setMirrorVerticalHideDecorations(toggled); } void KisPaintopBox::slotMoveToCenterMirrorX() { m_resourceProvider->mirrorHorizontalMoveCanvasToCenter(); } void KisPaintopBox::slotMoveToCenterMirrorY() { m_resourceProvider->mirrorVerticalMoveCanvasToCenter(); } diff --git a/libs/ui/kis_paintop_box.h b/libs/ui/kis_paintop_box.h index c79d954734..9b6cc973e6 100644 --- a/libs/ui/kis_paintop_box.h +++ b/libs/ui/kis_paintop_box.h @@ -1,255 +1,263 @@ /* * kis_paintop_box.h - part of KImageShop/Krayon/Krita * * Copyright (c) 2004-2008 Boudewijn Rempt (boud@valdyas.org) * Copyright (C) 2011 Silvio Heinrich * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_PAINTOP_BOX_H_ #define KIS_PAINTOP_BOX_H_ #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include "kis_signal_auto_connection.h" #include "kis_signal_compressor.h" class QToolButton; class QString; class QHBoxLayout; class KoColorSpace; class KoResource; class KoCanvasController; class KisViewManager; class KisCanvasResourceProvider; class KisPopupButton; class KisToolOptionsPopup; class KisPaintOpPresetsPopup; class KisPaintOpPresetsChooserPopup; class KisPaintOpConfigWidget; class KisCompositeOpComboBox; class KisWidgetChooser; class KisFavoriteResourceManager; class KisAction; class KisPresetSaveWidget; /** * This widget presents all paintops that a user can paint with. * Paintops represent real-world tools or the well-known Shoup * computer equivalents that do nothing but change color. * * To incorporate the dirty preset functionality and locked settings * the following slots are added * void slotReloadPreset(); void slotGuiChangedCurrentPreset(); void slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p); void slotDropLockedOption(KisPropertiesConfigurationSP p); void slotDirtyPresetToggled(bool); Everytime a value is changed in a preset, the preset is made dirty through the onChange() function. For Locked Settings however, a changed Locked Setting will not cause a preset to become dirty. That is becuase it borrows its values from the KisLockedPropertiesServer. Hence the dirty state of the Preset is kept consistent before and after a writeConfiguration operation in most cases. * XXX: When we have a lot of paintops, replace the listbox * with a table, and for every category a combobox. * * XXX: instead of text, use pretty pictures. */ class KRITAUI_EXPORT KisPaintopBox : public QWidget { Q_OBJECT enum { ENABLE_PRESETS = 0x0001, DISABLE_PRESETS = 0x0002, ENABLE_COMPOSITEOP = 0x0004, DISABLE_COMPOSITEOP = 0x0008, ENABLE_OPACITY = 0x0010, DISABLE_OPACITY = 0x0020, ENABLE_FLOW = 0x0040, DISABLE_FLOW = 0x0080, ENABLE_SIZE = 0x0100, DISABLE_SIZE = 0x0200, ENABLE_ALL = 0x5555, DISABLE_ALL = 0xAAAA }; public: KisPaintopBox(KisViewManager* view, QWidget* parent, const char* name); ~KisPaintopBox() override; void restoreResource(KoResource* resource); /** * Update the option widgets to the argument ones, removing the currently set widgets. */ void newOptionWidgets(const QList > & optionWidgetList); KisFavoriteResourceManager *favoriteResourcesManager() { return m_favoriteResourceManager; } public Q_SLOTS: void slotColorSpaceChanged(const KoColorSpace* colorSpace); void slotInputDeviceChanged(const KoInputDevice & inputDevice); void slotCanvasResourceChanged(int key, const QVariant& v); void resourceSelected(KoResource* resource); private: void setCurrentPaintop(const KoID& paintop); void setCurrentPaintop(KisPaintOpPresetSP preset); KisPaintOpPresetSP defaultPreset(const KoID& paintOp); KisPaintOpPresetSP activePreset(const KoID& paintOp); void updateCompositeOp(QString compositeOpID); void setWidgetState(int flags); void setSliderValue(const QString& sliderID, qreal value); void sliderChanged(int n); private Q_SLOTS: void slotUpdatePreset(); void slotSetupDefaultPreset(); void slotNodeChanged(const KisNodeSP node); void slotToggleEraseMode(bool checked); void slotSetCompositeMode(int index); void slotSetPaintop(const QString& paintOpId); void slotHorizontalMirrorChanged(bool value); void slotVerticalMirrorChanged(bool value); void slotSlider1Changed(); void slotSlider2Changed(); void slotSlider3Changed(); void slotToolChanged(KoCanvasController* canvas, int toolId); void slotPreviousFavoritePreset(); void slotNextFavoritePreset(); void slotSwitchToPreviousPreset(); void slotUnsetEraseMode(); void slotToggleAlphaLockMode(bool); void slotDisablePressureMode(bool); void slotReloadPreset(); void slotGuiChangedCurrentPreset(); void slotSaveLockedOptionToPreset(KisPropertiesConfigurationSP p); void slotDropLockedOption(KisPropertiesConfigurationSP p); void slotDirtyPresetToggled(bool); void slotEraserBrushSizeToggled(bool); void slotEraserBrushOpacityToggled(bool); void slotUpdateSelectionIcon(); void slotLockXMirrorToggle(bool); void slotLockYMirrorToggle(bool); void slotMoveToCenterMirrorX(); void slotMoveToCenterMirrorY(); void slotHideDecorationMirrorX(bool); void slotHideDecorationMirrorY(bool); void slotUpdateOptionsWidgetPopup(); private: KisCanvasResourceProvider* m_resourceProvider; QHBoxLayout* m_layout; QWidget* m_paintopWidget; KisPaintOpConfigWidget* m_optionWidget; KisPopupButton* m_toolOptionsPopupButton; KisPresetSaveWidget* m_savePresetWidget; KisPopupButton* m_brushEditorPopupButton; KisPopupButton* m_presetSelectorPopupButton; KisCompositeOpComboBox* m_cmbCompositeOp; QToolButton* m_eraseModeButton; QToolButton* m_alphaLockButton; QToolButton* m_disablePressureButton; QToolButton* m_hMirrorButton; QToolButton* m_vMirrorButton; KisToolOptionsPopup* m_toolOptionsPopup; KisPaintOpPresetsPopup* m_presetsPopup; KisPaintOpPresetsChooserPopup* m_presetsChooserPopup; KisViewManager* m_viewManager; KisPopupButton* m_workspaceWidget; KisWidgetChooser* m_sliderChooser[3]; QMap m_paintopOptionWidgets; KisFavoriteResourceManager* m_favoriteResourceManager; QToolButton* m_reloadButton; KisAction* m_eraseAction; KisAction* m_reloadAction; KisAction* m_disablePressureAction; QString m_currCompositeOpID; KisNodeWSP m_previousNode; KisAction* m_hMirrorAction; KisAction* m_vMirrorAction; + KisAction* hideCanvasDecorationsX; + KisAction* lockActionX; + KisAction* moveToCenterActionX; + KisAction* hideCanvasDecorationsY; + KisAction* lockActionY; + KisAction* moveToCenterActionY; + + struct TabletToolID { TabletToolID(const KoInputDevice& dev) { uniqueID = dev.uniqueTabletId(); // Only the eraser is special, and we don't look at Cursor pointer = QTabletEvent::Pen; if (dev.pointer() == QTabletEvent::Eraser) { pointer = QTabletEvent::Eraser; } } bool operator == (const TabletToolID& id) const { return pointer == id.pointer && uniqueID == id.uniqueID; } bool operator < (const TabletToolID& id) const { if (uniqueID == id.uniqueID) return pointer < id.pointer; return uniqueID < id.uniqueID; } QTabletEvent::PointerType pointer; qint64 uniqueID; }; struct TabletToolData { KoID paintOpID; KisPaintOpPresetSP preset; }; typedef QMap TabletToolMap; typedef QMap PaintOpPresetMap; TabletToolMap m_tabletToolMap; PaintOpPresetMap m_paintOpPresetMap; TabletToolID m_currTabletToolID; bool m_presetsEnabled; bool m_blockUpdate; bool m_dirtyPresetsEnabled; bool m_eraserBrushSizeEnabled; bool m_eraserBrushOpacityEnabled; KisSignalAutoConnectionsStore m_presetConnections; }; #endif //KIS_PAINTOP_BOX_H_ diff --git a/libs/ui/kis_view_plugin.h b/libs/ui/kis_view_plugin.h index 1415e55bcc..c330eacbaa 100644 --- a/libs/ui/kis_view_plugin.h +++ b/libs/ui/kis_view_plugin.h @@ -1,59 +1,59 @@ /* * Copyright (c) 2013 Sven Langkamp * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_VIEW_PLUGIN_H #define KIS_VIEW_PLUGIN_H #include #include class KisOperation; class KisOperationUIFactory; class KisAction; class KisViewManager; /** * KisViewPlugin is the base for plugins which add actions to the view */ class KRITAUI_EXPORT KisViewPlugin : public QObject { Q_OBJECT public: KisViewPlugin(QObject* parent = 0); ~KisViewPlugin() override; protected: /** * Registers a KisAction to the UI and action manager. - * @param name - title of the action in the krita.xmlgui file + * @param name - title of the action in the krita4.xmlgui file * @param action the action that should be added */ void addAction(const QString& name, KisAction* action); KisAction* createAction(const QString& name); void addUIFactory(KisOperationUIFactory* factory); void addOperation(KisOperation* operation); KisViewManager* m_view; }; #endif // KIS_VIEW_PLUGIN_H diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index 92d73361e8..8d66c13373 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,275 +1,279 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "opengl/kis_opengl.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool defaultFormatIsSet = false; bool isDebugEnabled = false; bool isDebugSynchronous = false; bool initialized = false; bool NeedsFenceWorkaround = false; bool NeedsPixmapCacheWorkaround = false; int glMajorVersion = 0; int glMinorVersion = 0; bool supportsDeprecatedFunctions = false; QString Renderer; QString debugText("OpenGL Info\n **OpenGL not initialized**"); void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } } void KisOpenGL::initialize() { if (initialized) return; KIS_SAFE_ASSERT_RECOVER(defaultFormatIsSet) { qWarning() << "Default OpenGL format was not set before calling KisOpenGL::initialize. This might be a BUG!"; setDefaultFormat(); } // we need a QSurface active to get our GL functions from the context QWindow surface; surface.setSurfaceType( QSurface::OpenGLSurface ); surface.create(); QOpenGLContext context; 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 glMajorVersion = context.format().majorVersion(); glMinorVersion = context.format().minorVersion(); supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); debugText.clear(); QDebug debugOut(&debugText); debugOut << "OpenGL Info"; debugOut << "\n Vendor: " << reinterpret_cast(funcs->glGetString(GL_VENDOR)); debugOut << "\n Renderer: " << Renderer; debugOut << "\n Version: " << reinterpret_cast(funcs->glGetString(GL_VERSION)); debugOut << "\n Shading language: " << reinterpret_cast(funcs->glGetString(GL_SHADING_LANGUAGE_VERSION)); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << context.format(); debugOut.nospace(); debugOut << "\n Version: " << glMajorVersion << "." << glMinorVersion; debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << supportsDeprecatedFunctions; qDebug().noquote() << debugText; initialized = true; } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg; initialize(); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QDesktopServices::storageLocation(QDesktopServices::TempLocation) + "/krita-opengl.txt"); 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 - + 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)); } } const QString &KisOpenGL::getDebugText() { initialize(); return debugText; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return (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(bool enableDebug, bool debugSynchronous) { if (defaultFormatIsSet) { return; } defaultFormatIsSet = true; QSurfaceFormat format; #ifdef Q_OS_OSX format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #else // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing isDebugEnabled = enableDebug; if (enableDebug) { format.setOption(QSurfaceFormat::DebugContext, true); isDebugSynchronous = debugSynchronous; qDebug() << "QOpenGLDebugLogger will be enabled, synchronous:" << debugSynchronous; } QSurfaceFormat::setDefaultFormat(format); } bool KisOpenGL::hasOpenGL() { - return ((glMajorVersion * 100 + glMinorVersion) >= 201); - //return (glMajorVersion >= 3 && supportsDeprecatedFunctions); +#ifdef Q_OS_OSX + return ((glMajorVersion * 100 + glMinorVersion) >= 302); +#else + return (glMajorVersion >= 3 && supportsDeprecatedFunctions) || + ((glMajorVersion * 100 + glMinorVersion) == 201); +#endif } diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp index ec0d6eed3d..4c05ba1a37 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -1,856 +1,889 @@ /* This file is part of the KDE project * Copyright (C) Boudewijn Rempt , (C) 2006-2013 * Copyright (C) 2015 Michael Abrahams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define GL_GLEXT_PROTOTYPES #include "opengl/kis_opengl_canvas2.h" #include "opengl/kis_opengl_canvas2_p.h" #include "opengl/kis_opengl_shader_loader.h" #include "opengl/kis_opengl_canvas_debugger.h" #include "canvas/kis_canvas2.h" #include "canvas/kis_coordinates_converter.h" #include "canvas/kis_display_filter.h" #include "canvas/kis_display_color_converter.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include +#ifndef Q_OS_OSX +#include +#endif + #define NEAR_VAL -1000.0 #define FAR_VAL 1000.0 #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 static bool OPENGL_SUCCESS = false; struct KisOpenGLCanvas2::Private { public: ~Private() { delete displayShader; delete checkerShader; delete solidColorShader; Sync::deleteSync(glSyncObject); } bool canvasInitialized{false}; KisOpenGLImageTexturesSP openGLImageTextures; KisOpenGLShaderLoader shaderLoader; KisShaderProgram *displayShader{0}; KisShaderProgram *checkerShader{0}; KisShaderProgram *solidColorShader{0}; + bool displayShaderCompiledWithDisplayFilterSupport{false}; GLfloat checkSizeScale; bool scrollCheckers; QSharedPointer displayFilter; KisOpenGL::FilterMode filterMode; bool proofingConfigIsUpdated=false; GLsync glSyncObject{0}; bool wrapAroundMode{false}; // Stores a quad for drawing the canvas QOpenGLVertexArrayObject quadVAO; QOpenGLBuffer quadBuffers[2]; // Stores data for drawing tool outlines QOpenGLVertexArrayObject outlineVAO; QOpenGLBuffer lineBuffer; QVector3D vertices[6]; QVector2D texCoords[6]; +#ifndef Q_OS_OSX + QOpenGLFunctions_2_1 *glFn201; +#endif + int xToColWithWrapCompensation(int x, const QRect &imageRect) { int firstImageColumn = openGLImageTextures->xToCol(imageRect.left()); int lastImageColumn = openGLImageTextures->xToCol(imageRect.right()); int colsPerImage = lastImageColumn - firstImageColumn + 1; int numWraps = floor(qreal(x) / imageRect.width()); int remainder = x - imageRect.width() * numWraps; return colsPerImage * numWraps + openGLImageTextures->xToCol(remainder); } int yToRowWithWrapCompensation(int y, const QRect &imageRect) { int firstImageRow = openGLImageTextures->yToRow(imageRect.top()); int lastImageRow = openGLImageTextures->yToRow(imageRect.bottom()); int rowsPerImage = lastImageRow - firstImageRow + 1; int numWraps = floor(qreal(y) / imageRect.height()); int remainder = y - imageRect.height() * numWraps; return rowsPerImage * numWraps + openGLImageTextures->yToRow(remainder); } }; KisOpenGLCanvas2::KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter) : QOpenGLWidget(parent) , KisCanvasWidgetBase(canvas, coordinatesConverter) , d(new Private()) { KisConfig cfg; cfg.setCanvasState("OPENGL_STARTED"); d->openGLImageTextures = KisOpenGLImageTextures::getImageTextures(image, colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); setAcceptDrops(true); setAutoFillBackground(false); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); #ifdef Q_OS_OSX setAttribute(Qt::WA_AcceptTouchEvents, false); #else setAttribute(Qt::WA_AcceptTouchEvents, true); #endif setAttribute(Qt::WA_InputMethodEnabled, true); setAttribute(Qt::WA_DontCreateNativeAncestors, true); setDisplayFilterImpl(colorConverter->displayFilter(), true); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); slotConfigChanged(); cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); } KisOpenGLCanvas2::~KisOpenGLCanvas2() { delete d; } bool KisOpenGLCanvas2::needsFpsDebugging() const { return KisOpenglCanvasDebugger::instance()->showFpsOnCanvas(); } void KisOpenGLCanvas2::setDisplayFilter(QSharedPointer displayFilter) { setDisplayFilterImpl(displayFilter, false); } void KisOpenGLCanvas2::setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing) { bool needsInternalColorManagement = !displayFilter || displayFilter->useInternalColorManagement(); bool needsFullRefresh = d->openGLImageTextures->setInternalColorManagementActive(needsInternalColorManagement); d->displayFilter = displayFilter; - if (d->canvasInitialized) { - d->canvasInitialized = false; - delete d->displayShader; - bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; - try { - d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); - } catch (const ShaderLoaderException &e) { - reportFailedShaderCompilation(e.what()); - } - d->canvasInitialized = true; - } - if (!initializing && needsFullRefresh) { canvas()->startUpdateInPatches(canvas()->image()->bounds()); } else if (!initializing) { canvas()->updateCanvas(); } } void KisOpenGLCanvas2::setWrapAroundViewingMode(bool value) { d->wrapAroundMode = value; update(); } inline void rectToVertices(QVector3D* vertices, const QRectF &rc) { vertices[0] = QVector3D(rc.left(), rc.bottom(), 0.f); vertices[1] = QVector3D(rc.left(), rc.top(), 0.f); vertices[2] = QVector3D(rc.right(), rc.bottom(), 0.f); vertices[3] = QVector3D(rc.left(), rc.top(), 0.f); vertices[4] = QVector3D(rc.right(), rc.top(), 0.f); vertices[5] = QVector3D(rc.right(), rc.bottom(), 0.f); } inline void rectToTexCoords(QVector2D* texCoords, const QRectF &rc) { texCoords[0] = QVector2D(rc.left(), rc.bottom()); texCoords[1] = QVector2D(rc.left(), rc.top()); texCoords[2] = QVector2D(rc.right(), rc.bottom()); texCoords[3] = QVector2D(rc.left(), rc.top()); texCoords[4] = QVector2D(rc.right(), rc.top()); texCoords[5] = QVector2D(rc.right(), rc.bottom()); } void KisOpenGLCanvas2::initializeGL() { KisOpenGL::initializeContext(context()); initializeOpenGLFunctions(); +#ifndef Q_OS_OSX + d->glFn201 = context()->versionFunctions(); + if (!d->glFn201) { + warnUI << "Cannot obtain QOpenGLFunctions_2_1, glLogicOp cannot be used"; + } +#endif KisConfig cfg; d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); d->openGLImageTextures->initGL(context()->functions()); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); initializeShaders(); // If we support OpenGL 3.2, then prepare our VAOs and VBOs for drawing if (KisOpenGL::hasOpenGL3()) { d->quadVAO.create(); d->quadVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); glEnableVertexAttribArray(PROGRAM_TEXCOORD_ATTRIBUTE); // Create the vertex buffer object, it has 6 vertices with 3 components d->quadBuffers[0].create(); d->quadBuffers[0].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[0].bind(); d->quadBuffers[0].allocate(d->vertices, 6 * 3 * sizeof(float)); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); // Create the texture buffer object, it has 6 texture coordinates with 2 components d->quadBuffers[1].create(); d->quadBuffers[1].setUsagePattern(QOpenGLBuffer::StaticDraw); d->quadBuffers[1].bind(); d->quadBuffers[1].allocate(d->texCoords, 6 * 2 * sizeof(float)); glVertexAttribPointer(PROGRAM_TEXCOORD_ATTRIBUTE, 2, GL_FLOAT, GL_FALSE, 0, 0); // Create the outline buffer, this buffer will store the outlines of // tools and will frequently change data d->outlineVAO.create(); d->outlineVAO.bind(); glEnableVertexAttribArray(PROGRAM_VERTEX_ATTRIBUTE); // The outline buffer has a StreamDraw usage pattern, because it changes constantly d->lineBuffer.create(); d->lineBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); d->lineBuffer.bind(); glVertexAttribPointer(PROGRAM_VERTEX_ATTRIBUTE, 3, GL_FLOAT, GL_FALSE, 0, 0); } Sync::init(context()); d->canvasInitialized = true; } /** * Loads all shaders and reports compilation problems */ void KisOpenGLCanvas2::initializeShaders() { + KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); + + delete d->checkerShader; + delete d->solidColorShader; + d->checkerShader = 0; + d->solidColorShader = 0; + + try { + d->checkerShader = d->shaderLoader.loadCheckerShader(); + d->solidColorShader = d->shaderLoader.loadSolidColorShader(); + } catch (const ShaderLoaderException &e) { + reportFailedShaderCompilation(e.what()); + } + + initializeDisplayShader(); +} + +void KisOpenGLCanvas2::initializeDisplayShader() +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(!d->canvasInitialized); + bool useHiQualityFiltering = d->filterMode == KisOpenGL::HighQualityFiltering; - if (!d->canvasInitialized) { - delete d->displayShader; - delete d->checkerShader; - delete d->solidColorShader; - try { - d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); - d->checkerShader = d->shaderLoader.loadCheckerShader(); - d->solidColorShader = d->shaderLoader.loadSolidColorShader(); - } catch (const ShaderLoaderException &e) { - reportFailedShaderCompilation(e.what()); - } + delete d->displayShader; + d->displayShader = 0; + + try { + d->displayShader = d->shaderLoader.loadDisplayShader(d->displayFilter, useHiQualityFiltering); + d->displayShaderCompiledWithDisplayFilterSupport = d->displayFilter; + } catch (const ShaderLoaderException &e) { + reportFailedShaderCompilation(e.what()); } } /** * Displays a message box telling the user that * shader compilation failed and turns off OpenGL. */ void KisOpenGLCanvas2::reportFailedShaderCompilation(const QString &context) { KisConfig cfg; qDebug() << "Shader Compilation Failure: " << context; QMessageBox::critical(this, i18nc("@title:window", "Krita"), QString(i18n("Krita could not initialize the OpenGL canvas:\n\n%1\n\n Krita will disable OpenGL and close now.")).arg(context), QMessageBox::Close); cfg.setUseOpenGL(false); cfg.setCanvasState("OPENGL_FAILED"); } void KisOpenGLCanvas2::resizeGL(int width, int height) { coordinatesConverter()->setCanvasWidgetSize(QSize(width, height)); paintGL(); } void KisOpenGLCanvas2::paintGL() { if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_PAINT_STARTED"); } KisOpenglCanvasDebugger::instance()->nofityPaintRequested(); renderCanvasGL(); if (d->glSyncObject) { Sync::deleteSync(d->glSyncObject); } d->glSyncObject = Sync::getSync(); QPainter gc(this); renderDecorations(&gc); gc.end(); if (!OPENGL_SUCCESS) { KisConfig cfg; cfg.writeEntry("canvasState", "OPENGL_SUCCESS"); OPENGL_SUCCESS = true; } } void KisOpenGLCanvas2::paintToolOutline(const QPainterPath &path) { if (!d->solidColorShader->bind()) { return; } // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable(GL_COLOR_LOGIC_OP); +#ifndef Q_OS_OSX + if (d->glFn201) { + d->glFn201->glLogicOp(GL_XOR); + } +#else glLogicOp(GL_XOR); +#endif KisConfig cfg; QColor cursorColor = cfg.getCursorMainColor(); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(cursorColor.redF(), cursorColor.greenF(), cursorColor.blueF(), 1.0f)); // Paint the tool outline if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } // Convert every disjointed subpath to a polygon and draw that polygon QList subPathPolygons = path.toSubpathPolygons(); for (int i = 0; i < subPathPolygons.size(); i++) { const QPolygonF& polygon = subPathPolygons.at(i); QVector vertices; vertices.resize(polygon.count()); for (int j = 0; j < polygon.count(); j++) { QPointF p = polygon.at(j); vertices[j].setX(p.x()); vertices[j].setY(p.y()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(vertices.constData(), 3 * vertices.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices.constData()); } glDrawArrays(GL_LINE_STRIP, 0, vertices.size()); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } glDisable(GL_COLOR_LOGIC_OP); d->solidColorShader->release(); } bool KisOpenGLCanvas2::isBusy() const { const bool isBusyStatus = Sync::syncStatus(d->glSyncObject) == Sync::Unsignaled; KisOpenglCanvasDebugger::instance()->nofitySyncStatus(isBusyStatus); return isBusyStatus; } void KisOpenGLCanvas2::drawCheckers() { if (!d->checkerShader) { return; } KisCoordinatesConverter *converter = coordinatesConverter(); QTransform textureTransform; QTransform modelTransform; QRectF textureRect; QRectF modelRect; QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : converter->widgetToViewport(this->rect()); converter->getOpenGLCheckersInfo(viewportRect, &textureTransform, &modelTransform, &textureRect, &modelRect, d->scrollCheckers); textureTransform *= QTransform::fromScale(d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE, d->checkSizeScale / KisOpenGLImageTextures::BACKGROUND_TEXTURE_SIZE); if (!d->checkerShader->bind()) { qWarning() << "Could not bind checker shader"; return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix(textureTransform); d->checkerShader->setUniformValue(d->checkerShader->location(Uniform::TextureMatrix), textureMatrix); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->checkerShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->checkerShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->checkerShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } // render checkers glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, d->openGLImageTextures->checkerTexture()); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); d->checkerShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::drawGrid() { if (!d->solidColorShader->bind()) { return; } QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->solidColorShader->setUniformValue(d->solidColorShader->location(Uniform::ModelViewProjection), modelMatrix); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisConfig cfg; QColor gridColor = cfg.getPixelGridColor(); d->solidColorShader->setUniformValue( d->solidColorShader->location(Uniform::FragmentColor), QVector4D(gridColor.redF(), gridColor.greenF(), gridColor.blueF(), 0.5f)); if (KisOpenGL::hasOpenGL3()) { d->outlineVAO.bind(); d->lineBuffer.bind(); } QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { wr &= d->openGLImageTextures->storedImageBounds(); } QPoint topLeftCorner = wr.topLeft(); QPoint bottomRightCorner = wr.bottomRight() + QPoint(1, 1); QVector grid; for (int i = topLeftCorner.x(); i <= bottomRightCorner.x(); ++i) { grid.append(QVector3D(i, topLeftCorner.y(), 0)); grid.append(QVector3D(i, bottomRightCorner.y(), 0)); } for (int i = topLeftCorner.y(); i <= bottomRightCorner.y(); ++i) { grid.append(QVector3D(topLeftCorner.x(), i, 0)); grid.append(QVector3D(bottomRightCorner.x(), i, 0)); } if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.allocate(grid.constData(), 3 * grid.size() * sizeof(float)); } else { d->solidColorShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->solidColorShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, grid.constData()); } glDrawArrays(GL_LINES, 0, grid.size()); if (KisOpenGL::hasOpenGL3()) { d->lineBuffer.release(); d->outlineVAO.release(); } d->solidColorShader->release(); } void KisOpenGLCanvas2::drawImage() { if (!d->displayShader) { return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); KisCoordinatesConverter *converter = coordinatesConverter(); d->displayShader->bind(); QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); modelMatrix.optimize(); modelMatrix = projectionMatrix * modelMatrix; d->displayShader->setUniformValue(d->displayShader->location(Uniform::ModelViewProjection), modelMatrix); QMatrix4x4 textureMatrix; textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); QRectF widgetRect(0,0, width(), height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); qreal scaleX, scaleY; converter->imageScale(&scaleX, &scaleY); d->displayShader->setUniformValue(d->displayShader->location(Uniform::ViewportScale), (GLfloat) scaleX); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TexelSize), (GLfloat) d->openGLImageTextures->texelSize()); QRect ir = d->openGLImageTextures->storedImageBounds(); QRect wr = widgetRectInImagePixels.toAlignedRect(); if (!d->wrapAroundMode) { // if we don't want to paint wrapping images, just limit the // processing area, and the code will handle all the rest wr &= ir; } int firstColumn = d->xToColWithWrapCompensation(wr.left(), ir); int lastColumn = d->xToColWithWrapCompensation(wr.right(), ir); int firstRow = d->yToRowWithWrapCompensation(wr.top(), ir); int lastRow = d->yToRowWithWrapCompensation(wr.bottom(), ir); int minColumn = d->openGLImageTextures->xToCol(ir.left()); int maxColumn = d->openGLImageTextures->xToCol(ir.right()); int minRow = d->openGLImageTextures->yToRow(ir.top()); int maxRow = d->openGLImageTextures->yToRow(ir.bottom()); int imageColumns = maxColumn - minColumn + 1; int imageRows = maxRow - minRow + 1; for (int col = firstColumn; col <= lastColumn; col++) { for (int row = firstRow; row <= lastRow; row++) { int effectiveCol = col; int effectiveRow = row; QPointF tileWrappingTranslation; if (effectiveCol > maxColumn || effectiveCol < minColumn) { int translationStep = floor(qreal(col) / imageColumns); int originCol = translationStep * imageColumns; effectiveCol = col - originCol; tileWrappingTranslation.rx() = translationStep * ir.width(); } if (effectiveRow > maxRow || effectiveRow < minRow) { int translationStep = floor(qreal(row) / imageRows); int originRow = translationStep * imageRows; effectiveRow = row - originRow; tileWrappingTranslation.ry() = translationStep * ir.height(); } KisTextureTile *tile = d->openGLImageTextures->getTextureTileCR(effectiveCol, effectiveRow); if (!tile) { warnUI << "OpenGL: Trying to paint texture tile but it has not been created yet."; continue; } /* * We create a float rect here to workaround Qt's * "history reasons" in calculation of right() * and bottom() coordinates of integer rects. */ QRectF textureRect(tile->tileRectInTexturePixels()); QRectF modelRect(tile->tileRectInImagePixels().translated(tileWrappingTranslation.x(), tileWrappingTranslation.y())); //Setup the geometry for rendering if (KisOpenGL::hasOpenGL3()) { rectToVertices(d->vertices, modelRect); d->quadBuffers[0].bind(); d->quadBuffers[0].write(0, d->vertices, 3 * 6 * sizeof(float)); rectToTexCoords(d->texCoords, textureRect); d->quadBuffers[1].bind(); d->quadBuffers[1].write(0, d->texCoords, 2 * 6 * sizeof(float)); } else { rectToVertices(d->vertices, modelRect); d->displayShader->enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, d->vertices); rectToTexCoords(d->texCoords, textureRect); d->displayShader->enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); d->displayShader->setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, d->texCoords); } if (d->displayFilter) { glActiveTexture(GL_TEXTURE0 + 1); glBindTexture(GL_TEXTURE_3D, d->displayFilter->lutTexture()); d->displayShader->setUniformValue(d->displayShader->location(Uniform::Texture1), 1); } int currentLodPlane = tile->currentLodPlane(); if (d->displayShader->location(Uniform::FixedLodLevel) >= 0) { d->displayShader->setUniformValue(d->displayShader->location(Uniform::FixedLodLevel), (GLfloat) currentLodPlane); } glActiveTexture(GL_TEXTURE0); tile->bindToActiveTexture(); if (currentLodPlane > 0) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else if (SCALE_MORE_OR_EQUAL_TO(scaleX, scaleY, 2.0)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); switch(d->filterMode) { case KisOpenGL::NearestFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); break; case KisOpenGL::BilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); break; case KisOpenGL::TrilinearFilterMode: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); break; case KisOpenGL::HighQualityFiltering: if (SCALE_LESS_THAN(scaleX, scaleY, 0.5)) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } break; } } glDrawArrays(GL_TRIANGLES, 0, 6); } } glBindTexture(GL_TEXTURE_2D, 0); d->displayShader->release(); glBindBuffer(GL_ARRAY_BUFFER, 0); } void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg; d->checkSizeScale = KisOpenGLImageTextures::BACKGROUND_TEXTURE_CHECK_SIZE / static_cast(cfg.checkSize()); d->scrollCheckers = cfg.scrollCheckers(); d->openGLImageTextures->generateCheckerTexture(createCheckersImage(cfg.checkSize())); d->openGLImageTextures->updateConfig(cfg.useOpenGLTextureBuffer(), cfg.numMipmapLevels()); d->filterMode = (KisOpenGL::FilterMode) cfg.openGLFilteringMode(); notifyConfigChanged(); } QVariant KisOpenGLCanvas2::inputMethodQuery(Qt::InputMethodQuery query) const { return processInputMethodQuery(query); } void KisOpenGLCanvas2::inputMethodEvent(QInputMethodEvent *event) { processInputMethodEvent(event); } void KisOpenGLCanvas2::renderCanvasGL() { // Draw the border (that is, clear the whole widget to the border color) QColor widgetBackgroundColor = borderColor(); glClearColor(widgetBackgroundColor.redF(), widgetBackgroundColor.greenF(), widgetBackgroundColor.blueF(), 1.0); glClear(GL_COLOR_BUFFER_BIT); - if (d->displayFilter) { - d->displayFilter->updateShader(); + if ((d->displayFilter && d->displayFilter->updateShader()) || + (bool(d->displayFilter) != d->displayShaderCompiledWithDisplayFilterSupport)) { + + KIS_SAFE_ASSERT_RECOVER_NOOP(d->canvasInitialized); + + d->canvasInitialized = false; // TODO: check if actually needed? + initializeDisplayShader(); + d->canvasInitialized = true; } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.bind(); } drawCheckers(); drawImage(); KisConfig cfg; if ((coordinatesConverter()->effectiveZoom() > cfg.getPixelGridDrawingThreshold() - 0.00001) && cfg.pixelGridEnabled()) { drawGrid(); } if (KisOpenGL::hasOpenGL3()) { d->quadVAO.release(); } } void KisOpenGLCanvas2::renderDecorations(QPainter *painter) { QRect boundingRect = coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect(); drawDecorations(*painter, boundingRect); } void KisOpenGLCanvas2::setDisplayProfile(KisDisplayColorConverter *colorConverter) { d->openGLImageTextures->setMonitorProfile(colorConverter->monitorProfile(), colorConverter->renderingIntent(), colorConverter->conversionFlags()); } void KisOpenGLCanvas2::channelSelectionChanged(const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); } void KisOpenGLCanvas2::finishResizingImage(qint32 w, qint32 h) { if (d->canvasInitialized) { d->openGLImageTextures->slotImageSizeChanged(w, h); } } KisUpdateInfoSP KisOpenGLCanvas2::startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) { d->openGLImageTextures->setChannelFlags(channelFlags); if (canvas()->proofingConfigUpdated()) { d->openGLImageTextures->setProofingConfig(canvas()->proofingConfiguration()); canvas()->setProofingConfigUpdated(false); } return d->openGLImageTextures->updateCache(rc); } QRect KisOpenGLCanvas2::updateCanvasProjection(KisUpdateInfoSP info) { // See KisQPainterCanvas::updateCanvasProjection for more info bool isOpenGLUpdateInfo = dynamic_cast(info.data()); if (isOpenGLUpdateInfo) { d->openGLImageTextures->recalculateCache(info); } #ifdef Q_OS_OSX /** * There is a bug on OSX: if we issue frame redraw before the tiles finished * uploading, the tiles will become corrupted. Depending on the GPU/driver * version either the tile itself, or its mipmaps will become totally * transparent. */ glFinish(); #endif return QRect(); // FIXME: Implement dirty rect for OpenGL } bool KisOpenGLCanvas2::callFocusNextPrevChild(bool next) { return focusNextPrevChild(next); } KisOpenGLImageTexturesSP KisOpenGLCanvas2::openGLImageTextures() const { return d->openGLImageTextures; } diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h index 88f6aa7458..4cb092c0db 100644 --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -1,122 +1,127 @@ /* * Copyright (C) Boudewijn Rempt , (C) 2006 * Copyright (C) Michael Abrahams , (C) 2015 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_OPENGL_CANVAS_2_H #define KIS_OPENGL_CANVAS_2_H #include -#include +#ifndef Q_OS_OSX +#include +#else #include +#endif #include "canvas/kis_canvas_widget_base.h" #include "opengl/kis_opengl_image_textures.h" #include "kritaui_export.h" #include "kis_ui_types.h" class KisCanvas2; class KisDisplayColorConverter; class QOpenGLShaderProgram; class QPainterPath; #ifndef Q_MOC_RUN -#ifndef Q_OS_MAC -#define GLFunctions QOpenGLFunctions_3_0 +#ifndef Q_OS_OSX +#define GLFunctions QOpenGLFunctions #else #define GLFunctions QOpenGLFunctions_3_2_Core #endif #endif /** * KisOpenGLCanvas is the widget that shows the actual image using OpenGL * * NOTE: if you change something in the event handling here, also change it * in the qpainter canvas. * */ class KRITAUI_EXPORT KisOpenGLCanvas2 : public QOpenGLWidget #ifndef Q_MOC_RUN , protected GLFunctions #endif , public KisCanvasWidgetBase { Q_OBJECT public: KisOpenGLCanvas2(KisCanvas2 *canvas, KisCoordinatesConverter *coordinatesConverter, QWidget *parent, KisImageWSP image, KisDisplayColorConverter *colorConverter); ~KisOpenGLCanvas2() override; public: // QOpenGLWidget void resizeGL(int width, int height) override; void initializeGL() override; void paintGL() override; QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; void inputMethodEvent(QInputMethodEvent *event) override; public: void renderCanvasGL(); void renderDecorations(QPainter *painter); void paintToolOutline(const QPainterPath &path); bool needsFpsDebugging() const; public: // Implement kis_abstract_canvas_widget interface - void initializeShaders(); void setDisplayFilter(QSharedPointer displayFilter) override; void setWrapAroundViewingMode(bool value) override; void channelSelectionChanged(const QBitArray &channelFlags) override; void setDisplayProfile(KisDisplayColorConverter *colorConverter) override; void finishResizingImage(qint32 w, qint32 h) override; KisUpdateInfoSP startUpdateCanvasProjection(const QRect & rc, const QBitArray &channelFlags) override; QRect updateCanvasProjection(KisUpdateInfoSP info) override; QWidget *widget() override { return this; } bool isBusy() const override; void setDisplayFilterImpl(QSharedPointer displayFilter, bool initializing); KisOpenGLImageTexturesSP openGLImageTextures() const; private Q_SLOTS: void slotConfigChanged(); protected: // KisCanvasWidgetBase bool callFocusNextPrevChild(bool next) override; private: + void initializeShaders(); + void initializeDisplayShader(); + void reportFailedShaderCompilation(const QString &context); void drawImage(); void drawCheckers(); void drawGrid(); private: struct Private; Private * const d; }; #endif // KIS_OPENGL_CANVAS_2_H diff --git a/libs/ui/widgets/squeezedcombobox.cpp b/libs/ui/widgets/squeezedcombobox.cpp index c5185785ce..c42a37035d 100644 --- a/libs/ui/widgets/squeezedcombobox.cpp +++ b/libs/ui/widgets/squeezedcombobox.cpp @@ -1,148 +1,162 @@ /* ============================================================ * Author: Tom Albers * Date : 2005-01-01 * Description : * * Copyright 2005 by Tom Albers * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option)* any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "widgets/squeezedcombobox.h" /** @file squeezedcombobox.cpp */ // Qt includes. #include #include #include #include #include #include #include SqueezedComboBox::SqueezedComboBox(QWidget *parent, const char *name) : QComboBox(parent) { setObjectName(name); setMinimumWidth(100); m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimeOut())); } SqueezedComboBox::~SqueezedComboBox() { delete m_timer; } bool SqueezedComboBox::contains(const QString& _text) const { if (_text.isEmpty()) return false; for (QMap::const_iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { if (it.value() == _text) { return true; } } return false; } qint32 SqueezedComboBox::findOriginalText(const QString& text) const { for(int i = 0; i < m_originalItems.size(); i++) { if(m_originalItems.value(i) == text) { return i; } } return -1; } +QStringList SqueezedComboBox::originalTexts() const +{ + return m_originalItems.values(); +} + +void SqueezedComboBox::resetOriginalTexts(const QStringList &texts) +{ + if (texts == m_originalItems.values()) return; + + clear(); + Q_FOREACH (const QString &item, texts) { + addSqueezedItem(item); + } +} QSize SqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18; int maxH = qMax(fm.lineSpacing(), 14) + 2; QStyleOptionComboBox options; options.initFrom(this); return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); } void SqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem), userData); } void SqueezedComboBox::addSqueezedItem(const QString& newItem, QVariant userData) { insertSqueezedItem(newItem, count(), userData); } void SqueezedComboBox::setCurrent(const QString& itemText) { qint32 itemIndex = findOriginalText(itemText); if (itemIndex >= 0) { setCurrentIndex(itemIndex); } } void SqueezedComboBox::resizeEvent(QResizeEvent *) { m_timer->start(200); } void SqueezedComboBox::slotTimeOut() { for (QMap::iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { setItemText(it.key(), squeezeText(it.value())); } } QString SqueezedComboBox::squeezeText(const QString& original) { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = width() - 30; QFontMetrics fm(fontMetrics()); // If we can fit the full text, return that. if (fm.width(original) < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize - fm.width("..."); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.width(original.right(i)) > widgetSize) { sqItem = QString("..." + original.right(--i)); break; } } return sqItem; } QString SqueezedComboBox::itemHighlighted() { int curItem = currentIndex(); return m_originalItems[curItem]; } diff --git a/libs/ui/widgets/squeezedcombobox.h b/libs/ui/widgets/squeezedcombobox.h index 20df604548..648755b2d3 100644 --- a/libs/ui/widgets/squeezedcombobox.h +++ b/libs/ui/widgets/squeezedcombobox.h @@ -1,135 +1,146 @@ /* ============================================================ * Author: Tom Albers * Date : 2005-01-01 * Description : * * Copyright 2005 by Tom Albers * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ /** @file widgets/squeezedcombobox.h */ #ifndef SQUEEZEDCOMBOBOX_H #define SQUEEZEDCOMBOBOX_H class QTimer; class QResizeEvent; class QWidget; // Qt includes. #include #include #include /** @class SqueezedComboBox * * This widget is a QComboBox, but then a little bit * different. It only shows the right part of the items * depending on de size of the widget. When it is not * possible to show the complete item, it will be shortened * and "..." will be prepended. * * @image html squeezedcombobox.png "This is how it looks" * @author Tom Albers */ class KRITAUI_EXPORT SqueezedComboBox : public QComboBox { Q_OBJECT public: /** * Constructor * @param parent parent widget * @param name name to give to the widget */ SqueezedComboBox(QWidget *parent = 0, const char *name = 0); /** * destructor */ ~SqueezedComboBox() override; /** * * Returns true if the combobox contains the original (not-squeezed) * version of text. * @param text the original (not-squeezed) text to check for */ bool contains(const QString & text) const; /** * * Returns index of a orinal text, -1 if the text isn't found * @param text the original (not-squeezed) text to search for */ qint32 findOriginalText(const QString & text) const; + + /** + * Return the list of original text items + */ + QStringList originalTexts() const; + + /** + * Reset the combo box and initialize it with the list of (original) text items + */ + void resetOriginalTexts(const QStringList &texts); + /** * This inserts a item to the list. See QComboBox::insertItem() * for detaills. Please do not use QComboBox::insertItem() to this * widget, as that will fail. * @param newItem the original (long version) of the item which needs * to be added to the combobox * @param index the position in the widget. */ void insertSqueezedItem(const QString& newItem, int index, QVariant userData = QVariant()); /** * Append an item. * @param newItem the original (long version) of the item which needs * to be added to the combobox */ void addSqueezedItem(const QString& newItem, QVariant userData = QVariant()); /** * Set the current item to the one matching the given text. * * @param itemText the original (long version) of the item text */ void setCurrent(const QString& itemText); /** * This method returns the full text (not squeezed) of the currently * highlighted item. * @return full text of the highlighted item */ QString itemHighlighted(); /** * Sets the sizeHint() of this widget. */ QSize sizeHint() const override; private Q_SLOTS: void slotTimeOut(); private: void resizeEvent(QResizeEvent *) override; QString squeezeText(const QString& original); // Prevent these from being used. void setCurrentText(const QString& itemText); void insertItem(const QString &text); void insertItem(qint32 index, const QString &text); void addItem(const QString &text); QMap m_originalItems; QTimer* m_timer; }; #endif // SQUEEZEDCOMBOBOX_H diff --git a/libs/vectorimage/VectorImageDebug.cpp b/libs/vectorimage/VectorImageDebug.cpp index 771c549f6a..bc780b12e7 100644 --- a/libs/vectorimage/VectorImageDebug.cpp +++ b/libs/vectorimage/VectorImageDebug.cpp @@ -1,26 +1,26 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "VectorImageDebug.h" const QLoggingCategory &VECTOR_IMAGE_LOG() \ { - static const QLoggingCategory category("krita.lib.vectorimage"); + static const QLoggingCategory category("krita.lib.vectorimage", QtInfoMsg); return category; } diff --git a/libs/widgets/WidgetsDebug.cpp b/libs/widgets/WidgetsDebug.cpp index 878892daa4..905436231c 100644 --- a/libs/widgets/WidgetsDebug.cpp +++ b/libs/widgets/WidgetsDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "WidgetsDebug.h" const QLoggingCategory &WIDGETS_LOG() \ { - static const QLoggingCategory category("krita.lib.widgets"); + static const QLoggingCategory category("krita.lib.widgets", QtInfoMsg); return category; } diff --git a/libs/widgetutils/WidgetUtilsDebug.cpp b/libs/widgetutils/WidgetUtilsDebug.cpp index ee4dd7f7ea..58a6288569 100644 --- a/libs/widgetutils/WidgetUtilsDebug.cpp +++ b/libs/widgetutils/WidgetUtilsDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "WidgetUtilsDebug.h" const QLoggingCategory &KRITAWIDGETUTILS_LOG() { - static const QLoggingCategory category("krita.lib.widgetutils"); + static const QLoggingCategory category("krita.lib.widgetutils", QtInfoMsg); return category; } diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc index 4740a69f4e..70894493a7 100644 --- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc +++ b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.cc @@ -1,914 +1,913 @@ - /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include KisRulerAssistantTool::KisRulerAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0), m_handleSize(32), m_handleHalfSize(16) { Q_ASSERT(m_canvas); setObjectName("tool_rulerassistanttool"); } KisRulerAssistantTool::~KisRulerAssistantTool() { } QPointF adjustPointF(const QPointF& _pt, const QRectF& _rc) { return QPointF(qBound(_rc.left(), _pt.x(), _rc.right()), qBound(_rc.top(), _pt.y(), _rc.bottom())); } void KisRulerAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { // Add code here to initialize your tool when it got activated KisTool::activate(toolActivation, shapes); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->setVisible(true); m_canvas->updateCanvas(); m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; } void KisRulerAssistantTool::deactivate() { // Add code here to initialize your tool when it got deactivated m_canvas->updateCanvas(); KisTool::deactivate(); } bool KisRulerAssistantTool::mouseNear(const QPointF& mousep, const QPointF& point) { QRectF handlerect(point-QPointF(m_handleHalfSize,m_handleHalfSize), QSizeF(m_handleSize, m_handleSize)); return handlerect.contains(mousep); } KisPaintingAssistantHandleSP KisRulerAssistantTool::nodeNearPoint(KisPaintingAssistantSP grid, QPointF point) { if (mouseNear(point, pixelToView(*grid->topLeft()))) { return grid->topLeft(); } else if (mouseNear(point, pixelToView(*grid->topRight()))) { return grid->topRight(); } else if (mouseNear(point, pixelToView(*grid->bottomLeft()))) { return grid->bottomLeft(); } else if (mouseNear(point, pixelToView(*grid->bottomRight()))) { return grid->bottomRight(); } return 0; } inline double norm2(const QPointF& p) { return p.x() * p.x() + p.y() * p.y(); } void KisRulerAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); bool newAssistantAllowed = true; if (m_newAssistant) { m_internalMode = MODE_CREATION; *m_newAssistant->handles().back() = snapToGuide(event, QPointF(), false); if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; } } if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed if (m_handleDrag == assistant->topLeft()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topLeft()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->bottomLeft()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topRight()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->bottomRight()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(m_selectedNode1); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(assistant->topRight()); m_newAssistant->addHandle(assistant->topLeft()); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->bottomLeft()); m_newAssistant->addHandle(assistant->bottomRight()); m_newAssistant->addHandle(m_selectedNode2); m_newAssistant->addHandle(m_selectedNode1); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || assistant->id() == "concentric ellipse" || assistant->id() == "fisheye-point")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->buttonPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->buttonPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // This code contains the click event behavior. The actual display of the icons are done at the bottom // of the paint even. Make sure the rectangles positions are the same between the two. // TODO: These 6 lines are duplicated below in the paint layer. It shouldn't be done like this. QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset)); QRectF deleteRect(iconDeletePosition, QSizeF(16, 16)); QRectF visibleRect(iconSnapPosition, QSizeF(16, 16)); QRectF moveRect(iconMovePosition, QSizeF(32, 32)); if (moveRect.contains(mousePos)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; return; } if (deleteRect.contains(mousePos)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(mousePos)) { newAssistantAllowed = false; if (assistant->snapping()==true){ snappingOff(assistant); outlineOff(assistant); } else{ snappingOn(assistant); outlineOn(assistant); } assistant->uncache();//this updates the chache of the assistant, very important. } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visiblity// QString key = m_options.comboBox->model()->index( m_options.comboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); if (m_newAssistant->numHandles() <= 1) { if (key == "vanishing point"){ m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-70,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(-140,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(70,0))); m_newAssistant->addSideHandle(new KisPaintingAssistantHandle(event->point+QPointF(140,0))); } addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(snapToGuide(event, QPointF(), false))); } } m_canvas->updateCanvas(); } void KisRulerAssistantTool::continuePrimaryAction(KoPointerEvent *event) { if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { *m_handleDrag = snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { if (handle == m_handleDrag) continue; double dist = norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { QPointF newAdjustment = snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } if (m_assistantDrag->id()== "vanishing point"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { if ((m_higlightedNode = nodeNearPoint(assistant, mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. if (m_handleDrag == assistant->sideHandles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); if (length<2.0){length=2.0;} length +=perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[1] = perspectiveline.p2(); } else if (m_handleDrag == assistant->sideHandles()[2]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){length=2.0;} length +=perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[3] = perspectiveline.p2(); } //for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ QPointF vanishingpoint(0,0); QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ *assistant->handles()[0] = vanishingpoint;} }//and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){length=2.0;} if (length2<2.0){length2=2.0;} length +=perspectiveline.length(); length2 +=perspectiveline2.length(); perspectiveline.setLength(length); perspectiveline2.setLength(length2); *assistant->sideHandles()[1] = perspectiveline.p2(); *assistant->sideHandles()[3] = perspectiveline2.p2(); } } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } void KisRulerAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if (m_assistantDrag) { m_assistantDrag.clear(); m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; m_canvas->updateCanvas(); } else { event->ignore(); } } void KisRulerAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } m_newAssistant.clear(); } void KisRulerAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } void KisRulerAssistantTool::snappingOn(KisPaintingAssistantSP assistant) { assistant->setSnapping(true); } void KisRulerAssistantTool::snappingOff(KisPaintingAssistantSP assistant) { assistant->setSnapping(false); } void KisRulerAssistantTool::outlineOn(KisPaintingAssistantSP assistant) { assistant->setOutline(true); } void KisRulerAssistantTool::outlineOff(KisPaintingAssistantSP assistant) { assistant->setOutline(false); } #include QPointF KisRulerAssistantTool::snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers) { if (!m_canvas->currentImage()) return e->point; KoSnapGuide *snapGuide = m_canvas->snapGuide(); QPointF pos = snapGuide->snap(e->point, offset, useModifiers ? e->modifiers() : Qt::NoModifier); //return m_canvas->currentImage()->documentToPixel(pos); return pos; } QPointF KisRulerAssistantTool::snapToGuide(const QPointF& pt, const QPointF &offset) { if (!m_canvas) return pt; KoSnapGuide *snapGuide = m_canvas->snapGuide(); QPointF pos = snapGuide->snap(pt, offset, Qt::NoModifier); return pos; } void KisRulerAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; m_canvas->updateCanvas(); } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { QPointF translate = event->point - m_dragEnd;; m_dragEnd = event->point; m_selectedNode1.data()->operator =(QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); m_canvas->updateCanvas(); } } void KisRulerAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { QPixmap iconDelete = KisIconUtils::loadIcon("dialog-cancel").pixmap(16, 16); QPixmap iconSnapOn = KisIconUtils::loadIcon("visible").pixmap(16, 16); QPixmap iconSnapOff = KisIconUtils::loadIcon("novisible").pixmap(16, 16); QPixmap iconMove = KisIconUtils::loadIcon("transform-move").pixmap(32, 32); QColor handlesColor(0, 0, 0, 125); if (m_newAssistant) { m_newAssistant->drawAssistant(_gc, QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())), m_canvas->coordinatesConverter(), false,m_canvas, true, false); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12))); KisPaintingAssistant::drawPath(_gc, path); } } // TODO: too many Q_FOREACH loops going through all assistants. Condense this to one to be a little more performant // Draw corner and middle perspective nodes Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { QRectF ellipse(_converter.documentToView(*handle) - QPointF(6, 6), QSizeF(12, 12)); // render handles when they are being dragged and moved if (handle == m_handleDrag || handle == m_handleCombine) { _gc.save(); _gc.setPen(Qt::transparent); _gc.setBrush(handlesColor); _gc.drawEllipse(ellipse); _gc.restore(); } if ( assistant->id() =="vanishing point") { if (assistant->handles().at(0) == handle ) { // vanishing point handle ellipse = QRectF(_converter.documentToView(*handle) - QPointF(10, 10), QSizeF(20, 20)); // TODO: change this to be smaller, but fill in with a color } //TODO: render outside handles a little bigger than rotation anchor handles } QPainterPath path; path.addEllipse(ellipse); KisPaintingAssistant::drawPath(_gc, path); } } Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // Draw middle perspective handles if(assistant->id()=="perspective") { assistant->findHandleLocation(); QPointF topMiddle, bottomMiddle, rightMiddle, leftMiddle; topMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->topRight()))*0.5; bottomMiddle = (_converter.documentToView(*assistant->bottomLeft()) + _converter.documentToView(*assistant->bottomRight()))*0.5; rightMiddle = (_converter.documentToView(*assistant->topRight()) + _converter.documentToView(*assistant->bottomRight()))*0.5; leftMiddle = (_converter.documentToView(*assistant->topLeft()) + _converter.documentToView(*assistant->bottomLeft()))*0.5; QPainterPath path; path.addEllipse(QRectF(leftMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(topMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(rightMiddle-QPointF(6,6),QSizeF(12,12))); path.addEllipse(QRectF(bottomMiddle-QPointF(6,6),QSizeF(12,12))); KisPaintingAssistant::drawPath(_gc, path); } if(assistant->id()=="vanishing point") { if (assistant->sideHandles().size() == 4) { // Draw the line QPointF p0 = _converter.documentToView(*assistant->handles()[0]); QPointF p1 = _converter.documentToView(*assistant->sideHandles()[0]); QPointF p2 = _converter.documentToView(*assistant->sideHandles()[1]); QPointF p3 = _converter.documentToView(*assistant->sideHandles()[2]); QPointF p4 = _converter.documentToView(*assistant->sideHandles()[3]); _gc.setPen(QColor(0, 0, 0, 75)); // Draw control lines QPen penStyle(QColor(120, 120, 120, 60), 2.0, Qt::DashDotDotLine); _gc.setPen(penStyle); _gc.drawLine(p0, p1); _gc.drawLine(p0, p3); _gc.drawLine(p1, p2); _gc.drawLine(p3, p4); } } } // Draw the assistant widget Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // We are going to put all of the assistant actions below the bounds of the assistant // so they are out of the way // assistant->buttonPosition() gets the center X/Y position point QPointF actionsPosition = m_canvas->viewConverter()->documentToView(assistant->buttonPosition()); QPointF iconDeletePosition(actionsPosition + QPointF(78, m_assistantHelperYOffset + 7)); QPointF iconSnapPosition(actionsPosition + QPointF(54, m_assistantHelperYOffset + 7)); QPointF iconMovePosition(actionsPosition + QPointF(15, m_assistantHelperYOffset )); // Background container for helpers QBrush backgroundColor = m_canvas->viewManager()->mainWindow()->palette().window(); QPointF actionsBGRectangle(actionsPosition + QPointF(25, m_assistantHelperYOffset)); _gc.setRenderHint(QPainter::Antialiasing); QPainterPath bgPath; bgPath.addRoundedRect(QRectF(actionsBGRectangle.x(), actionsBGRectangle.y(), 80, 30), 6, 6); QPen stroke(QColor(60, 60, 60, 80), 2); _gc.setPen(stroke); _gc.fillPath(bgPath, backgroundColor); _gc.drawPath(bgPath); QPainterPath movePath; // render circle behind by move helper _gc.setPen(stroke); movePath.addEllipse(iconMovePosition.x()-5, iconMovePosition.y()-5, 40, 40);// background behind icon _gc.fillPath(movePath, backgroundColor); _gc.drawPath(movePath); // Preview/Snap Tool helper _gc.drawPixmap(iconDeletePosition, iconDelete); if (assistant->snapping()==true) { _gc.drawPixmap(iconSnapPosition, iconSnapOn); } else { _gc.drawPixmap(iconSnapPosition, iconSnapOff); } // Move Assistant Tool helper _gc.drawPixmap(iconMovePosition, iconMove); } } void KisRulerAssistantTool::removeAllAssistants() { m_canvas->viewManager()->resourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisRulerAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point"){ //ideally we'd save and load side-handles as well, but this is all I've got// QPointF pos = *assistant->handles()[0]; assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0))); assistant->addSideHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0))); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->resourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisRulerAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QDesktopServices::storageLocation(QDesktopServices::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } QWidget *KisRulerAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_options.loadButton->setIcon(KisIconUtils::loadIcon("document-open")); m_options.saveButton->setIcon(KisIconUtils::loadIcon("document-save")); m_options.deleteButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { m_options.comboBox->addItem(id.name(), id.id()); } connect(m_options.saveButton, SIGNAL(clicked()), SLOT(saveAssistants())); connect(m_options.loadButton, SIGNAL(clicked()), SLOT(loadAssistants())); connect(m_options.deleteButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); } return m_optionsWidget; } diff --git a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h index 636a2d3757..8f00a66469 100644 --- a/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h +++ b/plugins/assistants/RulerAssistant/kis_ruler_assistant_tool.h @@ -1,128 +1,129 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_RULER_ASSISTANT_TOOL_H_ #define _KIS_RULER_ASSISTANT_TOOL_H_ #include #include #include #include #include "kis_painting_assistant.h" #include #include #include "ui_AssistantsToolOptions.h" class KisRulerAssistantTool : public KisTool { Q_OBJECT enum PerspectiveAssistantEditionMode { MODE_CREATION, // This is the mode when there is not yet a perspective grid MODE_EDITING, // This is the mode when the grid has been created, and we are waiting for the user to click on a control box MODE_DRAGGING_NODE, // In this mode one node is translated MODE_DRAGGING_TRANSLATING_TWONODES // This mode is used when creating a new sub perspective grid }; public: KisRulerAssistantTool(KoCanvasBase * canvas); ~KisRulerAssistantTool() override; virtual quint32 priority() { return 3; } void beginPrimaryAction(KoPointerEvent *event) override; void continuePrimaryAction(KoPointerEvent *event) override; void endPrimaryAction(KoPointerEvent *event) override; void mouseMoveEvent(KoPointerEvent *event) override; QWidget *createOptionWidget() override; private: void addAssistant(); void removeAssistant(KisPaintingAssistantSP assistant); void snappingOn(KisPaintingAssistantSP assistant); void snappingOff(KisPaintingAssistantSP assistant); void outlineOn(KisPaintingAssistantSP assistant); void outlineOff(KisPaintingAssistantSP assistant); bool mouseNear(const QPointF& mousep, const QPointF& point); KisPaintingAssistantHandleSP nodeNearPoint(KisPaintingAssistantSP grid, QPointF point); QPointF snapToGuide(KoPointerEvent *e, const QPointF &offset, bool useModifiers); QPointF snapToGuide(const QPointF& pt, const QPointF &offset); public Q_SLOTS: void activate(ToolActivation toolActivation, const QSet &shapes) override; void deactivate() override; private Q_SLOTS: void removeAllAssistants(); void saveAssistants(); void loadAssistants(); protected: void paint(QPainter& gc, const KoViewConverter &converter) override; protected: QPointer m_canvas; - QList m_handles, m_sideHandles; + QList m_handles; + QList m_sideHandles; KisPaintingAssistantHandleSP m_handleDrag; KisPaintingAssistantHandleSP m_handleCombine; KisPaintingAssistantSP m_assistantDrag; KisPaintingAssistantSP m_newAssistant; QPointF m_cursorStart; QPointF m_currentAdjustment; Ui::AssistantsToolOptions m_options; QWidget* m_optionsWidget; QPointF m_dragStart; QLineF m_radius; bool m_snapIsRadial; QPointF m_dragEnd; private: PerspectiveAssistantEditionMode m_internalMode; qint32 m_handleSize, m_handleHalfSize; KisPaintingAssistantHandleSP m_selectedNode1, m_selectedNode2, m_higlightedNode; int m_assistantHelperYOffset; }; class KisRulerAssistantToolFactory : public KoToolFactoryBase { public: KisRulerAssistantToolFactory() : KoToolFactoryBase("KisRulerAssistantTool") { setToolTip(i18n("Assistant Tool")); setSection(TOOL_TYPE_VIEW); setIconName(koIconNameCStr("krita_tool_ruler_assistant")); setPriority(0); setActivationShapeId(KRITA_TOOL_ACTIVATION_ID); }; ~KisRulerAssistantToolFactory() override {} KoToolBase * createTool(KoCanvasBase * canvas) override { return new KisRulerAssistantTool(canvas); } }; #endif diff --git a/plugins/color/lcms2engine/IccColorSpaceEngine.cpp b/plugins/color/lcms2engine/IccColorSpaceEngine.cpp index 336c78249f..0a92ec5a18 100644 --- a/plugins/color/lcms2engine/IccColorSpaceEngine.cpp +++ b/plugins/color/lcms2engine/IccColorSpaceEngine.cpp @@ -1,336 +1,336 @@ /* * Copyright (c) 2007-2008 Cyrille Berger * Copyright (c) 2011 Srikanth Tiyyagura * * 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 #include "LcmsColorSpace.h" // -- 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(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(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(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; } 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()) { 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 { 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(srcColorSpace->profile())->asLcms(), dstColorSpace, computeColorSpaceType(dstColorSpace), dynamic_cast(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(srcColorSpace->profile())->asLcms(), dstColorSpace, computeColorSpaceType(dstColorSpace), dynamic_cast(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(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"; + qWarning() << "Unknown 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/dockers/lut/lutdocker_dock.cpp b/plugins/dockers/lut/lutdocker_dock.cpp index 5e3e65a46d..54f158feae 100644 --- a/plugins/dockers/lut/lutdocker_dock.cpp +++ b/plugins/dockers/lut/lutdocker_dock.cpp @@ -1,617 +1,622 @@ /* * Copyright (c) 2004 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "lutdocker_dock.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include "widgets/squeezedcombobox.h" #include "kis_signals_blocker.h" #include "krita_utils.h" #include "ocio_display_filter.h" #include "black_white_point_chooser.h" OCIO::ConstConfigRcPtr defaultRawProfile() { /** * Copied from OCIO, just a noop profile */ const char * INTERNAL_RAW_PROFILE = "ocio_profile_version: 1\n" "strictparsing: false\n" "roles:\n" " default: raw\n" "displays:\n" " sRGB:\n" " - ! {name: Raw, colorspace: raw}\n" "colorspaces:\n" " - !\n" " name: raw\n" " family: raw\n" " equalitygroup:\n" " bitdepth: 32f\n" " isdata: true\n" " allocation: uniform\n" " description: 'A raw color space. Conversions to and from this space are no-ops.'\n"; std::istringstream istream; istream.str(INTERNAL_RAW_PROFILE); return OCIO::Config::CreateFromStream(istream); } LutDockerDock::LutDockerDock() : QDockWidget(i18n("LUT Management")) , m_canvas(0) , m_draggingSlider(false) { using namespace std::placeholders; // For _1 m_exposureCompressor.reset( new KisSignalCompressorWithParam(40, std::bind(&LutDockerDock::setCurrentExposureImpl, this, _1))); m_gammaCompressor.reset( new KisSignalCompressorWithParam(40, std::bind(&LutDockerDock::setCurrentGammaImpl, this, _1))); m_page = new QWidget(this); setupUi(m_page); setWidget(m_page); KisConfig cfg; m_chkUseOcio->setChecked(cfg.useOcio()); connect(m_chkUseOcio, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings())); connect(m_colorManagement, SIGNAL(currentIndexChanged(int)), SLOT(slotColorManagementModeChanged())); m_txtConfigurationPath->setText(cfg.ocioConfigurationPath()); m_bnSelectConfigurationFile->setToolTip(i18n("Select custom configuration file.")); connect(m_bnSelectConfigurationFile,SIGNAL(clicked()), SLOT(selectOcioConfiguration())); m_txtLut->setText(cfg.ocioLutPath()); m_bnSelectLut->setToolTip(i18n("Select LUT file")); connect(m_bnSelectLut, SIGNAL(clicked()), SLOT(selectLut())); connect(m_bnClearLut, SIGNAL(clicked()), SLOT(clearLut())); // See http://groups.google.com/group/ocio-dev/browse_thread/thread/ec95c5f54a74af65 -- maybe need to be reinstated // when people ask for it. m_lblLut->hide(); m_txtLut->hide(); m_bnSelectLut->hide(); m_bnClearLut->hide(); connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(refillViewCombobox())); m_exposureDoubleWidget->setToolTip(i18n("Select the exposure (stops) for HDR images.")); m_exposureDoubleWidget->setRange(-10, 10); m_exposureDoubleWidget->setPrecision(1); m_exposureDoubleWidget->setValue(0.0); m_exposureDoubleWidget->setSingleStep(0.25); m_exposureDoubleWidget->setPageStep(1); connect(m_exposureDoubleWidget, SIGNAL(valueChanged(double)), SLOT(exposureValueChanged(double))); connect(m_exposureDoubleWidget, SIGNAL(sliderPressed()), SLOT(exposureSliderPressed())); connect(m_exposureDoubleWidget, SIGNAL(sliderReleased()), SLOT(exposureSliderReleased())); // Gamma needs to be exponential (gamma *= 1.1f, gamma /= 1.1f as steps) m_gammaDoubleWidget->setToolTip(i18n("Select the amount of gamma modification for display. This does not affect the pixels of your image.")); m_gammaDoubleWidget->setRange(0.1, 5); m_gammaDoubleWidget->setPrecision(2); m_gammaDoubleWidget->setValue(1.0); m_gammaDoubleWidget->setSingleStep(0.1); m_gammaDoubleWidget->setPageStep(1); connect(m_gammaDoubleWidget, SIGNAL(valueChanged(double)), SLOT(gammaValueChanged(double))); connect(m_gammaDoubleWidget, SIGNAL(sliderPressed()), SLOT(gammaSliderPressed())); connect(m_gammaDoubleWidget, SIGNAL(sliderReleased()), SLOT(gammaSliderReleased())); m_bwPointChooser = new BlackWhitePointChooser(this); connect(m_bwPointChooser, SIGNAL(sigBlackPointChanged(qreal)), SLOT(updateDisplaySettings())); connect(m_bwPointChooser, SIGNAL(sigWhitePointChanged(qreal)), SLOT(updateDisplaySettings())); connect(m_btnConvertCurrentColor, SIGNAL(toggled(bool)), SLOT(updateDisplaySettings())); connect(m_btmShowBWConfiguration, SIGNAL(clicked()), SLOT(slotShowBWConfiguration())); slotUpdateIcons(); connect(m_cmbInputColorSpace, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbDisplayDevice, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbView, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbLook, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); connect(m_cmbComponents, SIGNAL(currentIndexChanged(int)), SLOT(updateDisplaySettings())); m_draggingSlider = false; connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(resetOcioConfiguration())); resetOcioConfiguration(); } LutDockerDock::~LutDockerDock() { } void LutDockerDock::setCanvas(KoCanvasBase* _canvas) { if (m_canvas) { m_canvas->disconnect(this); } setEnabled(_canvas != 0); if (KisCanvas2* canvas = dynamic_cast(_canvas)) { m_canvas = canvas; if (m_canvas) { if (!m_canvas->displayFilter()) { - m_displayFilter = QSharedPointer(new OcioDisplayFilter(this)); - m_canvas->setDisplayFilter(m_displayFilter); resetOcioConfiguration(); updateDisplaySettings(); } else { m_displayFilter = m_canvas->displayFilter(); OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); Q_ASSERT(displayFilter); m_ocioConfig = displayFilter->config; KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget); m_exposureDoubleWidget->setValue(displayFilter->exposure); KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget); m_gammaDoubleWidget->setValue(displayFilter->gamma); KisSignalsBlocker componentsBlocker(m_cmbComponents); m_cmbComponents->setCurrentIndex((int)displayFilter->swizzle); KisSignalsBlocker bwBlocker(m_bwPointChooser); m_bwPointChooser->setBlackPoint(displayFilter->blackPoint); m_bwPointChooser->setWhitePoint(displayFilter->whitePoint); } connect(m_canvas->image(), SIGNAL(sigColorSpaceChanged(const KoColorSpace*)), SLOT(slotImageColorSpaceChanged()), Qt::UniqueConnection); connect(m_canvas->viewManager()->mainWindow(), SIGNAL(themeChanged()), SLOT(slotUpdateIcons()), Qt::UniqueConnection); } } } void LutDockerDock::unsetCanvas() { m_canvas = 0; setEnabled(false); m_displayFilter = QSharedPointer(0); } void LutDockerDock::slotUpdateIcons() { m_btnConvertCurrentColor->setIcon(KisIconUtils::loadIcon("krita_tool_freehand")); m_btmShowBWConfiguration->setIcon(KisIconUtils::loadIcon("properties")); } void LutDockerDock::slotShowBWConfiguration() { m_bwPointChooser->showPopup(m_btmShowBWConfiguration->mapToGlobal(QPoint())); } bool LutDockerDock::canChangeExposureAndGamma() const { return m_chkUseOcio->isChecked() && m_ocioConfig; } qreal LutDockerDock::currentExposure() const { if (!m_displayFilter) return 0.0; OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); return canChangeExposureAndGamma() ? displayFilter->exposure : 0.0; } void LutDockerDock::setCurrentExposure(qreal value) { if (!canChangeExposureAndGamma()) return; m_exposureCompressor->start(value); } qreal LutDockerDock::currentGamma() const { if (!m_displayFilter) return 1.0; OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); return canChangeExposureAndGamma() ? displayFilter->gamma : 1.0; } void LutDockerDock::setCurrentGamma(qreal value) { if (!canChangeExposureAndGamma()) return; m_gammaCompressor->start(value); } void LutDockerDock::setCurrentExposureImpl(qreal value) { m_exposureDoubleWidget->setValue(value); if (!m_canvas) return; m_canvas->viewManager()->showFloatingMessage( i18nc("floating message about exposure", "Exposure: %1", KritaUtils::prettyFormatReal(m_exposureDoubleWidget->value())), QIcon(), 500, KisFloatingMessage::Low); } void LutDockerDock::setCurrentGammaImpl(qreal value) { m_gammaDoubleWidget->setValue(value); if (!m_canvas) return; m_canvas->viewManager()->showFloatingMessage( i18nc("floating message about gamma", "Gamma: %1", KritaUtils::prettyFormatReal(m_gammaDoubleWidget->value())), QIcon(), 500, KisFloatingMessage::Low); } void LutDockerDock::slotImageColorSpaceChanged() { enableControls(); writeControls(); resetOcioConfiguration(); } void LutDockerDock::exposureValueChanged(double exposure) { if (m_canvas && !m_draggingSlider) { m_canvas->viewManager()->resourceProvider()->setHDRExposure(exposure); updateDisplaySettings(); } } void LutDockerDock::exposureSliderPressed() { m_draggingSlider = true; } void LutDockerDock::exposureSliderReleased() { m_draggingSlider = false; exposureValueChanged(m_exposureDoubleWidget->value()); } void LutDockerDock::gammaValueChanged(double gamma) { if (m_canvas && !m_draggingSlider) { m_canvas->viewManager()->resourceProvider()->setHDRGamma(gamma); updateDisplaySettings(); } } void LutDockerDock::gammaSliderPressed() { m_draggingSlider = true; } void LutDockerDock::gammaSliderReleased() { m_draggingSlider = false; gammaValueChanged(m_gammaDoubleWidget->value()); } void LutDockerDock::enableControls() { bool canDoExternalColorCorrection = false; if (m_canvas) { KisImageSP image = m_canvas->viewManager()->image(); canDoExternalColorCorrection = image->colorSpace()->colorModelId() == RGBAColorModelID; } if (!canDoExternalColorCorrection) { KisSignalsBlocker colorManagementBlocker(m_colorManagement); Q_UNUSED(colorManagementBlocker); m_colorManagement->setCurrentIndex((int) KisConfig::INTERNAL); } bool ocioEnabled = m_chkUseOcio->isChecked(); m_colorManagement->setEnabled(ocioEnabled && canDoExternalColorCorrection); bool externalColorManagementEnabled = m_colorManagement->currentIndex() != (int)KisConfig::INTERNAL; m_lblInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbInputColorSpace->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbDisplayDevice->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblView->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbView->setEnabled(ocioEnabled && externalColorManagementEnabled); m_lblLook->setEnabled(ocioEnabled && externalColorManagementEnabled); m_cmbLook->setEnabled(ocioEnabled && externalColorManagementEnabled); bool enableConfigPath = m_colorManagement->currentIndex() == (int) KisConfig::OCIO_CONFIG; lblConfig->setEnabled(ocioEnabled && enableConfigPath); m_txtConfigurationPath->setEnabled(ocioEnabled && enableConfigPath); m_bnSelectConfigurationFile->setEnabled(ocioEnabled && enableConfigPath); } void LutDockerDock::updateDisplaySettings() { if (!m_canvas || !m_canvas->viewManager() || !m_canvas->viewManager()->image()) { return; } enableControls(); writeControls(); if (m_chkUseOcio->isChecked() && m_ocioConfig) { + KIS_SAFE_ASSERT_RECOVER_NOOP(!m_canvas->displayFilter() || + m_canvas->displayFilter() == m_displayFilter); + + if (!m_displayFilter) { + m_displayFilter = + m_canvas->displayFilter() ? + m_canvas->displayFilter() : + QSharedPointer(new OcioDisplayFilter(this)); + } + OcioDisplayFilter *displayFilter = qobject_cast(m_displayFilter.data()); displayFilter->config = m_ocioConfig; displayFilter->inputColorSpaceName = m_ocioConfig->getColorSpaceNameByIndex(m_cmbInputColorSpace->currentIndex()); displayFilter->displayDevice = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex()); displayFilter->view = m_ocioConfig->getView(displayFilter->displayDevice, m_cmbView->currentIndex()); displayFilter->look = m_ocioConfig->getLookNameByIndex(m_cmbLook->currentIndex()); displayFilter->gamma = m_gammaDoubleWidget->value(); displayFilter->exposure = m_exposureDoubleWidget->value(); displayFilter->swizzle = (OCIO_CHANNEL_SWIZZLE)m_cmbComponents->currentIndex(); displayFilter->blackPoint = m_bwPointChooser->blackPoint(); displayFilter->whitePoint = m_bwPointChooser->whitePoint(); displayFilter->forceInternalColorManagement = m_colorManagement->currentIndex() == (int)KisConfig::INTERNAL; displayFilter->setLockCurrentColorVisualRepresentation(m_btnConvertCurrentColor->isChecked()); displayFilter->updateProcessor(); m_canvas->setDisplayFilter(m_displayFilter); } else { m_canvas->setDisplayFilter(QSharedPointer(0)); } m_canvas->updateCanvas(); } void LutDockerDock::writeControls() { KisConfig cfg; cfg.setUseOcio(m_chkUseOcio->isChecked()); cfg.setOcioColorManagementMode((KisConfig::OcioColorManagementMode) m_colorManagement->currentIndex()); cfg.setOcioLockColorVisualRepresentation(m_btnConvertCurrentColor->isChecked()); } void LutDockerDock::slotColorManagementModeChanged() { enableControls(); writeControls(); resetOcioConfiguration(); } void LutDockerDock::selectOcioConfiguration() { QString filename = m_txtConfigurationPath->text(); KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker"); dialog.setCaption(i18n("Select OpenColorIO Configuration")); dialog.setDefaultDir(QDir::cleanPath(filename)); dialog.setMimeTypeFilters(QStringList() << "application/x-opencolorio-configuration"); filename = dialog.filename(); QFile f(filename); if (f.exists()) { m_txtConfigurationPath->setText(filename); KisConfig cfg; cfg.setOcioConfigurationPath(filename); writeControls(); resetOcioConfiguration(); } } void LutDockerDock::resetOcioConfiguration() { KisConfig cfg; - if (cfg.ocioColorManagementMode() == m_colorManagement->currentIndex() - && cfg.useOcio() == m_chkUseOcio->isChecked() - && cfg.ocioLockColorVisualRepresentation() == m_btnConvertCurrentColor->isChecked() - && cfg.ocioConfigurationPath() == m_txtConfigurationPath->text() - ) { - return; - } - - m_ocioConfig.reset(); - - if (cfg.ocioColorManagementMode() == m_colorManagement->currentIndex() - && cfg.useOcio() == m_chkUseOcio->isChecked() - && cfg.ocioLockColorVisualRepresentation() == m_btnConvertCurrentColor->isChecked() - && cfg.ocioConfigurationPath() == m_txtConfigurationPath->text() - ) { - return; - } - m_ocioConfig.reset(); try { if (cfg.ocioColorManagementMode() == KisConfig::INTERNAL) { m_ocioConfig = defaultRawProfile(); } else if (cfg.ocioColorManagementMode() == KisConfig::OCIO_ENVIRONMENT) { m_ocioConfig = OCIO::Config::CreateFromEnv(); } else if (cfg.ocioColorManagementMode() == KisConfig::OCIO_CONFIG) { QString configFile = cfg.ocioConfigurationPath(); if (QFile::exists(configFile)) { m_ocioConfig = OCIO::Config::CreateFromFile(configFile.toUtf8()); } else { m_ocioConfig = defaultRawProfile(); } } if (m_ocioConfig) { OCIO::SetCurrentConfig(m_ocioConfig); } } catch (OCIO::Exception &exception) { dbgKrita << "OpenColorIO Error:" << exception.what() << "Cannot create the LUT docker"; } if (m_ocioConfig) { refillControls(); } } void LutDockerDock::refillControls() { if (!m_canvas) return; if (!m_canvas->viewManager()) return; if (!m_canvas->viewManager()->resourceProvider()) return; if (!m_canvas->viewManager()->image()) return; KIS_ASSERT_RECOVER_RETURN(m_ocioConfig); { // Color Management Mode KisConfig cfg; KisSignalsBlocker modeBlocker(m_colorManagement); m_colorManagement->setCurrentIndex((int) cfg.ocioColorManagementMode()); } { // Exposure KisSignalsBlocker exposureBlocker(m_exposureDoubleWidget); m_exposureDoubleWidget->setValue(m_canvas->viewManager()->resourceProvider()->HDRExposure()); } { // Gamma KisSignalsBlocker gammaBlocker(m_gammaDoubleWidget); m_gammaDoubleWidget->setValue(m_canvas->viewManager()->resourceProvider()->HDRGamma()); } { // Components const KoColorSpace *cs = m_canvas->viewManager()->image()->colorSpace(); - KisSignalsBlocker componentsBlocker(m_cmbComponents); - m_cmbComponents->clear(); - m_cmbComponents->addSqueezedItem(i18n("Luminance")); - m_cmbComponents->addSqueezedItem(i18n("All Channels")); + QStringList itemsList; + itemsList << i18n("Luminance"); + itemsList << i18n("All Channels"); Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(cs->channels())) { - m_cmbComponents->addSqueezedItem(channel->name()); + itemsList << channel->name(); + } + + if (m_cmbComponents->originalTexts() != itemsList) { + KisSignalsBlocker componentsBlocker(m_cmbComponents); + m_cmbComponents->resetOriginalTexts(itemsList); + m_cmbComponents->setCurrentIndex(1); // All Channels... } - m_cmbComponents->setCurrentIndex(1); // All Channels... } { // Input Color Space - KisSignalsBlocker inputCSBlocker(m_cmbInputColorSpace); - m_cmbInputColorSpace->clear(); - + QStringList itemsList; int numOcioColorSpaces = m_ocioConfig->getNumColorSpaces(); for(int i = 0; i < numOcioColorSpaces; ++i) { const char *cs = m_ocioConfig->getColorSpaceNameByIndex(i); OCIO::ConstColorSpaceRcPtr colorSpace = m_ocioConfig->getColorSpace(cs); - m_cmbInputColorSpace->addSqueezedItem(QString::fromUtf8(colorSpace->getName())); + itemsList << QString::fromUtf8(colorSpace->getName()); + } + + if (itemsList != m_cmbInputColorSpace->originalTexts()) { + KisSignalsBlocker inputCSBlocker(m_cmbInputColorSpace); + m_cmbInputColorSpace->resetOriginalTexts(itemsList); } } { // Display Device - KisSignalsBlocker displayDeviceLocker(m_cmbDisplayDevice); - m_cmbDisplayDevice->clear(); + QStringList itemsList; int numDisplays = m_ocioConfig->getNumDisplays(); for (int i = 0; i < numDisplays; ++i) { - m_cmbDisplayDevice->addSqueezedItem(QString::fromUtf8(m_ocioConfig->getDisplay(i))); + itemsList << QString::fromUtf8(m_ocioConfig->getDisplay(i)); + } + + if (itemsList != m_cmbDisplayDevice->originalTexts()) { + KisSignalsBlocker displayDeviceLocker(m_cmbDisplayDevice); + m_cmbDisplayDevice->resetOriginalTexts(itemsList); } } { // Lock Current Color KisSignalsBlocker locker(m_btnConvertCurrentColor); KisConfig cfg; m_btnConvertCurrentColor->setChecked(cfg.ocioLockColorVisualRepresentation()); } refillViewCombobox(); { - KisSignalsBlocker LookComboLocker(m_cmbLook); - m_cmbLook->clear(); + QStringList itemsList; int numLooks = m_ocioConfig->getNumLooks(); for (int k = 0; k < numLooks; k++) { - m_cmbLook->addSqueezedItem(QString::fromUtf8(m_ocioConfig->getLookNameByIndex(k))); + itemsList << QString::fromUtf8(m_ocioConfig->getLookNameByIndex(k)); + } + itemsList << i18nc("Item to indicate no look transform being selected","None"); + + if (itemsList != m_cmbLook->originalTexts()) { + KisSignalsBlocker LookComboLocker(m_cmbLook); + m_cmbLook->resetOriginalTexts(itemsList); } - m_cmbLook->addSqueezedItem(i18nc("Item to indicate no look transform being selected","None")); } updateDisplaySettings(); } void LutDockerDock::refillViewCombobox() { KisSignalsBlocker viewComboLocker(m_cmbView); m_cmbView->clear(); if (!m_canvas || !m_ocioConfig) return; const char *display = m_ocioConfig->getDisplay(m_cmbDisplayDevice->currentIndex()); int numViews = m_ocioConfig->getNumViews(display); for (int j = 0; j < numViews; ++j) { m_cmbView->addSqueezedItem(QString::fromUtf8(m_ocioConfig->getView(display, j))); } } void LutDockerDock::selectLut() { QString filename = m_txtLut->text(); KoFileDialog dialog(this, KoFileDialog::OpenFile, "lutdocker"); dialog.setCaption(i18n("Select LUT file")); dialog.setDefaultDir(QDir::cleanPath(filename)); dialog.setMimeTypeFilters(QStringList() << "application/octet-stream", "application/octet-stream"); filename = dialog.filename(); QFile f(filename); if (f.exists() && filename != m_txtLut->text()) { m_txtLut->setText(filename); KisConfig cfg; cfg.setOcioLutPath(filename); updateDisplaySettings(); } } void LutDockerDock::clearLut() { m_txtLut->clear(); updateDisplaySettings(); } diff --git a/plugins/dockers/lut/ocio_display_filter.cpp b/plugins/dockers/lut/ocio_display_filter.cpp index 87339c4ee4..1faf0a3396 100644 --- a/plugins/dockers/lut/ocio_display_filter.cpp +++ b/plugins/dockers/lut/ocio_display_filter.cpp @@ -1,369 +1,377 @@ /* * Copyright (c) 2012 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ocio_display_filter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include OcioDisplayFilter::OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent) : KisDisplayFilter(parent) , inputColorSpaceName(0) , displayDevice(0) , view(0) , look(0) , swizzle(RGBA) , m_interface(interface) , m_lut3dTexID(0) , m_shaderDirty(true) { } OcioDisplayFilter::~OcioDisplayFilter() { } KisExposureGammaCorrectionInterface* OcioDisplayFilter::correctionInterface() const { return m_interface; } void OcioDisplayFilter::filter(quint8 *pixels, quint32 numPixels) { // processes that data _in_ place if (m_processor) { OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); m_processor->apply(img); } } void OcioDisplayFilter::approximateInverseTransformation(quint8 *pixels, quint32 numPixels) { // processes that data _in_ place if (m_revereseApproximationProcessor) { OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); m_revereseApproximationProcessor->apply(img); } } void OcioDisplayFilter::approximateForwardTransformation(quint8 *pixels, quint32 numPixels) { // processes that data _in_ place if (m_forwardApproximationProcessor) { OCIO::PackedImageDesc img(reinterpret_cast(pixels), numPixels, 1, 4); m_forwardApproximationProcessor->apply(img); } } bool OcioDisplayFilter::useInternalColorManagement() const { return forceInternalColorManagement; } bool OcioDisplayFilter::lockCurrentColorVisualRepresentation() const { return m_lockCurrentColorVisualRepresentation; } void OcioDisplayFilter::setLockCurrentColorVisualRepresentation(bool value) { m_lockCurrentColorVisualRepresentation = value; } QString OcioDisplayFilter::program() const { return m_program; } GLuint OcioDisplayFilter::lutTexture() const { return m_lut3dTexID; } void OcioDisplayFilter::updateProcessor() { if (!config) { return; } if (!displayDevice) { displayDevice = config->getDefaultDisplay(); } if (!view) { view = config->getDefaultView(displayDevice); } if (!inputColorSpaceName) { inputColorSpaceName = config->getColorSpaceNameByIndex(0); } if (!look) { look = config->getLookNameByIndex(0); } if (!displayDevice || !view || !inputColorSpaceName) { return; } OCIO::DisplayTransformRcPtr transform = OCIO::DisplayTransform::Create(); transform->setInputColorSpaceName(inputColorSpaceName); transform->setDisplay(displayDevice); transform->setView(view); /** * Look support: * As the OCIO docs will tell you, looks are a aesthetic transform that is * added onto the mix. * A view+display can have it's own assigned Look, or list of looks, and these * can be overriden optionally. * What the OCIO docs won't tell you is that a display transform won't use the * looks attached to it unless "skipColorSpaceConversions" is false... * I have no idea what "skipColorSpaceConversions" is beyond what it says on the * tin. It is not mentioned in the documentation anywhere. Or on the website. * Or how to set it. Or unset it. Why it is apparantly set true to begin with. * Only that, apparantly, this was done with non-color data in mind... * * Until there's clear documentation on how to use this feature, I am afraid the * override is all we can offer. */ if (config->getLook(look)) { transform->setLooksOverride(look); transform->setLooksOverrideEnabled(true); } OCIO::GroupTransformRcPtr approximateTransform = OCIO::GroupTransform::Create(); // fstop exposure control -- not sure how that translates to our exposure { float exposureGain = powf(2.0f, exposure); const qreal minRange = 0.001; if (qAbs(blackPoint - whitePoint) < minRange) { whitePoint = blackPoint + minRange; } const float oldMin[] = { blackPoint, blackPoint, blackPoint, 0.0f }; const float oldMax[] = { whitePoint, whitePoint, whitePoint, 1.0f }; const float newMin[] = { 0.0f, 0.0f, 0.0f, 0.0f }; const float newMax[] = { exposureGain, exposureGain, exposureGain, 1.0f }; float m44[16]; float offset4[4]; OCIO::MatrixTransform::Fit(m44, offset4, oldMin, oldMax, newMin, newMax); OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); mtx->setValue(m44, offset4); transform->setLinearCC(mtx); // approximation (no color correction); approximateTransform->push_back(mtx); } // channel swizzle { int channelHot[4]; switch (swizzle) { case LUMINANCE: channelHot[0] = 1; channelHot[1] = 1; channelHot[2] = 1; channelHot[3] = 0; break; case RGBA: channelHot[0] = 1; channelHot[1] = 1; channelHot[2] = 1; channelHot[3] = 1; break; case R: channelHot[0] = 1; channelHot[1] = 0; channelHot[2] = 0; channelHot[3] = 0; break; case G: channelHot[0] = 0; channelHot[1] = 1; channelHot[2] = 0; channelHot[3] = 0; break; case B: channelHot[0] = 0; channelHot[1] = 0; channelHot[2] = 1; channelHot[3] = 0; break; case A: channelHot[0] = 0; channelHot[1] = 0; channelHot[2] = 0; channelHot[3] = 1; default: ; } float lumacoef[3]; config->getDefaultLumaCoefs(lumacoef); float m44[16]; float offset[4]; OCIO::MatrixTransform::View(m44, offset, channelHot, lumacoef); OCIO::MatrixTransformRcPtr swizzle = OCIO::MatrixTransform::Create(); swizzle->setValue(m44, offset); transform->setChannelView(swizzle); } // Post-display transform gamma { float exponent = 1.0f/std::max(1e-6f, static_cast(gamma)); const float exponent4f[] = { exponent, exponent, exponent, exponent }; OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); expTransform->setValue(exponent4f); transform->setDisplayCC(expTransform); // approximation (no color correction); approximateTransform->push_back(expTransform); } m_processor = config->getProcessor(transform); m_forwardApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_FORWARD); try { m_revereseApproximationProcessor = config->getProcessor(approximateTransform, OCIO::TRANSFORM_DIR_INVERSE); } catch (...) { warnKrita << "OCIO inverted matrix does not exist!"; //m_revereseApproximationProcessor; } m_shaderDirty = true; } -void OcioDisplayFilter::updateShader() +bool OcioDisplayFilter::updateShader() { + bool result = false; + if (KisOpenGL::hasOpenGL3()) { QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions(); - updateShaderImpl(f); + result = updateShaderImpl(f); } // XXX This option can be removed once we move to Qt 5.7+ else if (KisOpenGL::supportsLoD()) { #ifdef Q_OS_MAC QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions(); #else QOpenGLFunctions_3_0 *f = QOpenGLContext::currentContext()->versionFunctions(); #endif - updateShaderImpl(f); + result = updateShaderImpl(f); } else { QOpenGLFunctions_2_0 *f = QOpenGLContext::currentContext()->versionFunctions(); - updateShaderImpl(f); + result = updateShaderImpl(f); } + + return result; } template -void OcioDisplayFilter::updateShaderImpl(F *f) { +bool OcioDisplayFilter::updateShaderImpl(F *f) { // check whether we are allowed to use shaders -- though that should // work for everyone these days KisConfig cfg; - if (!cfg.useOpenGL()) return; + if (!cfg.useOpenGL()) return false; - if (!m_shaderDirty) return; + if (!m_shaderDirty) return false; if (!f) { qWarning() << "Failed to get valid OpenGL functions for OcioDisplayFilter!"; - return; + return false; } f->initializeOpenGLFunctions(); + bool shouldRecompileShader = false; + const int lut3DEdgeSize = cfg.ocioLutEdgeSize(); if (m_lut3d.size() == 0) { //dbgKrita << "generating lut"; f->glGenTextures(1, &m_lut3dTexID); int num3Dentries = 3 * lut3DEdgeSize * lut3DEdgeSize * lut3DEdgeSize; m_lut3d.fill(0.0, num3Dentries); f->glActiveTexture(GL_TEXTURE1); f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID); f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); f->glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); f->glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB16F_ARB, lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize, 0, GL_RGB, GL_FLOAT, &m_lut3d.constData()[0]); } // Step 1: Create a GPU Shader Description OCIO::GpuShaderDesc shaderDesc; if (KisOpenGL::supportsLoD()) { shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_3); } else { shaderDesc.setLanguage(OCIO::GPU_LANGUAGE_GLSL_1_0); } shaderDesc.setFunctionName("OCIODisplay"); shaderDesc.setLut3DEdgeLen(lut3DEdgeSize); // Step 2: Compute the 3D LUT QString lut3dCacheID = QString::fromLatin1(m_processor->getGpuLut3DCacheID(shaderDesc)); if (lut3dCacheID != m_lut3dcacheid) { //dbgKrita << "Computing 3DLut " << m_lut3dcacheid; m_lut3dcacheid = lut3dCacheID; m_processor->getGpuLut3D(&m_lut3d[0], shaderDesc); f->glBindTexture(GL_TEXTURE_3D, m_lut3dTexID); f->glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, lut3DEdgeSize, lut3DEdgeSize, lut3DEdgeSize, GL_RGB, GL_FLOAT, &m_lut3d[0]); } // Step 3: Generate the shader text QString shaderCacheID = QString::fromLatin1(m_processor->getGpuShaderTextCacheID(shaderDesc)); if (m_program.isEmpty() || shaderCacheID != m_shadercacheid) { //dbgKrita << "Computing Shader " << m_shadercacheid; m_shadercacheid = shaderCacheID; std::ostringstream os; os << m_processor->getGpuShaderText(shaderDesc) << "\n"; m_program = QString::fromLatin1(os.str().c_str()); + shouldRecompileShader = true; } m_shaderDirty = false; + return shouldRecompileShader; } diff --git a/plugins/dockers/lut/ocio_display_filter.h b/plugins/dockers/lut/ocio_display_filter.h index 87222ac196..96c2c2e9d6 100644 --- a/plugins/dockers/lut/ocio_display_filter.h +++ b/plugins/dockers/lut/ocio_display_filter.h @@ -1,96 +1,96 @@ /* * Copyright (c) 2012 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef OCIO_DISPLAY_FILTER_H #define OCIO_DISPLAY_FILTER_H #include "kritalutdocker_export.h" #include #include #include #include #include "kis_exposure_gamma_correction_interface.h" namespace OCIO = OCIO_NAMESPACE; enum OCIO_CHANNEL_SWIZZLE { LUMINANCE, RGBA, R, G, B, A }; class KRITALUTDOCKER_EXPORT OcioDisplayFilter : public KisDisplayFilter { Q_OBJECT public: explicit OcioDisplayFilter(KisExposureGammaCorrectionInterface *interface, QObject *parent = 0); ~OcioDisplayFilter(); void filter(quint8 *pixels, quint32 numPixels); void approximateInverseTransformation(quint8 *pixels, quint32 numPixels); void approximateForwardTransformation(quint8 *pixels, quint32 numPixels); bool useInternalColorManagement() const; bool lockCurrentColorVisualRepresentation() const; void setLockCurrentColorVisualRepresentation(bool value); - void updateShader(); + bool updateShader(); template - void updateShaderImpl(F *f); + bool updateShaderImpl(F *f); KisExposureGammaCorrectionInterface *correctionInterface() const; virtual QString program() const; GLuint lutTexture() const; void updateProcessor(); OCIO::ConstConfigRcPtr config; const char *inputColorSpaceName; const char *displayDevice; const char *view; const char *look; OCIO_CHANNEL_SWIZZLE swizzle; float exposure; float gamma; float blackPoint; float whitePoint; bool forceInternalColorManagement; private: OCIO::ConstProcessorRcPtr m_processor; OCIO::ConstProcessorRcPtr m_revereseApproximationProcessor; OCIO::ConstProcessorRcPtr m_forwardApproximationProcessor; KisExposureGammaCorrectionInterface *m_interface; bool m_lockCurrentColorVisualRepresentation; QString m_program; GLuint m_lut3dTexID; QVector m_lut3d; QString m_lut3dcacheid; QString m_shadercacheid; bool m_shaderDirty; }; #endif // OCIO_DISPLAY_FILTER_H diff --git a/plugins/extensions/buginfo/buginfo.cpp b/plugins/extensions/buginfo/buginfo.cpp index a861f03049..3d6cd71384 100644 --- a/plugins/extensions/buginfo/buginfo.cpp +++ b/plugins/extensions/buginfo/buginfo.cpp @@ -1,52 +1,51 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "buginfo.h" #include #include #include #include #include #include #include #include "dlg_buginfo.h" K_PLUGIN_FACTORY_WITH_JSON(BugInfoFactory, "kritabuginfo.json", registerPlugin();) BugInfo::BugInfo(QObject *parent, const QVariantList &) : KisViewPlugin(parent) { KisAction *action = createAction("buginfo"); - action->setText(i18n("Show system information for bug reports.")); connect(action, SIGNAL(triggered()), this, SLOT(slotBugInfo())); } BugInfo::~BugInfo() { } void BugInfo::slotBugInfo() { DlgBugInfo dlgBugInfo(m_view->mainWindow()); dlgBugInfo.exec(); } #include "buginfo.moc" diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp index c12bcb75a2..538a8d06bf 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.cpp +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -1,88 +1,91 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dlg_buginfo.h" #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" DlgBugInfo::DlgBugInfo(QWidget *parent) : KoDialog(parent) { setCaption(i18n("Please paste this information in your bug report")); setButtons(User1 | Ok); setButtonText(User1, i18n("Copy to clipboard")); setDefaultButton(Ok); m_page = new WdgBugInfo(this); Q_CHECK_PTR(m_page); setMainWidget(m_page); QString info; // Krita version info info.append("Krita"); info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); info.append("\n\n"); // OS information info.append("OS Information"); info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); info.append("\n Product Type: ").append(QSysInfo::productType()); info.append("\n Product Version: ").append(QSysInfo::productVersion()); info.append("\n"); // OpenGL information info.append("\n").append(KisOpenGL::getDebugText()); // Installation information // calculate a default height for the widget int wheight = m_page->sizeHint().height(); m_page->txtBugInfo->setText(info); QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); int target_height = fm.height() * info.split('\n').size() + wheight; QDesktopWidget dw; QRect screen_rect = dw.availableGeometry(dw.primaryScreen()); - resize(sizeHint().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); + resize(m_page->size().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); - connect(this, &KoDialog::user1Clicked, this, [this](){ QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); }); + connect(this, &KoDialog::user1Clicked, this, [this](){ + QGuiApplication::clipboard()->setText(m_page->txtBugInfo->toPlainText()); + m_page->txtBugInfo->selectAll(); // feedback + }); } DlgBugInfo::~DlgBugInfo() { delete m_page; } diff --git a/plugins/extensions/buginfo/wdg_buginfo.ui b/plugins/extensions/buginfo/wdg_buginfo.ui index 35c684948a..b8e7bb913a 100644 --- a/plugins/extensions/buginfo/wdg_buginfo.ui +++ b/plugins/extensions/buginfo/wdg_buginfo.ui @@ -1,31 +1,32 @@ WdgBugInfo 0 0 - 367 + 640 439 - - Rotate Image - Please add this information to a bug report: - + + + QTextEdit::NoWrap + + diff --git a/plugins/extensions/pykrita/kritarunner/main.cpp b/plugins/extensions/pykrita/kritarunner/main.cpp index caf5eabd1d..9a2ac54ff5 100644 --- a/plugins/extensions/pykrita/kritarunner/main.cpp +++ b/plugins/extensions/pykrita/kritarunner/main.cpp @@ -1,123 +1,119 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "kis_md5_generator.h" #include #include #include 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/plugin/krita/__init__.py b/plugins/extensions/pykrita/plugin/krita/__init__.py index 9021765627..ee0328bc8c 100644 --- a/plugins/extensions/pykrita/plugin/krita/__init__.py +++ b/plugins/extensions/pykrita/plugin/krita/__init__.py @@ -1,72 +1,72 @@ import pykrita import os import sys import signal signal.signal(signal.SIGINT, signal.SIG_DFL) from .api import * from .decorators import * from .dockwidgetfactory import * from PyKrita import krita krita_path = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, krita_path) print("%s added to PYTHONPATH" % krita_path, file=sys.stderr) # Look for PyQt try: from PyQt5 import QtCore except ImportError: print("Python cannot find the Qt5 bindings.", file=sys.stderr) print("Please make sure, that the needed packages are installed.", file=sys.stderr) raise # Shows nice looking error dialog if an unhandled exception occures. import excepthook excepthook.install() import builtins -builtins.i18n = lambda s: unicode(QCoreApplication.translate("PyKrita", s)) +builtins.i18n = lambda s: QCoreApplication.translate("PyKrita", s) builtins.Scripter = Krita.instance() builtins.Application = Krita.instance() builtins.Krita = Krita.instance() def qDebug(text): '''Use KDE way to show debug info TODO Add a way to control debug output from partucular plugins (?) ''' plugin = sys._getframe(1).f_globals['__name__'] pykrita.qDebug('{}: {}'.format(plugin, text)) @pykritaEventHandler('_pluginLoaded') def on_load(plugin): if plugin in init.functions: # Call registered init functions for the plugin init.fire(plugin=plugin) del init.functions[plugin] return True @pykritaEventHandler('_pluginUnloading') def on_unload(plugin): if plugin in unload.functions: # Deinitialize plugin unload.fire(plugin=plugin) del unload.functions[plugin] return True @pykritaEventHandler('_pykritaLoaded') def on_pykrita_loaded(): qDebug('PYKRITA LOADED') return True @pykritaEventHandler('_pykritaUnloading') def on_pykrita_unloading(): qDebug('UNLOADING PYKRITA') return True diff --git a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py index 3c4ceb9998..00a2547208 100644 --- a/plugins/extensions/pykrita/plugin/plugins/hello/hello.py +++ b/plugins/extensions/pykrita/plugin/plugins/hello/hello.py @@ -1,74 +1,74 @@ """ This is a simple example of a Python script for Krita. It demonstrates how to set up a custom extension and a custom docker! """ from PyQt5.QtCore import qDebug from PyQt5.QtWidgets import QWidget, QLabel, QMessageBox from krita import Krita, Extension, DockWidget, DockWidgetFactory, DockWidgetFactoryBase def hello(): """ Show a test message box. """ - QMessageBox.information(QWidget(), "Test", "Hello! This is Krita version %s" % Application.version()) + QMessageBox.information(QWidget(), i18n("Test"), i18n("Hello! This is Krita version %s") % Application.version()) class HelloExtension(Extension): """ HelloExtension is a small example extension demonstrating basic Python scripting support in Krita! """ def __init__(self, parent): """ Standard Krita Python extension constructor. Most of the initialization happens in :func:`setup` :param parent: Parent widget :type parent: :class:`QWidget` or None """ super().__init__(parent) def setup(self): """ This is where most of the setup takes place! """ qDebug("Hello Setup") action = Krita.instance().createAction("hello_python", "hello") action.triggered.connect(hello) # Initialize and add the extension Scripter.addExtension(HelloExtension(Krita.instance())) class HelloDocker(DockWidget): """ The HelloDocker is an example of a simple Python-based docker. """ def __init__(self): """ Constructs an instance of HelloDocker and the widget it contains """ super().__init__() # The window title is also used in the Docker menu, # so it should be set to something sensible! self.setWindowTitle("HelloDocker") label = QLabel("Hello", self) self.setWidget(label) self._label = label def canvasChanged(self, canvas): """ Override canvasChanged from :class:`DockWidget`. This gets called when the canvas changes. You can also access the active canvas via :func:`DockWidget.canvas` """ self._label.setText("HelloDocker: canvas changed") # Register the docker so Krita can use it! Application.addDockWidgetFactory(DockWidgetFactory("hello", DockWidgetFactoryBase.DockRight, HelloDocker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py index 6c49f34992..ff350f23a5 100644 --- a/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py +++ b/plugins/extensions/pykrita/plugin/plugins/lastdocumentsdocker/lastdocumentslistmodel.py @@ -1,44 +1,44 @@ from PyQt5.QtCore import QAbstractListModel, Qt import krita class LastDocumentsListModel(QAbstractListModel): def __init__(self, parent=None): super(LastDocumentsListModel, self).__init__(parent) self.rootItem = ('Path',) self.kritaInstance = krita.Krita.instance() self.recentDocuments = [] self._loadRecentDocuments() def data(self, index, role): if not index.isValid(): return None if index.row() >= len(self.recentDocuments): return None if role == Qt.DecorationRole: return self.recentDocuments[index.row()] else: return None def rowCount(self, parent): return len(self.recentDocuments) def headerData(self, section, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: return self.rootItem[section] return None def _loadRecentDocuments(self): recentDocumentsPaths = self.kritaInstance.recentDocuments() - for path in recentDocumentsPaths: - document = self.kritaInstance.openDocument(path) - if document: - self.recentDocuments.append(document.thumbnail(70, 60)) - document.close() + #for path in recentDocumentsPaths: + # document = self.kritaInstance.openDocument(path) + # if document: + # self.recentDocuments.append(document.thumbnail(70, 60)) + # document.close() diff --git a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py index 4e9ecb0efd..82cfe1e648 100644 --- a/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/palette_docker/palette_docker.py @@ -1,232 +1,231 @@ # Description: A Python based docker that allows you to edit KPL color palettes. # By Wolthera # Importing the relevant dependancies: import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.Qt import * import math from krita import * # import the exporters from . import palette_exporter_gimppalette, palette_exporter_inkscapeSVG, palette_sortColors class Palette_Docker(DockWidget): # Init the docker def __init__(self): super().__init__() # make base-widget and layout widget = QWidget() layout = QVBoxLayout() buttonLayout = QHBoxLayout() widget.setLayout(layout) self.setWindowTitle("Python Palette Docker") # Make a combobox and add palettes self.cmb_palettes = QComboBox() allPalettes = Application.resources("palette") for palette_name in allPalettes: self.cmb_palettes.addItem(palette_name) self.cmb_palettes.model().sort(0) self.currentPalette = Palette(allPalettes[list(allPalettes.keys())[0]]) self.cmb_palettes.currentTextChanged.connect(self.slot_paletteChanged) layout.addWidget(self.cmb_palettes) # add combobox to the layout self.paletteView = PaletteView() self.paletteView.setPalette(self.currentPalette) layout.addWidget(self.paletteView) self.paletteView.entrySelectedForeGround.connect(self.slot_swatchSelected) self.colorComboBox = QComboBox() self.colorList = list() buttonLayout.addWidget(self.colorComboBox) - self.addEntry = QAction() - self.addEntry.setText("Add Entry") + self.addEntry = QAction(self) self.addEntry.setIconText("+") self.addEntry.triggered.connect(self.slot_add_entry) - self.addGroup = QAction() + self.addGroup = QAction(self) self.addGroup.triggered.connect(self.slot_add_group) self.addGroup.setText("Add Group") self.addGroup.setIconText(str("\U0001F4C2")) - self.removeEntry = QAction() + self.removeEntry = QAction(self) self.removeEntry.setText("Remove Entry") self.removeEntry.setIconText("-") self.removeEntry.triggered.connect(self.slot_remove_entry) addEntryButton = QToolButton() addEntryButton.setDefaultAction(self.addEntry) buttonLayout.addWidget(addEntryButton) addGroupButton = QToolButton() addGroupButton.setDefaultAction(self.addGroup) buttonLayout.addWidget(addGroupButton) removeEntryButton = QToolButton() removeEntryButton.setDefaultAction(self.removeEntry) buttonLayout.addWidget(removeEntryButton) # QActions self.extra = QToolButton() - self.editPaletteData = QAction() + self.editPaletteData = QAction(self) self.editPaletteData.setText("Edit Palette Settings") self.editPaletteData.triggered.connect(self.slot_edit_palette_data) self.extra.setDefaultAction(self.editPaletteData) buttonLayout.addWidget(self.extra) self.actionMenu = QMenu() - self.exportToGimp = QAction() + self.exportToGimp = QAction(self) self.exportToGimp.setText("Export as GIMP palette file.") self.exportToGimp.triggered.connect(self.slot_export_to_gimp_palette) - self.exportToInkscape = QAction() + self.exportToInkscape = QAction(self) self.exportToInkscape.setText("Export as Inkscape SVG with swatches.") self.exportToInkscape.triggered.connect(self.slot_export_to_inkscape_svg) - self.sortColors = QAction() + self.sortColors = QAction(self) self.sortColors.setText("Sort colors") self.sortColors.triggered.connect(self.slot_sort_colors) self.actionMenu.addAction(self.editPaletteData) self.actionMenu.addAction(self.exportToGimp) self.actionMenu.addAction(self.exportToInkscape) self.actionMenu.addAction(self.sortColors) self.extra.setMenu(self.actionMenu) layout.addLayout(buttonLayout) self.slot_fill_combobox() self.setWidget(widget) # add widget to the docker def slot_paletteChanged(self, name): self.currentPalette = Palette(Application.resources("palette")[name]) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() @pyqtSlot('KoColorSetEntry') def slot_swatchSelected(self, entry): print("entry " + entry.name) if (self.canvas()) is not None: if (self.canvas().view()) is not None: name = entry.name if len(entry.id) > 0: name = entry.id + " - " + entry.name if len(name) > 0: if name in self.colorList: self.colorComboBox.setCurrentIndex(self.colorList.index(name)) color = self.currentPalette.colorForEntry(entry) self.canvas().view().setForeGroundColor(color) ''' A function for making a combobox with the available colors. We use QCompleter on the colorComboBox so that people can type in the name of a color to select it. This is useful for people with carefully made palettes where the colors are named properly, which makes it easier for them to find colors. ''' def slot_fill_combobox(self): if self.currentPalette is None: pass palette = self.currentPalette self.colorComboBox.clear() self.colorList.clear() for i in range(palette.colorsCountTotal()): entry = palette.colorSetEntryByIndex(i) color = palette.colorForEntry(entry).colorForCanvas(self.canvas()) colorSquare = QPixmap(12, 12) if entry.spotColor is True: img = colorSquare.toImage() circlePainter = QPainter() img.fill(self.colorComboBox.palette().color(QPalette.Base)) circlePainter.begin(img) brush = QBrush(Qt.SolidPattern) brush.setColor(color) circlePainter.setBrush(brush) circlePainter.pen().setWidth(0) circlePainter.drawEllipse(0, 0, 11, 11) circlePainter.end() colorSquare = QPixmap.fromImage(img) else: colorSquare.fill(color) name = entry.name if len(entry.id) > 0: name = entry.id + " - " + entry.name self.colorList.append(name) self.colorComboBox.addItem(QIcon(colorSquare), name) self.colorComboBox.setEditable(True) self.colorComboBox.setInsertPolicy(QComboBox.NoInsert) self.colorComboBox.completer().setCompletionMode(QCompleter.PopupCompletion) self.colorComboBox.completer().setCaseSensitivity(False) self.colorComboBox.completer().setFilterMode(Qt.MatchContains) self.colorComboBox.currentIndexChanged.connect(self.slot_get_color_from_combobox) def slot_get_color_from_combobox(self): if self.currentPalette is not None: entry = self.currentPalette.colorSetEntryByIndex(self.colorComboBox.currentIndex()) self.slot_swatchSelected(entry) def slot_add_entry(self): if (self.canvas()) is not None: if (self.canvas().view()) is not None: color = self.canvas().view().foreGroundColor() succes = self.paletteView.addEntryWithDialog(color) if succes is True: self.slot_fill_combobox() def slot_add_group(self): succes = self.paletteView.addGroupWithDialog() if succes is True: self.slot_fill_combobox() def slot_remove_entry(self): succes = self.paletteView.removeSelectedEntryWithDialog() if succes is True: self.slot_fill_combobox() ''' A function for giving a gui to edit palette metadata... I also want this to be the way to edit the settings of the palette docker. ''' def slot_edit_palette_data(self): dialog = QDialog(self) tabWidget = QTabWidget() dialog.setWindowTitle("Edit Palette Data") dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(tabWidget) paletteWidget = QWidget() paletteWidget.setLayout(QVBoxLayout()) tabWidget.addTab(paletteWidget, "Palette Data") paletteName = QLineEdit() paletteName.setText(self.cmb_palettes.currentText()) paletteWidget.layout().addWidget(paletteName) paletteColumns = QSpinBox() paletteColumns.setValue(self.currentPalette.columnCount()) paletteWidget.layout().addWidget(paletteColumns) paletteComment = QPlainTextEdit() paletteComment.appendPlainText(self.currentPalette.comment()) paletteWidget.layout().addWidget(paletteComment) buttons = QDialogButtonBox(QDialogButtonBox.Ok) dialog.layout().addWidget(buttons) buttons.accepted.connect(dialog.accept) # buttons.rejected.connect(dialog.reject()) if dialog.exec_() == QDialog.Accepted: Resource = Application.resources("palette")[self.cmb_palettes.currentText()] Resource.setName(paletteName.text()) self.currentPalette = Palette(Resource) print(paletteColumns.value()) self.currentPalette.setColumnCount(paletteColumns.value()) self.paletteView.setPalette(self.currentPalette) self.slot_fill_combobox() self.currentPalette.setComment(paletteComment.toPlainText()) self.currentPalette.save() def slot_export_to_gimp_palette(self): palette_exporter_gimppalette.gimpPaletteExporter(self.cmb_palettes.currentText()) def slot_export_to_inkscape_svg(self): palette_exporter_inkscapeSVG.inkscapeSVGExporter(self.cmb_palettes.currentText()) def slot_sort_colors(self): colorSorter = palette_sortColors.sortColors(self.cmb_palettes.currentText()) self.paletteView.setPalette(colorSorter.palette()) def canvasChanged(self, canvas): pass # Add docker to the application :) Application.addDockWidgetFactory(DockWidgetFactory("palette_docker", DockWidgetFactoryBase.DockRight, Palette_Docker)) diff --git a/plugins/extensions/pykrita/sip/krita/Document.sip b/plugins/extensions/pykrita/sip/krita/Document.sip index 83102c4a7b..63d3db0f22 100644 --- a/plugins/extensions/pykrita/sip/krita/Document.sip +++ b/plugins/extensions/pykrita/sip/krita/Document.sip @@ -1,69 +1,77 @@ 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; + QList horizontalGuides() const; + QList verticalGuides() const; + bool guidesVisible() const; + bool guidesLocked() const; public Q_SLOTS: Node * activeNode() const /Factory/; void setActiveNode(Node* value); QList topLevelNodes() const /Factory/; Node *nodeByName(const QString &node) const /Factory/; bool batchmode() const; void setBatchmode(bool value); QString colorDepth() const; QString colorModel() const; QString colorProfile() const; bool setColorProfile(const QString &colorProfile); bool setColorSpace(const QString &value, const QString &colorDepth, const QString &colorProfile); QString documentInfo() const; void setDocumentInfo(const QString &document); QString fileName() const; void setFileName(QString value); int height() const; void setHeight(int value); QString name() const; void setName(QString value); int resolution() const; void setResolution(int value); Node * rootNode() const /Factory/; Selection * selection() const /Factory/; void setSelection(Selection* value); int width() const; void setWidth(int value); int xOffset() const; void setXOffset(int x); int yOffset() const; void setYOffset(int y); double xRes() const; void setXRes(double xRes) const; double yRes() const; void setYRes(double yRes) const; QByteArray pixelData(int x, int y, int w, int h) const; bool close(); void crop(int x, int y, int w, int h); bool exportImage(const QString &filename, const InfoObject & exportConfiguration); void flatten(); void resizeImage(int x, int y, int w, int h); void scaleImage(int w, int h, int xres, int yres, QString strategy); void rotateImage(double radians); void shearImage(double angleX, double angleY); bool save(); bool saveAs(const QString & filename); Node *createNode(const QString & name, const QString & nodeType) /Factory/; QImage projection(int x = 0, int y = 0, int w = 0, int h = 0) const; QImage thumbnail(int w, int h) const; void lock(); void unlock(); void waitForDone(); bool tryBarrierLock(); bool isIdle(); void refreshProjection(); + void setHorizontalGuides(const QList &lines); + void setVerticalGuides(const QList &lines); + void setGuidesVisible(bool visible); + void setGuidesLocked(bool locked); private: }; diff --git a/plugins/extensions/qmic/PluginSettings.cpp b/plugins/extensions/qmic/PluginSettings.cpp index a0de220f35..a3c0c581cc 100644 --- a/plugins/extensions/qmic/PluginSettings.cpp +++ b/plugins/extensions/qmic/PluginSettings.cpp @@ -1,79 +1,103 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 "PluginSettings.h" #include #include #include #include #include #include #include "kis_config.h" PluginSettings::PluginSettings(QWidget *parent) : KisPreferenceSet(parent) { setupUi(this); - fileRequester->setFileName(KisConfig().readEntry("gmic_qt_plugin_path")); + fileRequester->setFileName(gmicQtPath()); fileRequester->setStartDir(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); } PluginSettings::~PluginSettings() { KisConfig().writeEntry("gmic_qt_plugin_path", fileRequester->fileName()); } QString PluginSettings::id() { return QString("qmicsettings"); } QString PluginSettings::name() { return header(); } QString PluginSettings::header() { return QString(i18n("G'Mic-Qt Integration")); } QIcon PluginSettings::icon() { - return koIcon("gmic"); // XXX: Replace with Animtim's G'Mic icon + return koIcon("gmic"); +} + +QString PluginSettings::gmicQtPath() +{ + QString gmicqt("gmic_krita_qt"); +#ifdef Q_OS_WIN + gmicqt += ".exe"; +#endif + gmicqt = KisConfig().readEntry("gmic_qt_plugin_path", qApp->applicationDirPath() + "/" + gmicqt); + QFileInfo fi(gmicqt); + if (!fi.exists()) { + QFileInfo fi2(qApp->applicationDirPath() + "/" + gmicqt); + if (fi2.exists()) { + gmicqt = qApp->applicationDirPath() + "/" + gmicqt; + } + else { + gmicqt.clear(); + } + } + return gmicqt; } void PluginSettings::savePreferences() const { KisConfig().writeEntry("gmic_qt_plugin_path", fileRequester->fileName()); Q_EMIT(settingsChanged()); } void PluginSettings::loadPreferences() { - fileRequester->setFileName(KisConfig().readEntry("gmic_qt_plugin_path")); + QString gmicqt("gmic_host_krita"); +#ifdef Q_OS_WIN + gmicqt += ".exe"; +#endif + fileRequester->setFileName(gmicQtPath()); } void PluginSettings::loadDefaultPreferences() { - fileRequester->setFileName(""); + fileRequester->setFileName(gmicQtPath()); } diff --git a/plugins/extensions/qmic/PluginSettings.h b/plugins/extensions/qmic/PluginSettings.h index 8b8399ced8..00dfa51a1d 100644 --- a/plugins/extensions/qmic/PluginSettings.h +++ b/plugins/extensions/qmic/PluginSettings.h @@ -1,84 +1,86 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; 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 PLUGINSETTINGS_H #define PLUGINSETTINGS_H #include "kis_preference_set_registry.h" class QIcon; #include "ui_WdgQMicSettings.h" class PluginSettings : public KisPreferenceSet, public Ui::WdgQMicSettings { Q_OBJECT public: PluginSettings(QWidget *parent = 0); ~PluginSettings(); virtual QString id(); virtual QString name(); virtual QString header(); virtual QIcon icon(); + static QString gmicQtPath(); + public Q_SLOTS: void savePreferences() const; void loadPreferences(); void loadDefaultPreferences(); Q_SIGNALS: void settingsChanged() const; }; class PluginSettingsUpdateRepeater : public QObject { Q_OBJECT Q_SIGNALS: void settingsUpdated(); public Q_SLOTS: void updateSettings() { Q_EMIT settingsUpdated(); } }; class PluginSettingsFactory : public KisAbstractPreferenceSetFactory { public: PluginSettingsFactory() { } KisPreferenceSet* createPreferenceSet() { PluginSettings* ps = new PluginSettings(); QObject::connect(ps, SIGNAL(settingsChanged()), &repeater, SLOT(updateSettings()), Qt::UniqueConnection); return ps; } virtual QString id() const { return "QMicSettings"; } PluginSettingsUpdateRepeater repeater; }; #endif // PLUGINSETTINGS_H diff --git a/plugins/extensions/qmic/QMic.cpp b/plugins/extensions/qmic/QMic.cpp index 5879df7a11..e20f8d840d 100644 --- a/plugins/extensions/qmic/QMic.cpp +++ b/plugins/extensions/qmic/QMic.cpp @@ -1,498 +1,500 @@ /* * Copyright (c) 2017 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QMic.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_input_output_mapper.h" #include "kis_qmic_simple_convertor.h" #include "kis_import_qmic_processing_visitor.h" #include #include "kis_qmic_applicator.h" #include "kis_qmic_progress_manager.h" static const char ack[] = "ack"; K_PLUGIN_FACTORY_WITH_JSON(QMicFactory, "kritaqmic.json", registerPlugin();) QMic::QMic(QObject *parent, const QVariantList &) : KisViewPlugin(parent) , m_gmicApplicator(0) , m_progressManager(0) { +#ifndef Q_OS_MAC KisPreferenceSetRegistry *preferenceSetRegistry = KisPreferenceSetRegistry::instance(); PluginSettingsFactory* settingsFactory = new PluginSettingsFactory(); preferenceSetRegistry->add("QMicPluginSettingsFactory", settingsFactory); m_qmicAction = createAction("QMic"); m_qmicAction->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(m_qmicAction , SIGNAL(triggered()), this, SLOT(slotQMic())); m_againAction = createAction("QMicAgain"); m_againAction->setActivationFlags(KisAction::ACTIVE_DEVICE); m_againAction->setEnabled(false); connect(m_againAction, SIGNAL(triggered()), this, SLOT(slotQMicAgain())); m_gmicApplicator = new KisQmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); - +#endif } QMic::~QMic() { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key(); memorySegment->detach(); } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); if (m_pluginProcess) { m_pluginProcess->close(); } delete m_gmicApplicator; delete m_progressManager; delete m_localServer; } void QMic::slotQMicAgain() { slotQMic(true); } void QMic::slotQMic(bool again) { m_qmicAction->setEnabled(false); m_againAction->setEnabled(false); if (m_pluginProcess) { qDebug() << "Plugin is already started" << m_pluginProcess->state(); return; } delete m_progressManager; m_progressManager = new KisQmicProgressManager(m_view); connect(m_progressManager, SIGNAL(sigProgress()), this, SLOT(slotUpdateProgress())); // find the krita-gmic-qt plugin - KisConfig cfg; - QString pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); - + QString pluginPath = PluginSettings::gmicQtPath(); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { - KoDialog dlg; - dlg.setWindowTitle(i18nc("@title:Window", "Krita")); - QWidget *w = new QWidget(&dlg); - dlg.setMainWidget(w); - QVBoxLayout *l = new QVBoxLayout(w); - l->addWidget(new PluginSettings(w)); - dlg.setButtons(KoDialog::Ok); - dlg.exec(); - pluginPath = cfg.readEntry("gmic_qt_plugin_path", QString::null); + { + KoDialog dlg; + dlg.setWindowTitle(i18nc("@title:Window", "Krita")); + QWidget *w = new QWidget(&dlg); + dlg.setMainWidget(w); + QVBoxLayout *l = new QVBoxLayout(w); + l->addWidget(new PluginSettings(w)); + dlg.setButtons(KoDialog::Ok); + dlg.exec(); + } + pluginPath = PluginSettings::gmicQtPath(); if (pluginPath.isEmpty() || !QFileInfo(pluginPath).exists()) { + m_qmicAction->setEnabled(true); + m_againAction->setEnabled(true); return; } + } m_key = QUuid::createUuid().toString(); m_localServer = new QLocalServer(); m_localServer->listen(m_key); connect(m_localServer, SIGNAL(newConnection()), SLOT(connected())); m_pluginProcess = new QProcess(this); m_pluginProcess->setProcessChannelMode(QProcess::ForwardedChannels); connect(m_pluginProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(pluginFinished(int,QProcess::ExitStatus))); connect(m_pluginProcess, SIGNAL(stateChanged(QProcess::ProcessState)), this, SLOT(pluginStateChanged(QProcess::ProcessState))); m_pluginProcess->start(pluginPath, QStringList() << m_key << (again ? QString(" reapply") : QString::null)); bool r = m_pluginProcess->waitForStarted(); while (m_pluginProcess->waitForFinished(10)) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } qDebug() << "Plugin started" << r << m_pluginProcess->state(); } void QMic::connected() { qDebug() << "connected"; QLocalSocket *socket = m_localServer->nextPendingConnection(); if (!socket) { return; } while (socket->bytesAvailable() < static_cast(sizeof(quint32))) { if (!socket->isValid()) { // stale request return; } socket->waitForReadyRead(1000); } QDataStream ds(socket); QByteArray msg; quint32 remaining; ds >> remaining; msg.resize(remaining); int got = 0; char* uMsgBuf = msg.data(); // FIXME: Should use read transaction for Qt >= 5.7: // https://doc.qt.io/qt-5/qdatastream.html#using-read-transactions do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning() << "Message reception failed" << socket->errorString(); delete socket; m_localServer->close(); delete m_localServer; m_localServer = 0; return; } QString message = QString::fromUtf8(msg); qDebug() << "Received" << message; // Check the message: we can get three different ones QMultiMap messageMap; Q_FOREACH(QString line, message.split('\n', QString::SkipEmptyParts)) { QList kv = line.split('=', QString::SkipEmptyParts); if (kv.size() == 2) { messageMap.insert(kv[0], kv[1]); } else { qWarning() << "line" << line << "is invalid."; } } if (!messageMap.contains("command")) { qWarning() << "Message did not contain a command"; return; } int mode = 0; if (messageMap.contains("mode")) { mode = messageMap.values("mode").first().toInt(); } QByteArray ba; if (messageMap.values("command").first() == "gmic_qt_get_image_size") { KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { QRect selectionRect = selection->selectedExactRect(); ba = QByteArray::number(selectionRect.width()) + "," + QByteArray::number(selectionRect.height()); } else { ba = QByteArray::number(m_view->image()->width()) + "," + QByteArray::number(m_view->image()->height()); } } else if (messageMap.values("command").first() == "gmic_qt_get_cropped_images") { // Parse the message, create the shared memory segments, and create a new message to send back and waid for ack QRectF cropRect(0.0, 0.0, 1.0, 1.0); if (!messageMap.contains("croprect") || messageMap.values("croprect").first().split(',', QString::SkipEmptyParts).size() != 4) { qWarning() << "gmic-qt didn't send a croprect or not a valid croprect"; } else { QStringList cr = messageMap.values("croprect").first().split(',', QString::SkipEmptyParts); cropRect.setX(cr[0].toFloat()); cropRect.setY(cr[1].toFloat()); cropRect.setWidth(cr[2].toFloat()); cropRect.setHeight(cr[3].toFloat()); } if (!prepareCroppedImages(&ba, cropRect, mode)) { qWarning() << "Failed to prepare images for gmic-qt"; } } else if (messageMap.values("command").first() == "gmic_qt_output_images") { // Parse the message. read the shared memory segments, fix up the current image and send an ack qDebug() << "gmic_qt_output_images"; QStringList layers = messageMap.values("layer"); m_outputMode = (OutputMode)mode; if (m_outputMode != IN_PLACE) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented yet.")); m_outputMode = IN_PLACE; } slotStartApplicator(layers); } else if (messageMap.values("command").first() == "gmic_qt_detach") { Q_FOREACH(QSharedMemory *memorySegment, m_sharedMemorySegments) { qDebug() << "detaching" << memorySegment->key() << memorySegment->isAttached(); if (memorySegment->isAttached()) { if (!memorySegment->detach()) { qDebug() << "\t" << memorySegment->error() << memorySegment->errorString(); } } } qDeleteAll(m_sharedMemorySegments); m_sharedMemorySegments.clear(); } else { qWarning() << "Received unknown command" << messageMap.values("command"); } qDebug() << "Sending" << QString::fromUtf8(ba); // HACK: Make sure QDataStream does not refuse to write! // Proper fix: Change the above read to use read transaction ds.resetStatus(); ds.writeBytes(ba.constData(), ba.length()); // Flush the socket because we might not return to the event loop! if (!socket->waitForBytesWritten(2000)) { qWarning() << "Failed to write response:" << socket->error(); } // Wait for the ack bool r = true; r &= socket->waitForReadyRead(2000); // wait for ack r &= (socket->read(qstrlen(ack)) == ack); if (!socket->waitForDisconnected(2000)) { qWarning() << "Remote not disconnected:" << socket->error(); // Wait again socket->disconnectFromServer(); if (socket->waitForDisconnected(2000)) { qWarning() << "Disconnect timed out:" << socket->error(); } } } void QMic::pluginStateChanged(QProcess::ProcessState state) { qDebug() << "stateChanged" << state; } void QMic::pluginFinished(int exitCode, QProcess::ExitStatus exitStatus) { qDebug() << "pluginFinished" << exitCode << exitStatus; delete m_pluginProcess; m_pluginProcess = 0; delete m_localServer; m_localServer = 0; delete m_progressManager; m_progressManager = 0; m_qmicAction->setEnabled(true); m_againAction->setEnabled(true); } void QMic::slotUpdateProgress() { if (!m_gmicApplicator) { qWarning() << "G'Mic applicator already deleted!"; return; } qDebug() << "slotUpdateProgress" << m_gmicApplicator->getProgress(); m_progressManager->updateProgress(m_gmicApplicator->getProgress()); } void QMic::slotStartProgressReporting() { qDebug() << "slotStartProgressReporting();"; if (m_progressManager->inProgress()) { m_progressManager->finishProgress(); } m_progressManager->initProgress(); } void QMic::slotGmicFinished(bool successfully, int milliseconds, const QString &msg) { qDebug() << "slotGmicFinished();" << successfully << milliseconds << msg; if (successfully) { m_gmicApplicator->finish(); } else { m_gmicApplicator->cancel(); QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("G'Mic failed, reason:") + msg); } } void QMic::slotStartApplicator(QStringList gmicImages) { qDebug() << "slotStartApplicator();" << gmicImages; // Create a vector of gmic images QVector *> images; Q_FOREACH(const QString &image, gmicImages) { QStringList parts = image.split(',', QString::SkipEmptyParts); Q_ASSERT(parts.size() == 4); QString key = parts[0]; QString layerName = QByteArray::fromHex(parts[1].toLatin1()); int spectrum = parts[2].toInt(); int width = parts[3].toInt(); int height = parts[4].toInt(); qDebug() << key << layerName << width << height; QSharedMemory m(key); if (!m.attach(QSharedMemory::ReadOnly)) { qWarning() << "Could not attach to shared memory area." << m.error() << m.errorString(); } if (m.isAttached()) { if (!m.lock()) { qDebug() << "Could not lock memeory segment" << m.error() << m.errorString(); } qDebug() << "Memory segment" << key << m.size() << m.constData() << m.data(); gmic_image *gimg = new gmic_image(); gimg->assign(width, height, 1, spectrum); gimg->name = layerName; gimg->_data = new float[width * height * spectrum * sizeof(float)]; qDebug() << "width" << width << "height" << height << "size" << width * height * spectrum * sizeof(float) << "shared memory size" << m.size(); memcpy(gimg->_data, m.constData(), width * height * spectrum * sizeof(float)); qDebug() << "created gmic image" << gimg->name << gimg->_width << gimg->_height; if (!m.unlock()) { qDebug() << "Could not unlock memeory segment" << m.error() << m.errorString(); } if (!m.detach()) { qDebug() << "Could not detach from memeory segment" << m.error() << m.errorString(); } images.append(gimg); } } qDebug() << "Got" << images.size() << "gmic images"; // Start the applicator KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = m_view->image()->root(); KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(m_inputMode); m_gmicApplicator->setProperties(m_view->image(), rootNode, images, actionName, layers); m_gmicApplicator->preview(); m_gmicApplicator->finish(); } bool QMic::prepareCroppedImages(QByteArray *message, QRectF &rc, int inputMode) { m_view->image()->lock(); m_inputMode = (InputLayerMode)inputMode; qDebug() << "prepareCroppedImages()" << QString::fromUtf8(*message) << rc << inputMode; KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP nodes = mapper.inputNodes(m_inputMode); if (nodes->isEmpty()) { m_view->image()->unlock(); return false; } for (int i = 0; i < nodes->size(); ++i) { KisNodeSP node = nodes->at(i); if (node->paintDevice()) { QRect cropRect; KisSelectionSP selection = m_view->image()->globalSelection(); if (selection) { cropRect = selection->selectedExactRect(); } else { cropRect = m_view->image()->bounds(); } qDebug() << "Converting node" << node->name() << cropRect; const QRectF mappedRect = KisAlgebra2D::mapToRect(cropRect).mapRect(rc); const QRect resultRect = mappedRect.toAlignedRect(); QSharedMemory *m = new QSharedMemory(QString("key_%1").arg(QUuid::createUuid().toString())); m_sharedMemorySegments.append(m); if (!m->create(resultRect.width() * resultRect.height() * 4 * sizeof(float))) { //buf.size())) { qWarning() << "Could not create shared memory segment" << m->error() << m->errorString(); return false; } m->lock(); gmic_image img; img.assign(resultRect.width(), resultRect.height(), 1, 4); img._data = reinterpret_cast(m->data()); KisQmicSimpleConvertor::convertToGmicImageFast(node->paintDevice(), &img, resultRect); message->append(m->key().toUtf8()); m->unlock(); - qDebug() << "size" << m->size(); - message->append(","); message->append(node->name().toUtf8().toHex()); message->append(","); message->append(QByteArray::number(resultRect.width())); message->append(","); message->append(QByteArray::number(resultRect.height())); message->append("\n"); } } qDebug() << QString::fromUtf8(*message); m_view->image()->unlock(); return true; } #include "QMic.moc" diff --git a/plugins/extensions/qmic/kis_qmic_applicator.h b/plugins/extensions/qmic/kis_qmic_applicator.h index f9ed05d2b1..84f66b4568 100644 --- a/plugins/extensions/qmic/kis_qmic_applicator.h +++ b/plugins/extensions/qmic/kis_qmic_applicator.h @@ -1,64 +1,64 @@ /* * Copyright (c) 2013-2014 Lukáš Tvrdý #include #include #include "gmic.h" #include "kis_qmic_data.h" class KisProcessingApplicator; class KisQmicApplicator : public QObject { Q_OBJECT public: KisQmicApplicator(); ~KisQmicApplicator(); void setProperties(KisImageWSP image, KisNodeSP node, QVector *> images, const KUndo2MagicString &actionName, KisNodeListSP kritaNodes); void preview(); void cancel(); void finish(); float getProgress() const; Q_SIGNALS: - void gmicFinished(bool successfully, int miliseconds = -1, const QString &msg = QString()); + void gmicFinished(bool successfully, int milliseconds = -1, const QString &msg = QString()); private: KisProcessingApplicator *m_applicator; KisImageWSP m_image; KisNodeSP m_node; KUndo2MagicString m_actionName; KisNodeListSP m_kritaNodes; bool m_applicatorStrokeEnded; QVector *> m_images; KisQmicDataSP m_gmicData; }; #endif diff --git a/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp b/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp index a0dab1b9c3..fb9ea32d7c 100644 --- a/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp +++ b/plugins/extensions/qmic/kis_qmic_simple_convertor.cpp @@ -1,880 +1,880 @@ /* * Copyright (c) 2013 Lukáš Tvrdý #include #include #include #include #include #include #define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v ) #define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v ) template class KisColorToFloatConvertor : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorToFloatConvertor(float gmicUnitValue = 255.0f) : m_gmicUnitValue(gmicUnitValue) {} float m_gmicUnitValue; void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { float gmicUnitValue2KritaUnitValue = m_gmicUnitValue / KoColorSpaceMathsTraits::unitValue; const RGBPixel* srcPixel = reinterpret_cast(src); KoRgbF32Traits::Pixel* dstPixel = reinterpret_cast(dst); while(nPixels > 0) { dstPixel->red = SCALE_TO_FLOAT(srcPixel->red) * gmicUnitValue2KritaUnitValue; dstPixel->green = SCALE_TO_FLOAT(srcPixel->green) * gmicUnitValue2KritaUnitValue; dstPixel->blue = SCALE_TO_FLOAT(srcPixel->blue) * gmicUnitValue2KritaUnitValue; dstPixel->alpha = SCALE_TO_FLOAT(srcPixel->alpha) * gmicUnitValue2KritaUnitValue; --nPixels; ++srcPixel; ++dstPixel; } } }; template class KisColorFromFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromFloat(float gmicUnitValue = 255.0f) : m_gmicUnitValue(gmicUnitValue) { } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; while(nPixels > 0) { dstPixel->red = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->green = SCALE_FROM_FLOAT(srcPixel->green * gmicUnitValue2KritaUnitValue); dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->blue * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->alpha * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; template class KisColorFromGrayScaleFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromGrayScaleFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; // warning: green and blue channels on input contain random data!!! see that we copy only one channel // when gmic image has grayscale colorspace while(nPixels > 0) { dstPixel->red = dstPixel->green = dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->alpha * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; template class KisColorFromGrayScaleAlphaFloat : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisColorFromGrayScaleAlphaFloat(float gmicUnitValue = 255.0f):m_gmicUnitValue(gmicUnitValue){} void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { const KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(src); RGBPixel* dstPixel = reinterpret_cast(dst); float gmicUnitValue2KritaUnitValue = KoColorSpaceMathsTraits::unitValue / m_gmicUnitValue; // warning: green and blue channels on input contain random data!!! see that we copy only one channel // when gmic image has grayscale colorspace while(nPixels > 0) { dstPixel->red = dstPixel->green = dstPixel->blue = SCALE_FROM_FLOAT(srcPixel->red * gmicUnitValue2KritaUnitValue); dstPixel->alpha = SCALE_FROM_FLOAT(srcPixel->green * gmicUnitValue2KritaUnitValue); --nPixels; ++srcPixel; ++dstPixel; } } private: float m_gmicUnitValue; }; static KoColorTransformation* createTransformationFromGmic(const KoColorSpace* colorSpace, quint32 gmicSpectrum,float gmicUnitValue) { KoColorTransformation * colorTransformation = 0; if (colorSpace->colorModelId() != RGBAColorModelID) { - dbgKrita << "Unsupported color space for fast pixel tranformation to gmic pixel format" << colorSpace->id(); + dbgKrita << "Unsupported color space for fast pixel transformation to gmic pixel format" << colorSpace->id(); return 0; } if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< float, KoRgbTraits < float > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat >(gmicUnitValue); } } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< half, KoRgbTraits < half > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< half, KoRgbTraits < half > >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< half, KoRgbTraits < half > >(gmicUnitValue); } } #endif else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< quint16, KoBgrTraits < quint16 > >(gmicUnitValue); } } else if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { if (gmicSpectrum == 3 || gmicSpectrum == 4) { colorTransformation = new KisColorFromFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } else if (gmicSpectrum == 1) { colorTransformation = new KisColorFromGrayScaleFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } else if (gmicSpectrum == 2) { colorTransformation = new KisColorFromGrayScaleAlphaFloat< quint8, KoBgrTraits < quint8 > >(gmicUnitValue); } } else { - dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel tranformation to gmic pixel format"; + dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel transformation to gmic pixel format"; return 0; } return colorTransformation; } static KoColorTransformation* createTransformation(const KoColorSpace* colorSpace) { KoColorTransformation * colorTransformation = 0; if (colorSpace->colorModelId() != RGBAColorModelID) { - dbgKrita << "Unsupported color space for fast pixel tranformation to gmic pixel format" << colorSpace->id(); + dbgKrita << "Unsupported color space for fast pixel transformation to gmic pixel format" << colorSpace->id(); return 0; } if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< float, KoRgbTraits < float > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< quint16, KoBgrTraits < quint16 > >(); } else if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { colorTransformation = new KisColorToFloatConvertor< quint8, KoBgrTraits < quint8 > >(); } else { - dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel tranformation to gmic pixel format"; + dbgKrita << "Unsupported color space " << colorSpace->id() << " for fast pixel transformation to gmic pixel format"; return 0; } return colorTransformation; } void KisQmicSimpleConvertor::convertFromGmicFast(gmic_image& gmicImage, KisPaintDeviceSP dst, float gmicUnitValue) { const KoColorSpace * dstColorSpace = dst->colorSpace(); KoColorTransformation * gmicToDstPixelFormat = createTransformationFromGmic(dstColorSpace,gmicImage._spectrum,gmicUnitValue); if (gmicToDstPixelFormat == 0) { dbgPlugins << "Fall-back to slow color conversion"; convertFromGmicImage(gmicImage, dst, gmicUnitValue); return; } qint32 x = 0; qint32 y = 0; qint32 width = gmicImage._width; qint32 height = gmicImage._height; width = width < 0 ? 0 : width; height = height < 0 ? 0 : height; const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); // this function always convert to rgba or rgb with various color depth quint32 dstNumChannels = rgbaFloat32bitcolorSpace->channelCount(); // number of channels that we will copy quint32 numChannels = gmicImage._spectrum; // gmic image has 4, 3, 2, 1 channel QVector planes(dstNumChannels); int channelOffset = gmicImage._width * gmicImage._height; for (unsigned int channelIndex = 0; channelIndex < gmicImage._spectrum; channelIndex++) { planes[channelIndex] = gmicImage._data + channelOffset * channelIndex; } for (unsigned int channelIndex = gmicImage._spectrum; channelIndex < dstNumChannels; channelIndex++) { planes[channelIndex] = 0; //turn off } qint32 dataY = 0; qint32 imageY = y; qint32 rowsRemaining = height; const qint32 floatPixelSize = rgbaFloat32bitcolorSpace->pixelSize(); KisRandomAccessorSP it = dst->createRandomAccessorNG(dst->x(), dst->y()); // 0,0 int tileWidth = it->numContiguousColumns(dst->x()); int tileHeight = it->numContiguousRows(dst->y()); Q_ASSERT(tileWidth == 64); Q_ASSERT(tileHeight == 64); quint8 * convertedTile = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * tileWidth * tileHeight]; // grayscale and rgb case does not have alpha, so let's fill 4th channel of rgba tile with opacity opaque if (gmicImage._spectrum == 1 || gmicImage._spectrum == 3) { quint32 nPixels = tileWidth * tileHeight; quint32 pixelIndex = 0; KoRgbF32Traits::Pixel* srcPixel = reinterpret_cast(convertedTile); while (pixelIndex < nPixels) { srcPixel->alpha = gmicUnitValue; ++srcPixel; ++pixelIndex; } } while (rowsRemaining > 0) { qint32 dataX = 0; qint32 imageX = x; qint32 columnsRemaining = width; qint32 numContiguousImageRows = it->numContiguousRows(imageY); qint32 rowsToWork = qMin(numContiguousImageRows, rowsRemaining); while (columnsRemaining > 0) { qint32 numContiguousImageColumns = it->numContiguousColumns(imageX); qint32 columnsToWork = qMin(numContiguousImageColumns, columnsRemaining); const qint32 dataIdx = dataX + dataY * width; const qint32 tileRowStride = (tileWidth - columnsToWork) * floatPixelSize; quint8 *tileItStart = convertedTile; // copy gmic channels to float tile qint32 channelSize = sizeof(float); for(quint32 i=0; imoveTo(imageX, imageY); quint8 *dstTileItStart = it->rawData(); tileItStart = convertedTile; // back to the start of the converted tile // copy float tile to dst colorspace based on input colorspace (rgb or grayscale) for (qint32 row = 0; row < rowsToWork; row++) { gmicToDstPixelFormat->transform(tileItStart, dstTileItStart, columnsToWork); dstTileItStart += dstColorSpace->pixelSize() * tileWidth; tileItStart += floatPixelSize * tileWidth; } imageX += columnsToWork; dataX += columnsToWork; columnsRemaining -= columnsToWork; } imageY += rowsToWork; dataY += rowsToWork; rowsRemaining -= rowsToWork; } delete [] convertedTile; delete gmicToDstPixelFormat; } void KisQmicSimpleConvertor::convertToGmicImageFast(KisPaintDeviceSP dev, gmic_image *gmicImage, QRect rc) { KoColorTransformation * pixelToGmicPixelFormat = createTransformation(dev->colorSpace()); if (pixelToGmicPixelFormat == 0) { qDebug() << "Fall-back to slow color conversion method"; convertToGmicImage(dev, gmicImage, rc); return; } if (rc.isEmpty()) { qDebug() << "Image rectangle is empty! Using supplied gmic layer dimension"; rc = QRect(0, 0, gmicImage->_width, gmicImage->_height); } qint32 x = rc.x(); qint32 y = rc.y(); qint32 width = rc.width(); qint32 height = rc.height(); width = width < 0 ? 0 : width; height = height < 0 ? 0 : height; const qint32 numChannels = 4; int greenOffset = gmicImage->_width * gmicImage->_height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; QVector planes; planes.append(gmicImage->_data); planes.append(gmicImage->_data + greenOffset); planes.append(gmicImage->_data + blueOffset); planes.append(gmicImage->_data + alphaOffset); KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(dev->x(), dev->y()); int tileWidth = it->numContiguousColumns(dev->x()); int tileHeight = it->numContiguousRows(dev->y()); Q_ASSERT(tileWidth == 64); Q_ASSERT(tileHeight == 64); const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); Q_CHECK_PTR(rgbaFloat32bitcolorSpace); const qint32 dstPixelSize = rgbaFloat32bitcolorSpace->pixelSize(); const qint32 srcPixelSize = dev->pixelSize(); quint8 * dstTile = new quint8[dstPixelSize * tileWidth * tileHeight]; qint32 dataY = 0; qint32 imageX = x; qint32 imageY = y; it->moveTo(imageX, imageY); qint32 rowsRemaining = height; while (rowsRemaining > 0) { qint32 dataX = 0; imageX = x; qint32 columnsRemaining = width; qint32 numContiguousImageRows = it->numContiguousRows(imageY); qint32 rowsToWork = qMin(numContiguousImageRows, rowsRemaining); qint32 convertedTileY = tileHeight - rowsToWork; Q_ASSERT(convertedTileY >= 0); while (columnsRemaining > 0) { qint32 numContiguousImageColumns = it->numContiguousColumns(imageX); qint32 columnsToWork = qMin(numContiguousImageColumns, columnsRemaining); qint32 convertedTileX = tileWidth - columnsToWork; Q_ASSERT(convertedTileX >= 0); const qint32 dataIdx = dataX + dataY * width; const qint32 dstTileIndex = convertedTileX + convertedTileY * tileWidth; const qint32 tileRowStride = (tileWidth - columnsToWork) * dstPixelSize; const qint32 srcTileRowStride = (tileWidth - columnsToWork) * srcPixelSize; it->moveTo(imageX, imageY); quint8 *tileItStart = dstTile + dstTileIndex * dstPixelSize; // transform tile row by row quint8 *dstTileIt = tileItStart; quint8 *srcTileIt = const_cast(it->rawDataConst()); qint32 row = rowsToWork; while (row > 0) { pixelToGmicPixelFormat->transform(srcTileIt, dstTileIt , columnsToWork); srcTileIt += columnsToWork * srcPixelSize; srcTileIt += srcTileRowStride; dstTileIt += columnsToWork * dstPixelSize; dstTileIt += tileRowStride; row--; } // here we want to copy floats to dstTile, so tileItStart has to point to float buffer qint32 channelSize = sizeof(float); for(qint32 i = 0; i< numChannels; i++) { float * planeIt = planes[i] + dataIdx; qint32 dataStride = (width - columnsToWork); quint8* tileIt = tileItStart; for (qint32 row = 0; row < rowsToWork; row++) { for (int col = 0; col < columnsToWork; col++) { memcpy(planeIt, tileIt, channelSize); tileIt += dstPixelSize; planeIt += 1; } tileIt += tileRowStride; planeIt += dataStride; } // skip channel in tile: red, green, blue, alpha tileItStart += channelSize; } imageX += columnsToWork; dataX += columnsToWork; columnsRemaining -= columnsToWork; } imageY += rowsToWork; dataY += rowsToWork; rowsRemaining -= rowsToWork; } delete [] dstTile; delete pixelToGmicPixelFormat; } // gmic assumes float rgba in 0.0 - 255.0 void KisQmicSimpleConvertor::convertToGmicImage(KisPaintDeviceSP dev, gmic_image *gmicImage, QRect rc) { Q_ASSERT(!dev.isNull()); Q_ASSERT(gmicImage->_spectrum == 4); // rgba if (rc.isEmpty()) { rc = QRect(0, 0, gmicImage->_width, gmicImage->_height); } const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); Q_CHECK_PTR(rgbaFloat32bitcolorSpace); KoColorTransformation *pixelToGmicPixelFormat = createTransformation(rgbaFloat32bitcolorSpace); int greenOffset = gmicImage->_width * gmicImage->_height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); const KoColorSpace * colorSpace = dev->colorSpace(); KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG(0,0); int optimalBufferSize = 64; // most common numContiguousColumns, tile size? quint8 * floatRGBApixel = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * optimalBufferSize]; quint32 pixelSize = rgbaFloat32bitcolorSpace->pixelSize(); int pos = 0; for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(rc.x() + x, rc.y() + y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(rc.x() + x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); colorSpace->convertPixelsTo(it->rawDataConst(), floatRGBApixel, rgbaFloat32bitcolorSpace, numContiguousColumns, renderingIntent, conversionFlags); pixelToGmicPixelFormat->transform(floatRGBApixel, floatRGBApixel, numContiguousColumns); pos = y * gmicImage->_width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { memcpy(gmicImage->_data + pos ,floatRGBApixel + bx * pixelSize , 4); memcpy(gmicImage->_data + pos + greenOffset ,floatRGBApixel + bx * pixelSize + 4, 4); memcpy(gmicImage->_data + pos + blueOffset ,floatRGBApixel + bx * pixelSize + 8, 4); memcpy(gmicImage->_data + pos + alphaOffset ,floatRGBApixel + bx * pixelSize + 12, 4); pos++; } x += numContiguousColumns; } } delete [] floatRGBApixel; delete pixelToGmicPixelFormat; } void KisQmicSimpleConvertor::convertFromGmicImage(gmic_image& gmicImage, KisPaintDeviceSP dst, float gmicMaxChannelValue) { Q_ASSERT(!dst.isNull()); const KoColorSpace *rgbaFloat32bitcolorSpace = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->rgb8()->profile()); const KoColorSpace *dstColorSpace = dst->colorSpace(); if (dstColorSpace == 0) { dstColorSpace = rgbaFloat32bitcolorSpace; } KisPaintDeviceSP dev = dst; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; QRect rc(0,0,gmicImage._width, gmicImage._height); KisRandomAccessorSP it = dev->createRandomAccessorNG(0,0); int pos; float r,g,b,a; int optimalBufferSize = 64; // most common numContiguousColumns, tile size? quint8 * floatRGBApixel = new quint8[rgbaFloat32bitcolorSpace->pixelSize() * optimalBufferSize]; quint32 pixelSize = rgbaFloat32bitcolorSpace->pixelSize(); KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(); KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); // Krita needs rgba in 0.0...1.0 float multiplied = KoColorSpaceMathsTraits::unitValue / gmicMaxChannelValue; switch (gmicImage._spectrum) { case 1: { // convert grayscale to rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = g = b = gmicImage._data[pos] * multiplied; a = KoColorSpaceMathsTraits::unitValue; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 2: { // convert grayscale alpha to rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = g = b = gmicImage._data[pos] * multiplied; a = gmicImage._data[pos + greenOffset] * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 3: { // convert rgb -> rgba for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = gmicImage._data[pos] * multiplied; g = gmicImage._data[pos + greenOffset] * multiplied; b = gmicImage._data[pos + blueOffset ] * multiplied; a = gmicMaxChannelValue * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } case 4: { for (int y = 0; y < rc.height(); y++) { int x = 0; while (x < rc.width()) { it->moveTo(x, y); qint32 numContiguousColumns = qMin(it->numContiguousColumns(x), optimalBufferSize); numContiguousColumns = qMin(numContiguousColumns, rc.width() - x); pos = y * gmicImage._width + x; for (qint32 bx = 0; bx < numContiguousColumns; bx++) { r = gmicImage._data[pos] * multiplied; g = gmicImage._data[pos + greenOffset] * multiplied; b = gmicImage._data[pos + blueOffset ] * multiplied; a = gmicImage._data[pos + alphaOffset] * multiplied; memcpy(floatRGBApixel + bx * pixelSize, &r,4); memcpy(floatRGBApixel + bx * pixelSize + 4, &g,4); memcpy(floatRGBApixel + bx * pixelSize + 8, &b,4); memcpy(floatRGBApixel + bx * pixelSize + 12, &a,4); pos++; } rgbaFloat32bitcolorSpace->convertPixelsTo(floatRGBApixel, it->rawData(), dstColorSpace, numContiguousColumns,renderingIntent, conversionFlags); x += numContiguousColumns; } } break; } default: { dbgPlugins << "Unsupported gmic output format : " << gmicImage._width << gmicImage._height << gmicImage._depth << gmicImage._spectrum; } } } QImage KisQmicSimpleConvertor::convertToQImage(gmic_image& gmicImage, float gmicActualMaxChannelValue) { QImage image = QImage(gmicImage._width, gmicImage._height, QImage::Format_ARGB32); dbgPlugins << image.format() <<"first pixel:"<< gmicImage._data[0] << gmicImage._width << gmicImage._height << gmicImage._spectrum; int greenOffset = gmicImage._width * gmicImage._height; int blueOffset = greenOffset * 2; int pos = 0; // always put 255 to qimage float multiplied = 255.0f / gmicActualMaxChannelValue; for (unsigned int y = 0; y < gmicImage._height; y++) { QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (unsigned int x = 0; x < gmicImage._width; x++) { pos = y * gmicImage._width + x; float r = gmicImage._data[pos] * multiplied; float g = gmicImage._data[pos + greenOffset] * multiplied; float b = gmicImage._data[pos + blueOffset] * multiplied; pixel[x] = qRgb(int(r),int(g), int(b)); } } return image; } void KisQmicSimpleConvertor::convertFromQImage(const QImage &image, gmic_image *gmicImage, float gmicUnitValue) { int greenOffset = gmicImage->_width * gmicImage->_height; int blueOffset = greenOffset * 2; int alphaOffset = greenOffset * 3; int pos = 0; // QImage has 0..255 float qimageUnitValue = 255.0f; float multiplied = gmicUnitValue / qimageUnitValue; Q_ASSERT(image.width() == int(gmicImage->_width)); Q_ASSERT(image.height() == int(gmicImage->_height)); Q_ASSERT(image.format() == QImage::Format_ARGB32); switch (gmicImage->_spectrum) { case 1: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qGray(pixel[x]) * multiplied; } } break; } case 2: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qGray(pixel[x]) * multiplied; gmicImage->_data[pos + greenOffset] = qAlpha(pixel[x]) * multiplied; } } break; } case 3: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qRed(pixel[x]) * multiplied; gmicImage->_data[pos + greenOffset] = qGreen(pixel[x]) * multiplied; gmicImage->_data[pos + blueOffset] = qBlue(pixel[x]) * multiplied; } } break; } case 4: { for (int y = 0; y < image.height(); y++) { const QRgb *pixel = reinterpret_cast(image.scanLine(y)); for (int x = 0; x < image.width(); x++) { pos = y * gmicImage->_width + x; gmicImage->_data[pos] = qRed(pixel[x]) * multiplied; gmicImage->_data[pos + greenOffset] = qGreen(pixel[x]) * multiplied; gmicImage->_data[pos + blueOffset] = qBlue(pixel[x]) * multiplied; gmicImage->_data[pos + alphaOffset] = qAlpha(pixel[x]) * multiplied; } } break; } default: { Q_ASSERT(false); dbgKrita << "Unexpected gmic image format"; break; } } } diff --git a/plugins/flake/textshape/kotext/KoTextEditor_p.h b/plugins/flake/textshape/kotext/KoTextEditor_p.h index 002a9a7a74..56b3f89cb7 100644 --- a/plugins/flake/textshape/kotext/KoTextEditor_p.h +++ b/plugins/flake/textshape/kotext/KoTextEditor_p.h @@ -1,264 +1,264 @@ /* This file is part of the KDE project * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2009 Thomas Zander * Copyright (C) 2015 Soma Schliszka * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; 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 KOTEXTEDITOR_P_H #define KOTEXTEDITOR_P_H #include "KoTextEditor.h" #include "KoTextDocument.h" #include "styles/KoParagraphStyle.h" #include "styles/KoStyleManager.h" #include "changetracker/KoChangeTracker.h" #include #include #include #include #include #include #include class KUndo2Command; class Q_DECL_HIDDEN KoTextEditor::Private { public: enum State { NoOp, KeyPress, Delete, Format, Custom }; explicit Private(KoTextEditor *qq, QTextDocument *document); ~Private() {} void documentCommandAdded(); void updateState(State newState, const KUndo2MagicString &title = KUndo2MagicString()); void newLine(KUndo2Command *parent); void clearCharFormatProperty(int propertyId); void emitTextFormatChanged(); KoTextEditor *q; QTextCursor caret; QTextDocument *document; QStack commandStack; bool addNewCommand; bool dummyMacroAdded; int customCommandCount; KUndo2MagicString commandTitle; State editorState; bool editProtected; bool editProtectionCached; }; class KoTextVisitor { public: /// The ObjectVisitingMode enum marks how was the visited object selected. enum ObjectVisitingMode { Partly, /// The visited object (table, cell, ...) is just @b partly selected. (Eg. just one cell is selected in the visited table) Entirely, /// The visited object (table, cell, ...) is @b entirely selected. }; explicit KoTextVisitor(KoTextEditor *editor) : m_abortVisiting(false) , m_editor(editor) { } virtual ~KoTextVisitor() {} // called whenever a visit was prevented by editprotection virtual void nonVisit() {} virtual void visitFragmentSelection(QTextCursor &) { } /** * This method allows to perform custom operation when the visitor reaches a QTextTable * @param visitedTable pointer to the currenlty visited table object * @param visitingMode flag, marks if the table is just partly visited or entirely */ virtual void visitTable(QTextTable *visitedTable, ObjectVisitingMode visitingMode) { Q_UNUSED(visitedTable); Q_UNUSED(visitingMode); } /** * This method allows to perform custom operation when the visitor reaches a QTextTableCell * @param visitedTable pointer to the currenlty visited cell object * @param visitingMode flag, marks if the cell is just partly visited or entirely */ virtual void visitTableCell(QTextTableCell *visitedCell, ObjectVisitingMode visitingMode) { Q_UNUSED(visitedCell); Q_UNUSED(visitingMode); } // The default implementation calls visitFragmentSelection on each fragment.intersect.selection virtual void visitBlock(QTextBlock &block, const QTextCursor &caret) { for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextCursor fragmentSelection(caret); fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position())); fragmentSelection.setPosition(qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor); if (fragmentSelection.anchor() >= fragmentSelection.position()) { continue; } visitFragmentSelection(fragmentSelection); } } bool abortVisiting() { return m_abortVisiting;} void setAbortVisiting(bool abort) {m_abortVisiting = abort;} KoTextEditor * editor() const {return m_editor;} private: bool m_abortVisiting; KoTextEditor *m_editor; }; class BlockFormatVisitor { public: BlockFormatVisitor() {} virtual ~BlockFormatVisitor() {} virtual void visit(QTextBlock &block) const = 0; static void visitSelection(KoTextEditor *editor, const BlockFormatVisitor &visitor, const KUndo2MagicString &title = kundo2_i18n("Format"), bool resetProperties = false, bool registerChange = true) { int start = qMin(editor->position(), editor->anchor()); int end = qMax(editor->position(), editor->anchor()); QTextBlock block = editor->block(); if (block.position() > start) block = block.document()->findBlock(start); // now loop over all blocks that the selection contains and alter the text fragments where applicable. while (block.isValid() && block.position() <= end) { QTextBlockFormat prevFormat = block.blockFormat(); if (resetProperties) { if (KoTextDocument(editor->document()).styleManager()) { KoParagraphStyle *old = KoTextDocument(editor->document()).styleManager()->paragraphStyle(block.blockFormat().intProperty(KoParagraphStyle::StyleId)); if (old) old->unapplyStyle(block); } } visitor.visit(block); QTextCursor cursor(block); QTextBlockFormat format = cursor.blockFormat(); if (registerChange) editor->registerTrackedChange(cursor, KoGenChange::FormatChange, title, format, prevFormat, true); block = block.next(); } } }; class CharFormatVisitor { public: CharFormatVisitor() {} virtual ~CharFormatVisitor() {} virtual void visit(QTextCharFormat &format) const = 0; static void visitSelection(KoTextEditor *editor, const CharFormatVisitor &visitor, const KUndo2MagicString &title = kundo2_i18n("Format"), bool registerChange = true) { int start = qMin(editor->position(), editor->anchor()); int end = qMax(editor->position(), editor->anchor()); if (start == end) { // just set a new one. QTextCharFormat format = editor->charFormat(); visitor.visit(format); if (registerChange && KoTextDocument(editor->document()).changeTracker() && KoTextDocument(editor->document()).changeTracker()->recordChanges()) { QTextCharFormat prevFormat(editor->charFormat()); int changeId = KoTextDocument(editor->document()).changeTracker()->getFormatChangeId(title, format, prevFormat, editor->charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt()); format.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } editor->cursor()->setCharFormat(format); return; } QTextBlock block = editor->block(); if (block.position() > start) block = block.document()->findBlock(start); QList cursors; QList formats; // now loop over all blocks that the selection contains and alter the text fragments where applicable. while (block.isValid() && block.position() < end) { QTextBlock::iterator iter = block.begin(); while (! iter.atEnd()) { QTextFragment fragment = iter.fragment(); if (fragment.position() > end) break; if (fragment.position() + fragment.length() <= start) { ++iter; continue; } QTextCursor cursor(block); cursor.setPosition(fragment.position() + 1); - QTextCharFormat format = cursor.charFormat(); // this gets the format one char after the postion. + QTextCharFormat format = cursor.charFormat(); // this gets the format one char after the position. visitor.visit(format); if (registerChange && KoTextDocument(editor->document()).changeTracker() && KoTextDocument(editor->document()).changeTracker()->recordChanges()) { QTextCharFormat prevFormat(cursor.charFormat()); int changeId = KoTextDocument(editor->document()).changeTracker()->getFormatChangeId(title, format, prevFormat, cursor.charFormat().property( KoCharacterStyle::ChangeTrackerId ).toInt()); format.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); } cursor.setPosition(qMax(start, fragment.position())); int to = qMin(end, fragment.position() + fragment.length()); cursor.setPosition(to, QTextCursor::KeepAnchor); cursors.append(cursor); formats.append(format); QTextCharFormat prevFormat(cursor.charFormat()); if (registerChange) editor->registerTrackedChange(cursor,KoGenChange::FormatChange,title, format, prevFormat, false); //this will lead to every fragment having a different change untill the change merging in registerTrackedChange checks also for formatChange or not? ++iter; } block = block.next(); } QList::Iterator iter = formats.begin(); Q_FOREACH (QTextCursor cursor, cursors) { cursor.setCharFormat(*iter); ++iter; } } }; #endif //KOTEXTEDITOR_P_H diff --git a/plugins/flake/textshape/kotext/TextDebug.cpp b/plugins/flake/textshape/kotext/TextDebug.cpp index d862bb1987..0bb3587900 100644 --- a/plugins/flake/textshape/kotext/TextDebug.cpp +++ b/plugins/flake/textshape/kotext/TextDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TextDebug.h" const QLoggingCategory &TEXT_LOG() \ { - static const QLoggingCategory category("krita.lib.text"); + static const QLoggingCategory category("krita.lib.text", QtInfoMsg); return category; } diff --git a/plugins/flake/textshape/textlayout/TextLayoutDebug.cpp b/plugins/flake/textshape/textlayout/TextLayoutDebug.cpp index 0d8800dd71..95a95c04f3 100644 --- a/plugins/flake/textshape/textlayout/TextLayoutDebug.cpp +++ b/plugins/flake/textshape/textlayout/TextLayoutDebug.cpp @@ -1,27 +1,27 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "TextLayoutDebug.h" const QLoggingCategory &TEXT_LAYOUT_LOG() \ { - static const QLoggingCategory category("krita.lib.textlayout"); + static const QLoggingCategory category("krita.lib.textlayout", QtInfoMsg); return category; } diff --git a/plugins/impex/psd/psd_layer_section.cpp b/plugins/impex/psd/psd_layer_section.cpp index deb3f9f1fb..a9efb0ac23 100644 --- a/plugins/impex/psd/psd_layer_section.cpp +++ b/plugins/impex/psd/psd_layer_section.cpp @@ -1,588 +1,588 @@ /* * Copyright (c) 2009 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "psd_layer_section.h" #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include "psd_header.h" #include "psd_utils.h" #include "compression.h" #include #include #include #include PSDLayerMaskSection::PSDLayerMaskSection(const PSDHeader& header) : globalInfoSection(header), m_header(header) { hasTransparency = false; layerMaskBlockSize = 0; nLayers = 0; } PSDLayerMaskSection::~PSDLayerMaskSection() { qDeleteAll(layers); } bool PSDLayerMaskSection::read(QIODevice* io) { bool retval = true; // be optimistic! <:-) try { retval = readImpl(io); } catch (KisAslReaderUtils::ASLParseException &e) { warnKrita << "WARNING: PSD (emb. pattern):" << e.what(); retval = false; } return retval; } bool PSDLayerMaskSection::readLayerInfoImpl(QIODevice* io) { quint32 layerInfoSectionSize = 0; SAFE_READ_EX(io, layerInfoSectionSize); if (layerInfoSectionSize & 0x1) { warnKrita << "WARNING: layerInfoSectionSize is NOT even! Fixing..."; layerInfoSectionSize++; } { SETUP_OFFSET_VERIFIER(layerInfoSectionTag, io, layerInfoSectionSize, 0); dbgFile << "Layer info block size" << layerInfoSectionSize; if (layerInfoSectionSize > 0 ) { if (!psdread(io, &nLayers) || nLayers == 0) { error = QString("Could not read read number of layers or no layers in image. %1").arg(nLayers); return false; } hasTransparency = nLayers < 0; // first alpha channel is the alpha channel of the projection. nLayers = qAbs(nLayers); dbgFile << "Number of layers:" << nLayers; dbgFile << "Has separate projection transparency:" << hasTransparency; for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to read layer" << i << "pos" << io->pos(); dbgFile << "== Enter PSDLayerRecord"; PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); if (!layerRecord->read(io)) { error = QString("Could not load layer %1: %2").arg(i).arg(layerRecord->error); return false; } dbgFile << "== Leave PSDLayerRecord"; dbgFile << "Finished reading layer" << i << layerRecord->layerName << "blending mode" << layerRecord->blendModeKey << io->pos() << "Number of channels:" << layerRecord->channelInfoRecords.size(); layers << layerRecord; } } // get the positions for the channels belonging to each layer for (int i = 0; i < nLayers; ++i) { dbgFile << "Going to seek channel positions for layer" << i << "pos" << io->pos(); if (i > layers.size()) { error = QString("Expected layer %1, but only have %2 layers").arg(i).arg(layers.size()); return false; } PSDLayerRecord *layerRecord = layers.at(i); for (int j = 0; j < layerRecord->nChannels; ++j) { // save the current location so we can jump beyond this block later on. quint64 channelStartPos = io->pos(); dbgFile << "\tReading channel image data for channel" << j << "from pos" << io->pos(); KIS_ASSERT_RECOVER(j < layerRecord->channelInfoRecords.size()) { return false; } ChannelInfo* channelInfo = layerRecord->channelInfoRecords.at(j); quint16 compressionType; if (!psdread(io, &compressionType)) { error = "Could not read compression type for channel"; return false; } channelInfo->compressionType = (Compression::CompressionType)compressionType; dbgFile << "\t\tChannel" << j << "has compression type" << compressionType; QRect channelRect = layerRecord->channelRect(channelInfo); // read the rle row lengths; if (channelInfo->compressionType == Compression::RLE) { for(qint64 row = 0; row < channelRect.height(); ++row) { //dbgFile << "Reading the RLE bytecount position of row" << row << "at pos" << io->pos(); quint32 byteCount; if (m_header.version == 1) { quint16 _byteCount; if (!psdread(io, &_byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } byteCount = _byteCount; } else { if (!psdread(io, &byteCount)) { error = QString("Could not read byteCount for rle-encoded channel"); return 0; } } ////dbgFile << "rle byte count" << byteCount; channelInfo->rleRowLengths << byteCount; } } // we're beyond all the length bytes, rle bytes and whatever, this is the // location of the real pixel data channelInfo->channelDataStart = io->pos(); dbgFile << "\t\tstart" << channelStartPos << "data start" << channelInfo->channelDataStart << "data length" << channelInfo->channelDataLength << "pos" << io->pos(); // make sure we are at the start of the next channel data block io->seek(channelStartPos + channelInfo->channelDataLength); // this is the length of the actual channel data bytes channelInfo->channelDataLength = channelInfo->channelDataLength - (channelInfo->channelDataStart - channelStartPos); dbgFile << "\t\tchannel record" << j << "for layer" << i << "with id" << channelInfo->channelId - << "starting postion" << channelInfo->channelDataStart + << "starting position" << channelInfo->channelDataStart << "with length" << channelInfo->channelDataLength << "and has compression type" << channelInfo->compressionType; } } } return true; } bool PSDLayerMaskSection::readImpl(QIODevice* io) { dbgFile << "reading layer section. Pos:" << io->pos() << "bytes left:" << io->bytesAvailable(); layerMaskBlockSize = 0; if (m_header.version == 1) { quint32 _layerMaskBlockSize = 0; if (!psdread(io, &_layerMaskBlockSize) || _layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(_layerMaskBlockSize).arg(io->bytesAvailable()); return false; } layerMaskBlockSize = _layerMaskBlockSize; } else if (m_header.version == 2) { if (!psdread(io, &layerMaskBlockSize) || layerMaskBlockSize > (quint64)io->bytesAvailable()) { error = QString("Could not read layer + mask block size. Got %1. Bytes left %2") .arg(layerMaskBlockSize).arg(io->bytesAvailable()); return false; } } quint64 start = io->pos(); dbgFile << "layer + mask section size" << layerMaskBlockSize; if (layerMaskBlockSize == 0) { dbgFile << "No layer + mask info, so no layers, only a background layer"; return true; } if (!readLayerInfoImpl(io)) { return false; } quint32 globalMaskBlockLength; if (!psdread(io, &globalMaskBlockLength)) { error = "Could not read global mask info block"; return false; } if (globalMaskBlockLength > 0) { if (!psdread(io, &globalLayerMaskInfo.overlayColorSpace)) { error = "Could not read global mask info overlay colorspace"; return false; } for (int i = 0; i < 4; ++i) { if (!psdread(io, &globalLayerMaskInfo.colorComponents[i])) { error = QString("Could not read mask info visualizaion color component %1").arg(i); return false; } } if (!psdread(io, &globalLayerMaskInfo.opacity)) { error = "Could not read global mask info visualization opacity"; return false; } if (!psdread(io, &globalLayerMaskInfo.kind)) { error = "Could not read global mask info visualization type"; return false; } } // global additional sections /** * Newer versions of PSD have layers info block wrapped into * 'Lr16' or 'Lr32' additional section, while the main block is * absent. * * Here we pass the callback which should be used when such * additional section is recognized. */ globalInfoSection.setExtraLayerInfoBlockHandler(std::bind(&PSDLayerMaskSection::readLayerInfoImpl, this, std::placeholders::_1)); globalInfoSection.read(io); /* put us after this section so reading the next section will work even if we mess up */ io->seek(start + layerMaskBlockSize); return true; } struct FlattenedNode { FlattenedNode() : type(RASTER_LAYER) {} KisNodeSP node; enum Type { RASTER_LAYER, FOLDER_OPEN, FOLDER_CLOSED, SECTION_DIVIDER }; Type type; }; void addBackgroundIfNeeded(KisNodeSP root, QList &nodes) { KisGroupLayer *group = dynamic_cast(root.data()); if (!group) return; KoColor projectionColor = group->defaultProjectionColor(); if (projectionColor.opacityU8() == OPACITY_TRANSPARENT_U8) return; KisPaintLayerSP layer = new KisPaintLayer(group->image(), i18nc("Automatically created layer name when saving into PSD", "Background"), OPACITY_OPAQUE_U8); layer->paintDevice()->setDefaultPixel(projectionColor); { FlattenedNode item; item.node = layer; item.type = FlattenedNode::RASTER_LAYER; nodes << item; } } void flattenNodes(KisNodeSP node, QList &nodes) { KisNodeSP child = node->firstChild(); while (child) { bool isGroupLayer = child->inherits("KisGroupLayer"); bool isRasterLayer = child->inherits("KisPaintLayer") || child->inherits("KisShapeLayer"); if (isGroupLayer) { { FlattenedNode item; item.node = child; item.type = FlattenedNode::SECTION_DIVIDER; nodes << item; } flattenNodes(child, nodes); { FlattenedNode item; item.node = child; item.type = FlattenedNode::FOLDER_OPEN; nodes << item; } } else if (isRasterLayer) { FlattenedNode item; item.node = child; item.type = FlattenedNode::RASTER_LAYER; nodes << item; } child = child->nextSibling(); } } KisNodeSP findOnlyTransparencyMask(KisNodeSP node, FlattenedNode::Type type) { if (type != FlattenedNode::FOLDER_OPEN && type != FlattenedNode::FOLDER_CLOSED && type != FlattenedNode::RASTER_LAYER) { return 0; } KisLayer *layer = qobject_cast(node.data()); QList masks = layer->effectMasks(); if (masks.size() != 1) return 0; KisEffectMaskSP onlyMask = masks.first(); return onlyMask->inherits("KisTransparencyMask") ? onlyMask : 0; } QDomDocument fetchLayerStyleXmlData(KisNodeSP node) { const KisLayer *layer = qobject_cast(node.data()); KisPSDLayerStyleSP layerStyle = layer->layerStyle(); if (!layerStyle) return QDomDocument(); KisAslLayerStyleSerializer serializer; serializer.setStyles(QVector() << layerStyle); return serializer.formPsdXmlDocument(); } inline QDomNode findNodeByKey(const QString &key, QDomNode parent) { return KisDomUtils::findElementByAttibute(parent, "node", "key", key); } void mergePatternsXMLSection(const QDomDocument &src, QDomDocument &dst) { QDomNode srcPatternsNode = findNodeByKey("Patterns", src.documentElement()); QDomNode dstPatternsNode = findNodeByKey("Patterns", dst.documentElement()); if (srcPatternsNode.isNull()) return; if (dstPatternsNode.isNull()) { dst = src; return; } KIS_ASSERT_RECOVER_RETURN(!srcPatternsNode.isNull()); KIS_ASSERT_RECOVER_RETURN(!dstPatternsNode.isNull()); QDomNode node = srcPatternsNode.firstChild(); while(!node.isNull()) { QDomNode importedNode = dst.importNode(node, true); KIS_ASSERT_RECOVER_RETURN(!importedNode.isNull()); dstPatternsNode.appendChild(importedNode); node = node.nextSibling(); } } bool PSDLayerMaskSection::write(QIODevice* io, KisNodeSP rootLayer) { bool retval = true; try { writeImpl(io, rootLayer); } catch (KisAslWriterUtils::ASLWriteException &e) { error = PREPEND_METHOD(e.what()); retval = false; } return retval; } void PSDLayerMaskSection::writeImpl(QIODevice* io, KisNodeSP rootLayer) { dbgFile << "Writing layer layer section"; // Build the whole layer structure QList nodes; addBackgroundIfNeeded(rootLayer, nodes); flattenNodes(rootLayer, nodes); if (nodes.isEmpty()) { throw KisAslWriterUtils::ASLWriteException("Could not find paint layers to save"); } { KisAslWriterUtils::OffsetStreamPusher layerAndMaskSectionSizeTag(io, 2); QDomDocument mergedPatternsXmlDoc; { KisAslWriterUtils::OffsetStreamPusher layerInfoSizeTag(io, 4); { // number of layers (negative, because krita always has alpha) const qint16 layersSize = -nodes.size(); SAFE_WRITE_EX(io, layersSize); dbgFile << "Number of layers" << layersSize << "at" << io->pos(); } // Layer records section Q_FOREACH (const FlattenedNode &item, nodes) { KisNodeSP node = item.node; PSDLayerRecord *layerRecord = new PSDLayerRecord(m_header); layers.append(layerRecord); KisNodeSP onlyTransparencyMask = findOnlyTransparencyMask(node, item.type); const QRect maskRect = onlyTransparencyMask ? onlyTransparencyMask->paintDevice()->exactBounds() : QRect(); const bool nodeVisible = node->visible(); const KoColorSpace *colorSpace = node->colorSpace(); const quint8 nodeOpacity = node->opacity(); const quint8 nodeClipping = 0; const KisPaintLayer *paintLayer = qobject_cast(node.data()); const bool alphaLocked = (paintLayer && paintLayer->alphaLocked()); const QString nodeCompositeOp = node->compositeOpId(); const KisGroupLayer *groupLayer = qobject_cast(node.data()); const bool nodeIsPassThrough = groupLayer && groupLayer->passThroughMode(); QDomDocument stylesXmlDoc = fetchLayerStyleXmlData(node); if (mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) { mergedPatternsXmlDoc = stylesXmlDoc; } else if (!mergedPatternsXmlDoc.isNull() && !stylesXmlDoc.isNull()) { mergePatternsXMLSection(stylesXmlDoc, mergedPatternsXmlDoc); } bool nodeIrrelevant = false; QString nodeName; KisPaintDeviceSP layerContentDevice; psd_section_type sectionType; if (item.type == FlattenedNode::RASTER_LAYER) { nodeIrrelevant = false; nodeName = node->name(); layerContentDevice = onlyTransparencyMask ? node->original() : node->projection(); sectionType = psd_other; } else { nodeIrrelevant = true; nodeName = item.type == FlattenedNode::SECTION_DIVIDER ? QString("") : node->name(); layerContentDevice = 0; sectionType = item.type == FlattenedNode::SECTION_DIVIDER ? psd_bounding_divider : item.type == FlattenedNode::FOLDER_OPEN ? psd_open_folder : psd_closed_folder; } // === no access to node anymore QRect layerRect; if (layerContentDevice) { QRect rc = layerContentDevice->exactBounds(); rc = rc.normalized(); // keep to the max of photoshop's capabilities if (rc.width() > 30000) rc.setWidth(30000); if (rc.height() > 30000) rc.setHeight(30000); layerRect = rc; } layerRecord->top = layerRect.y(); layerRecord->left = layerRect.x(); layerRecord->bottom = layerRect.y() + layerRect.height(); layerRecord->right = layerRect.x() + layerRect.width(); // colors + alpha channel // note: transparency mask not included layerRecord->nChannels = colorSpace->colorChannelCount() + 1; ChannelInfo *info = new ChannelInfo; info->channelId = -1; // For the alpha channel, which we always have in Krita, and should be saved first in layerRecord->channelInfoRecords << info; // the rest is in display order: rgb, cmyk, lab... for (int i = 0; i < (int)colorSpace->colorChannelCount(); ++i) { info = new ChannelInfo; info->channelId = i; // 0 for red, 1 = green, etc layerRecord->channelInfoRecords << info; } layerRecord->blendModeKey = composite_op_to_psd_blendmode(nodeCompositeOp); layerRecord->isPassThrough = nodeIsPassThrough; layerRecord->opacity = nodeOpacity; layerRecord->clipping = nodeClipping; layerRecord->transparencyProtected = alphaLocked; layerRecord->visible = nodeVisible; layerRecord->irrelevant = nodeIrrelevant; layerRecord->layerName = nodeName; layerRecord->write(io, layerContentDevice, onlyTransparencyMask, maskRect, sectionType, stylesXmlDoc); } dbgFile << "start writing layer pixel data" << io->pos(); // Now save the pixel data Q_FOREACH (PSDLayerRecord *layerRecord, layers) { layerRecord->writePixelData(io); } } { // write the global layer mask info -- which is empty const quint32 globalMaskSize = 0; SAFE_WRITE_EX(io, globalMaskSize); } { PsdAdditionalLayerInfoBlock globalInfoSection(m_header); globalInfoSection.writePattBlockEx(io, mergedPatternsXmlDoc); } } } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp index 4a3a6a4c0a..7ed4cb4b59 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/dcrawsettingswidget.cpp @@ -1,1324 +1,1324 @@ /** =========================================================== * @file * * This file is a part of digiKam project *
http://www.digikam.org * * @date 2006-09-13 * @brief LibRaw settings widgets * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2011 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "dcrawsettingswidget.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "kdcraw.h" #include "rnuminput.h" #include "rcombobox.h" #include "rexpanderbox.h" #include "libkdcraw_debug.h" #include namespace KDcrawIface { class Q_DECL_HIDDEN DcrawSettingsWidget::Private { public: Private() { autoBrightnessBox = 0; sixteenBitsImage = 0; fourColorCheckBox = 0; brightnessLabel = 0; brightnessSpinBox = 0; blackPointCheckBox = 0; blackPointSpinBox = 0; whitePointCheckBox = 0; whitePointSpinBox = 0; whiteBalanceComboBox = 0; whiteBalanceLabel = 0; customWhiteBalanceSpinBox = 0; customWhiteBalanceLabel = 0; customWhiteBalanceGreenSpinBox = 0; customWhiteBalanceGreenLabel = 0; unclipColorLabel = 0; dontStretchPixelsCheckBox = 0; RAWQualityComboBox = 0; RAWQualityLabel = 0; noiseReductionComboBox = 0; NRSpinBox1 = 0; NRSpinBox2 = 0; NRLabel1 = 0; NRLabel2 = 0; enableCACorrectionBox = 0; autoCACorrectionBox = 0; caRedMultSpinBox = 0; caBlueMultSpinBox = 0; caRedMultLabel = 0; caBlueMultLabel = 0; unclipColorComboBox = 0; reconstructLabel = 0; reconstructSpinBox = 0; outputColorSpaceLabel = 0; outputColorSpaceComboBox = 0; demosaicingSettings = 0; whiteBalanceSettings = 0; correctionsSettings = 0; colormanSettings = 0; medianFilterPassesSpinBox = 0; medianFilterPassesLabel = 0; inIccUrlEdit = 0; outIccUrlEdit = 0; inputColorSpaceLabel = 0; inputColorSpaceComboBox = 0; fixColorsHighlightsBox = 0; refineInterpolationBox = 0; noiseReductionLabel = 0; expoCorrectionBox = 0; expoCorrectionShiftSpinBox = 0; expoCorrectionHighlightSpinBox = 0; expoCorrectionShiftLabel = 0; expoCorrectionHighlightLabel = 0; } /** Convert Exposure correction shift E.V value from GUI to Linear value needs by libraw decoder. */ double shiftExpoFromEvToLinear(double ev) const { // From GUI : -2.0EV => 0.25 // +3.0EV => 8.00 return (1.55*ev + 3.35); } /** Convert Exposure correction shift Linear value from liraw decoder to E.V value needs by GUI. */ double shiftExpoFromLinearToEv(double lin) const { // From GUI : 0.25 => -2.0EV // 8.00 => +3.0EV return ((lin-3.35) / 1.55); } public: QWidget* demosaicingSettings; QWidget* whiteBalanceSettings; QWidget* correctionsSettings; QWidget* colormanSettings; QLabel* whiteBalanceLabel; QLabel* customWhiteBalanceLabel; QLabel* customWhiteBalanceGreenLabel; QLabel* brightnessLabel; QLabel* RAWQualityLabel; QLabel* NRLabel1; QLabel* NRLabel2; QLabel* caRedMultLabel; QLabel* caBlueMultLabel; QLabel* unclipColorLabel; QLabel* reconstructLabel; QLabel* inputColorSpaceLabel; QLabel* outputColorSpaceLabel; QLabel* medianFilterPassesLabel; QLabel* noiseReductionLabel; QLabel* expoCorrectionShiftLabel; QLabel* expoCorrectionHighlightLabel; QCheckBox* blackPointCheckBox; QCheckBox* whitePointCheckBox; QCheckBox* sixteenBitsImage; QCheckBox* autoBrightnessBox; QCheckBox* fourColorCheckBox; QCheckBox* dontStretchPixelsCheckBox; QCheckBox* enableCACorrectionBox; QCheckBox* autoCACorrectionBox; QCheckBox* fixColorsHighlightsBox; QCheckBox* refineInterpolationBox; QCheckBox* expoCorrectionBox; RFileSelector* inIccUrlEdit; RFileSelector* outIccUrlEdit; RComboBox* noiseReductionComboBox; RComboBox* whiteBalanceComboBox; RComboBox* RAWQualityComboBox; RComboBox* unclipColorComboBox; RComboBox* inputColorSpaceComboBox; RComboBox* outputColorSpaceComboBox; RIntNumInput* customWhiteBalanceSpinBox; RIntNumInput* reconstructSpinBox; RIntNumInput* blackPointSpinBox; RIntNumInput* whitePointSpinBox; RIntNumInput* NRSpinBox1; RIntNumInput* NRSpinBox2; RIntNumInput* medianFilterPassesSpinBox; RDoubleNumInput* customWhiteBalanceGreenSpinBox; RDoubleNumInput* caRedMultSpinBox; RDoubleNumInput* caBlueMultSpinBox; RDoubleNumInput* brightnessSpinBox; RDoubleNumInput* expoCorrectionShiftSpinBox; RDoubleNumInput* expoCorrectionHighlightSpinBox; }; DcrawSettingsWidget::DcrawSettingsWidget(QWidget* const parent, int advSettings) : RExpanderBox(parent), d(new Private) { setup(advSettings); } void DcrawSettingsWidget::setup(int advSettings) { setObjectName( QLatin1String("DCRawSettings Expander" )); // --------------------------------------------------------------- // DEMOSAICING Settings panel d->demosaicingSettings = new QWidget(this); QGridLayout* const demosaicingLayout = new QGridLayout(d->demosaicingSettings); int line = 0; d->sixteenBitsImage = new QCheckBox(i18nc("@option:check", "16 bits color depth"), d->demosaicingSettings); d->sixteenBitsImage->setToolTip(i18nc("@info:whatsthis", "

If enabled, all RAW files will " "be decoded in 16-bit color depth using a linear gamma curve. To " "prevent dark picture rendering in the editor, it is recommended to " "use Color Management in this mode.

" "

If disabled, all RAW files will be decoded in 8-bit color " "depth with a BT.709 gamma curve and a 99th-percentile white point. " "This mode is faster than 16-bit decoding.

")); demosaicingLayout->addWidget(d->sixteenBitsImage, 0, 0, 1, 2); if (advSettings & SIXTEENBITS) { d->sixteenBitsImage->show(); line = 1; } else { d->sixteenBitsImage->hide(); } d->fourColorCheckBox = new QCheckBox(i18nc("@option:check", "Interpolate RGB as four colors"), d->demosaicingSettings); d->fourColorCheckBox->setToolTip(i18nc("@info:whatsthis", "Interpolate RGB as four " "colors" "

The default is to assume that all green pixels are the same. " "If even-row green pixels are more sensitive to ultraviolet light " "than odd-row this difference causes a mesh pattern in the output; " "using this option solves this problem with minimal loss of detail.

" "

To resume, this option blurs the image a little, but it " "eliminates false 2x2 mesh patterns with VNG quality method or " "mazes with AHD quality method.

")); demosaicingLayout->addWidget(d->fourColorCheckBox, line, 0, 1, line == 0 ? 2 : 3); line++; QLabel* const dcrawVersion = new QLabel(d->demosaicingSettings); dcrawVersion->setAlignment(Qt::AlignRight); dcrawVersion->setToolTip(i18nc("@info:tooltip", "Visit LibRaw project website")); dcrawVersion->setOpenExternalLinks(true); dcrawVersion->setTextFormat(Qt::RichText); dcrawVersion->setTextInteractionFlags(Qt::LinksAccessibleByMouse); dcrawVersion->setText(QString::fromLatin1("%2") .arg(QLatin1String("http://www.libraw.org")) .arg(QString::fromLatin1("libraw %1").arg(KDcraw::librawVersion()))); demosaicingLayout->addWidget(dcrawVersion, 0, 2, 1, 1); d->dontStretchPixelsCheckBox = new QCheckBox(i18nc("@option:check", "Do not stretch or rotate pixels"), d->demosaicingSettings); d->dontStretchPixelsCheckBox->setToolTip(i18nc("@info:whatsthis", "Do not stretch or rotate pixels" "

For Fuji Super CCD cameras, show the image tilted 45 degrees. " "For cameras with non-square pixels, do not stretch the image to " "its correct aspect ratio. In any case, this option guarantees that " "each output pixel corresponds to one RAW pixel.

")); demosaicingLayout->addWidget(d->dontStretchPixelsCheckBox, line, 0, 1, 3); line++; d->RAWQualityLabel = new QLabel(i18nc("@label:listbox", "Quality:"), d->demosaicingSettings); d->RAWQualityComboBox = new RComboBox(d->demosaicingSettings); // Original dcraw demosaicing methods d->RAWQualityComboBox->insertItem(RawDecodingSettings::BILINEAR, i18nc("@item:inlistbox Quality", "Bilinear")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VNG, i18nc("@item:inlistbox Quality", "VNG")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::PPG, i18nc("@item:inlistbox Quality", "PPG")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::AHD, i18nc("@item:inlistbox Quality", "AHD")); // Extended demosaicing method from GPL2 pack d->RAWQualityComboBox->insertItem(RawDecodingSettings::DCB, i18nc("@item:inlistbox Quality", "DCB")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::PL_AHD, i18nc("@item:inlistbox Quality", "AHD v2")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::AFD, i18nc("@item:inlistbox Quality", "AFD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD, i18nc("@item:inlistbox Quality", "VCD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::VCD_AHD, i18nc("@item:inlistbox Quality", "VCD & AHD")); d->RAWQualityComboBox->insertItem(RawDecodingSettings::LMMSE, i18nc("@item:inlistbox Quality", "LMMSE")); // Extended demosaicing method from GPL3 pack d->RAWQualityComboBox->insertItem(RawDecodingSettings::AMAZE, i18nc("@item:inlistbox Quality", "AMaZE")); // If Libraw do not support GPL2 pack, disable entries relevant. if (!KDcraw::librawUseGPL2DemosaicPack()) { for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i) d->RAWQualityComboBox->combo()->setItemData(i, false, Qt::UserRole-1); } // If Libraw do not support GPL3 pack, disable entries relevant. if (!KDcraw::librawUseGPL3DemosaicPack()) { d->RAWQualityComboBox->combo()->setItemData(RawDecodingSettings::AMAZE, false, Qt::UserRole-1); } d->RAWQualityComboBox->setDefaultIndex(RawDecodingSettings::BILINEAR); d->RAWQualityComboBox->setCurrentIndex(RawDecodingSettings::BILINEAR); d->RAWQualityComboBox->setToolTip(i18nc("@info:whatsthis", "Quality (interpolation)" "

Select here the demosaicing method to use when decoding RAW " "images. A demosaicing algorithm is a digital image process used to " "interpolate a complete image from the partial raw data received " "from the color-filtered image sensor, internal to many digital " "cameras, in form of a matrix of colored pixels. Also known as CFA " "interpolation or color reconstruction, another common spelling is " "demosaicing. The following methods are available for demosaicing " "RAW images:

" // Original dcraw demosaicing methods "

  • Bilinear: use " "high-speed but low-quality bilinear interpolation (default - for " "slow computers). In this method, the red value of a non-red pixel " "is computed as the average of the adjacent red pixels, and similarly " "for blue and green.
  • " "
  • VNG: use Variable Number " "of Gradients interpolation. This method computes gradients near " "the pixel of interest and uses the lower gradients (representing " "smoother and more similar parts of the image) to make an estimate.
  • " "
  • PPG: use Patterned-Pixel-" "Grouping interpolation. Pixel Grouping uses assumptions about " "natural scenery in making estimates. It has fewer color artifacts " "on natural images than the Variable Number of Gradients method.
  • " "
  • AHD: use Adaptive " "Homogeneity-Directed interpolation. This method selects the " "direction of interpolation so as to maximize a homogeneity metric, " "thus typically minimizing color artifacts.
  • " // Extended demosaicing method "
  • DCB: DCB interpolation from " "linuxphoto.org project.
  • " "
  • AHD v2: modified AHD " "interpolation using Variance of Color Differences method.
  • " "
  • AFD: Adaptive Filtered " "Demosaicing interpolation through 5 pass median filter from PerfectRaw " "project.
  • " "
  • VCD: Variance of Color " "Differences interpolation.
  • " "
  • VCD & AHD: Mixed demosaicing " "between VCD and AHD.
  • " "
  • LMMSE: color demosaicing via " "directional linear minimum mean-square error estimation interpolation " "from PerfectRaw.
  • " "
  • AMaZE: Aliasing Minimization " "interpolation and Zipper Elimination to apply color aberration removal " "from RawTherapee project.

" "

Note: some methods can be unavailable if RAW decoder have been built " "without extension packs.

")); demosaicingLayout->addWidget(d->RAWQualityLabel, line, 0, 1, 1); demosaicingLayout->addWidget(d->RAWQualityComboBox, line, 1, 1, 2); line++; d->medianFilterPassesSpinBox = new RIntNumInput(d->demosaicingSettings); d->medianFilterPassesSpinBox->setRange(0, 10, 1); d->medianFilterPassesSpinBox->setDefaultValue(0); d->medianFilterPassesLabel = new QLabel(i18nc("@label:slider", "Pass:"), d->whiteBalanceSettings); d->medianFilterPassesSpinBox->setToolTip( i18nc("@info:whatsthis", "Pass" "

Set here the passes used by the median filter applied after " "interpolation to Red-Green and Blue-Green channels.

" "

This setting is only available for specific Quality options: " "Bilinear, " "VNG, PPG, " "AHD, " "DCB, and VCD & AHD.

")); demosaicingLayout->addWidget(d->medianFilterPassesLabel, line, 0, 1, 1); demosaicingLayout->addWidget(d->medianFilterPassesSpinBox, line, 1, 1, 2); line++; d->refineInterpolationBox = new QCheckBox(i18nc("@option:check", "Refine interpolation"), d->demosaicingSettings); d->refineInterpolationBox->setToolTip(i18nc("@info:whatsthis", "Refine interpolation" "

This setting is available only for few Quality options:

" "

  • DCB: turn on " "the enhance interpolated colors filter.
  • " "
  • VCD & AHD: turn on the " "enhanced effective color interpolation (EECI) refine to improve " "sharpness.

")); demosaicingLayout->addWidget(d->refineInterpolationBox, line, 0, 1, 2); // If Libraw do not support GPL2 pack, disable options relevant. if (!KDcraw::librawUseGPL2DemosaicPack()) { d->medianFilterPassesLabel->setEnabled(false); d->medianFilterPassesSpinBox->setEnabled(false); d->refineInterpolationBox->setEnabled(false); } addItem(d->demosaicingSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Demosaicing"), QString("demosaicing"), true); // --------------------------------------------------------------- // WHITE BALANCE Settings Panel d->whiteBalanceSettings = new QWidget(this); QGridLayout* const whiteBalanceLayout = new QGridLayout(d->whiteBalanceSettings); d->whiteBalanceLabel = new QLabel(i18nc("@label:listbox", "Method:"), d->whiteBalanceSettings); d->whiteBalanceComboBox = new RComboBox(d->whiteBalanceSettings); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::NONE, i18nc("@item:inlistbox", "Default D65")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CAMERA, i18nc("@item:inlistbox", "Camera")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::AUTO, i18nc("@item:inlistbox set while balance automatically", "Automatic")); d->whiteBalanceComboBox->insertItem(RawDecodingSettings::CUSTOM, i18nc("@item:inlistbox set white balance manually", "Manual")); d->whiteBalanceComboBox->setDefaultIndex(RawDecodingSettings::CAMERA); d->whiteBalanceComboBox->setToolTip(i18nc("@info:whatsthis", "White Balance" "

Configure the raw white balance:

" "

  • Default D65: " "Use a standard daylight D65 white balance.
  • " "
  • Camera: Use the white " "balance specified by the camera. If not available, reverts to " "default neutral white balance.
  • " "
  • Automatic: Calculates an " "automatic white balance averaging the entire image.
  • " "
  • Manual: Set a custom " "temperature and green level values.

")); d->customWhiteBalanceSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->customWhiteBalanceSpinBox->setRange(2000, 12000, 10); d->customWhiteBalanceSpinBox->setDefaultValue(6500); d->customWhiteBalanceLabel = new QLabel(i18nc("@label:slider", "T(K):"), d->whiteBalanceSettings); d->customWhiteBalanceSpinBox->setToolTip( i18nc("@info:whatsthis", "Temperature" "

Set here the color temperature in Kelvin.

")); d->customWhiteBalanceGreenSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->customWhiteBalanceGreenSpinBox->setDecimals(2); d->customWhiteBalanceGreenSpinBox->setRange(0.2, 2.5, 0.01); d->customWhiteBalanceGreenSpinBox->setDefaultValue(1.0); d->customWhiteBalanceGreenLabel = new QLabel(i18nc("@label:slider Green component", "Green:"), d->whiteBalanceSettings); d->customWhiteBalanceGreenSpinBox->setToolTip(i18nc("@info:whatsthis", "

Set here the " "green component to set magenta color cast removal level.

")); d->unclipColorLabel = new QLabel(i18nc("@label:listbox", "Highlights:"), d->whiteBalanceSettings); d->unclipColorComboBox = new RComboBox(d->whiteBalanceSettings); d->unclipColorComboBox->insertItem(0, i18nc("@item:inlistbox", "Solid white")); d->unclipColorComboBox->insertItem(1, i18nc("@item:inlistbox", "Unclip")); d->unclipColorComboBox->insertItem(2, i18nc("@item:inlistbox", "Blend")); d->unclipColorComboBox->insertItem(3, i18nc("@item:inlistbox", "Rebuild")); d->unclipColorComboBox->setDefaultIndex(0); d->unclipColorComboBox->setToolTip(i18nc("@info:whatsthis", "Highlights" "

Select here the highlight clipping method:

" "

  • Solid white: " "clip all highlights to solid white
  • " "
  • Unclip: leave highlights " "unclipped in various shades of pink
  • " "
  • Blend:Blend clipped and " "unclipped values together for a gradual fade to white
  • " "
  • Rebuild: reconstruct " "highlights using a level value

")); d->reconstructLabel = new QLabel(i18nc("@label:slider Highlight reconstruct level", "Level:"), d->whiteBalanceSettings); d->reconstructSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->reconstructSpinBox->setRange(0, 6, 1); d->reconstructSpinBox->setDefaultValue(0); d->reconstructSpinBox->setToolTip(i18nc("@info:whatsthis", "Level" "

Specify the reconstruct highlight level. Low values favor " "whites and high values favor colors.

")); d->expoCorrectionBox = new QCheckBox(i18nc("@option:check", "Exposure Correction (E.V)"), d->whiteBalanceSettings); d->expoCorrectionBox->setToolTip(i18nc("@info:whatsthis", "

Turn on the exposure " "correction before interpolation.

")); d->expoCorrectionShiftLabel = new QLabel(i18nc("@label:slider", "Linear Shift:"), d->whiteBalanceSettings); d->expoCorrectionShiftSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->expoCorrectionShiftSpinBox->setDecimals(2); d->expoCorrectionShiftSpinBox->setRange(-2.0, 3.0, 0.01); d->expoCorrectionShiftSpinBox->setDefaultValue(0.0); d->expoCorrectionShiftSpinBox->setToolTip(i18nc("@info:whatsthis", "Shift" "

Linear Shift of exposure correction before interpolation in E.V

")); d->expoCorrectionHighlightLabel = new QLabel(i18nc("@label:slider", "Highlight:"), d->whiteBalanceSettings); d->expoCorrectionHighlightSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->expoCorrectionHighlightSpinBox->setDecimals(2); d->expoCorrectionHighlightSpinBox->setRange(0.0, 1.0, 0.01); d->expoCorrectionHighlightSpinBox->setDefaultValue(0.0); d->expoCorrectionHighlightSpinBox->setToolTip(i18nc("@info:whatsthis", "Highlight" "

Amount of highlight preservation for exposure correction " "before interpolation in E.V. Only take effect if Shift Correction is > 1.0 E.V

")); d->fixColorsHighlightsBox = new QCheckBox(i18nc("@option:check", "Correct false colors in highlights"), d->whiteBalanceSettings); d->fixColorsHighlightsBox->setToolTip(i18nc("@info:whatsthis", "

If enabled, images with " "overblown channels are processed much more accurately, without " "'pink clouds' (and blue highlights under tungsten lamps).

")); d->autoBrightnessBox = new QCheckBox(i18nc("@option:check", "Auto Brightness"), d->whiteBalanceSettings); d->autoBrightnessBox->setToolTip(i18nc("@info:whatsthis", "

If disable, use a fixed white level " "and ignore the image histogram to adjust brightness.

")); d->brightnessLabel = new QLabel(i18nc("@label:slider", "Brightness:"), d->whiteBalanceSettings); d->brightnessSpinBox = new RDoubleNumInput(d->whiteBalanceSettings); d->brightnessSpinBox->setDecimals(2); d->brightnessSpinBox->setRange(0.0, 10.0, 0.01); d->brightnessSpinBox->setDefaultValue(1.0); d->brightnessSpinBox->setToolTip(i18nc("@info:whatsthis", "Brightness" "

Specify the brightness level of output image. The default " "value is 1.0 (works in 8-bit mode only).

")); if (! (advSettings & POSTPROCESSING)) { d->brightnessLabel->hide(); d->brightnessSpinBox->hide(); } d->blackPointCheckBox = new QCheckBox(i18nc("@option:check Black point", "Black:"), d->whiteBalanceSettings); d->blackPointCheckBox->setToolTip(i18nc("@info:whatsthis", "Black point" "

Use a specific black point value to decode RAW pictures. If " "you set this option to off, the Black Point value will be " "automatically computed.

")); d->blackPointSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->blackPointSpinBox->setRange(0, 1000, 1); d->blackPointSpinBox->setDefaultValue(0); d->blackPointSpinBox->setToolTip(i18nc("@info:whatsthis", "Black point value" "

Specify specific black point value of the output image.

")); d->whitePointCheckBox = new QCheckBox(i18nc("@option:check White point", "White:"), d->whiteBalanceSettings); d->whitePointCheckBox->setToolTip(i18nc("@info:whatsthis", "White point" "

Use a specific white point value to decode RAW pictures. If " "you set this option to off, the White Point value will be " "automatically computed.

")); d->whitePointSpinBox = new RIntNumInput(d->whiteBalanceSettings); d->whitePointSpinBox->setRange(0, 20000, 1); d->whitePointSpinBox->setDefaultValue(0); d->whitePointSpinBox->setToolTip(i18nc("@info:whatsthis", "White point value" "

Specify specific white point value of the output image.

")); if (! (advSettings & BLACKWHITEPOINTS)) { d->blackPointCheckBox->hide(); d->blackPointSpinBox->hide(); d->whitePointCheckBox->hide(); d->whitePointSpinBox->hide(); } whiteBalanceLayout->addWidget(d->whiteBalanceLabel, 0, 0, 1, 1); whiteBalanceLayout->addWidget(d->whiteBalanceComboBox, 0, 1, 1, 2); whiteBalanceLayout->addWidget(d->customWhiteBalanceLabel, 1, 0, 1, 1); whiteBalanceLayout->addWidget(d->customWhiteBalanceSpinBox, 1, 1, 1, 2); whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenLabel, 2, 0, 1, 1); whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenSpinBox, 2, 1, 1, 2); whiteBalanceLayout->addWidget(d->unclipColorLabel, 3, 0, 1, 1); whiteBalanceLayout->addWidget(d->unclipColorComboBox, 3, 1, 1, 2); whiteBalanceLayout->addWidget(d->reconstructLabel, 4, 0, 1, 1); whiteBalanceLayout->addWidget(d->reconstructSpinBox, 4, 1, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionBox, 5, 0, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionShiftLabel, 6, 0, 1, 1); whiteBalanceLayout->addWidget(d->expoCorrectionShiftSpinBox, 6, 1, 1, 2); whiteBalanceLayout->addWidget(d->expoCorrectionHighlightLabel, 7, 0, 1, 1); whiteBalanceLayout->addWidget(d->expoCorrectionHighlightSpinBox, 7, 1, 1, 2); whiteBalanceLayout->addWidget(d->fixColorsHighlightsBox, 8, 0, 1, 3); whiteBalanceLayout->addWidget(d->autoBrightnessBox, 9, 0, 1, 3); whiteBalanceLayout->addWidget(d->brightnessLabel, 10, 0, 1, 1); whiteBalanceLayout->addWidget(d->brightnessSpinBox, 10, 1, 1, 2); whiteBalanceLayout->addWidget(d->blackPointCheckBox, 11, 0, 1, 1); whiteBalanceLayout->addWidget(d->blackPointSpinBox, 11, 1, 1, 2); whiteBalanceLayout->addWidget(d->whitePointCheckBox, 12, 0, 1, 1); whiteBalanceLayout->addWidget(d->whitePointSpinBox, 12, 1, 1, 2); whiteBalanceLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); whiteBalanceLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->whiteBalanceSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "White Balance"), QString("whitebalance"), true); // --------------------------------------------------------------- // CORRECTIONS Settings panel d->correctionsSettings = new QWidget(this); QGridLayout* const correctionsLayout = new QGridLayout(d->correctionsSettings); d->noiseReductionLabel = new QLabel(i18nc("@label:listbox", "Noise reduction:"), d->correctionsSettings); d->noiseReductionComboBox = new RComboBox(d->colormanSettings); d->noiseReductionComboBox->insertItem(RawDecodingSettings::NONR, i18nc("@item:inlistbox Noise Reduction", "None")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::WAVELETSNR, i18nc("@item:inlistbox Noise Reduction", "Wavelets")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::FBDDNR, i18nc("@item:inlistbox Noise Reduction", "FBDD")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::LINENR, i18nc("@item:inlistbox Noise Reduction", "CFA Line Denoise")); d->noiseReductionComboBox->insertItem(RawDecodingSettings::IMPULSENR, i18nc("@item:inlistbox Noise Reduction", "Impulse Denoise")); d->noiseReductionComboBox->setDefaultIndex(RawDecodingSettings::NONR); d->noiseReductionComboBox->setToolTip(i18nc("@info:whatsthis", "Noise Reduction" "

Select here the noise reduction method to apply during RAW " "decoding.

" "

  • None: no " "noise reduction.
  • " "
  • Wavelets: wavelets " "correction to erase noise while preserving real detail. It's " "applied after interpolation.
  • " "
  • FBDD: Fake Before " "Demosaicing Denoising noise reduction. It's applied before " "interpolation.
  • " "
  • CFA Line Denoise: Banding " "noise suppression. It's applied after interpolation.
  • " "
  • Impulse Denoise: Impulse " "noise suppression. It's applied after interpolation.

")); d->NRSpinBox1 = new RIntNumInput(d->correctionsSettings); d->NRSpinBox1->setRange(100, 1000, 1); d->NRSpinBox1->setDefaultValue(100); d->NRLabel1 = new QLabel(d->correctionsSettings); d->NRSpinBox2 = new RIntNumInput(d->correctionsSettings); d->NRSpinBox2->setRange(100, 1000, 1); d->NRSpinBox2->setDefaultValue(100); d->NRLabel2 = new QLabel(d->correctionsSettings); d->enableCACorrectionBox = new QCheckBox(i18nc("@option:check", "Enable Chromatic Aberration correction"), d->correctionsSettings); d->enableCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "Enable Chromatic " "Aberration correction" "

Enlarge the raw red-green and blue-yellow axis by the given " "factors (automatic by default).

")); d->autoCACorrectionBox = new QCheckBox(i18nc("@option:check", "Automatic color axis adjustments"), d->correctionsSettings); d->autoCACorrectionBox->setToolTip(i18nc("@info:whatsthis", "Automatic Chromatic " "Aberration correction" "

If this option is turned on, it will try to shift image " "channels slightly and evaluate Chromatic Aberration change. Note " "that if you shot blue-red pattern, the method may fail. In this " "case, disable this option and tune manually color factors.

")); d->caRedMultLabel = new QLabel(i18nc("@label:slider", "Red-Green:"), d->correctionsSettings); d->caRedMultSpinBox = new RDoubleNumInput(d->correctionsSettings); d->caRedMultSpinBox->setDecimals(1); d->caRedMultSpinBox->setRange(-4.0, 4.0, 0.1); d->caRedMultSpinBox->setDefaultValue(0.0); d->caRedMultSpinBox->setToolTip(i18nc("@info:whatsthis", "Red-Green multiplier" "

Set here the amount of correction on red-green axis

")); d->caBlueMultLabel = new QLabel(i18nc("@label:slider", "Blue-Yellow:"), d->correctionsSettings); d->caBlueMultSpinBox = new RDoubleNumInput(d->correctionsSettings); d->caBlueMultSpinBox->setDecimals(1); d->caBlueMultSpinBox->setRange(-4.0, 4.0, 0.1); d->caBlueMultSpinBox->setDefaultValue(0.0); d->caBlueMultSpinBox->setToolTip(i18nc("@info:whatsthis", "Blue-Yellow multiplier" "

Set here the amount of correction on blue-yellow axis

")); correctionsLayout->addWidget(d->noiseReductionLabel, 0, 0, 1, 1); correctionsLayout->addWidget(d->noiseReductionComboBox, 0, 1, 1, 2); correctionsLayout->addWidget(d->NRLabel1, 1, 0, 1, 1); correctionsLayout->addWidget(d->NRSpinBox1, 1, 1, 1, 2); correctionsLayout->addWidget(d->NRLabel2, 2, 0, 1, 1); correctionsLayout->addWidget(d->NRSpinBox2, 2, 1, 1, 2); correctionsLayout->addWidget(d->enableCACorrectionBox, 3, 0, 1, 3); correctionsLayout->addWidget(d->autoCACorrectionBox, 4, 0, 1, 3); correctionsLayout->addWidget(d->caRedMultLabel, 5, 0, 1, 1); correctionsLayout->addWidget(d->caRedMultSpinBox, 5, 1, 1, 2); correctionsLayout->addWidget(d->caBlueMultLabel, 6, 0, 1, 1); correctionsLayout->addWidget(d->caBlueMultSpinBox, 6, 1, 1, 2); correctionsLayout->setRowStretch(7, 10); correctionsLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); correctionsLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->correctionsSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Corrections"), QString("corrections"), false); // --------------------------------------------------------------- // COLOR MANAGEMENT Settings panel d->colormanSettings = new QWidget(this); QGridLayout* const colormanLayout = new QGridLayout(d->colormanSettings); d->inputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Camera Profile:"), d->colormanSettings); d->inputColorSpaceComboBox = new RComboBox(d->colormanSettings); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::NOINPUTCS, i18nc("@item:inlistbox Camera Profile", "None")); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::EMBEDDED, i18nc("@item:inlistbox Camera Profile", "Embedded")); d->inputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMINPUTCS, i18nc("@item:inlistbox Camera Profile", "Custom")); d->inputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::NOINPUTCS); d->inputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "Camera Profile" "

Select here the input color space used to decode RAW data.

" "

  • None: no " "input color profile is used during RAW decoding.
  • " "
  • Embedded: use embedded " "color profile from RAW file, if it exists.
  • " "
  • Custom: use a custom " "input color space profile.

")); d->inIccUrlEdit = new RFileSelector(d->colormanSettings); d->inIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile); d->inIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)")); d->outputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Workspace:"), d->colormanSettings); d->outputColorSpaceComboBox = new RComboBox( d->colormanSettings ); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::RAWCOLOR, i18nc("@item:inlistbox Workspace", "Raw (no profile)")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::SRGB, i18nc("@item:inlistbox Workspace", "sRGB")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::ADOBERGB, i18nc("@item:inlistbox Workspace", "Adobe RGB")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::WIDEGAMMUT, i18nc("@item:inlistbox Workspace", "Wide Gamut")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::PROPHOTO, i18nc("@item:inlistbox Workspace", "Pro-Photo")); d->outputColorSpaceComboBox->insertItem(RawDecodingSettings::CUSTOMOUTPUTCS, i18nc("@item:inlistbox Workspace", "Custom")); d->outputColorSpaceComboBox->setDefaultIndex(RawDecodingSettings::SRGB); d->outputColorSpaceComboBox->setToolTip(i18nc("@info:whatsthis", "Workspace" "

Select here the output color space used to decode RAW data.

" "

  • Raw (linear): " "in this mode, no output color space is used during RAW decoding.
  • " "
  • sRGB: this is an RGB " "color space, created cooperatively by Hewlett-Packard and " "Microsoft. It is the best choice for images destined for the Web " "and portrait photography.
  • " "
  • Adobe RGB: this color " "space is an extended RGB color space, developed by Adobe. It is " "used for photography applications such as advertising and fine " "art.
  • " "
  • Wide Gamut: this color " "space is an expanded version of the Adobe RGB color space.
  • " "
  • Pro-Photo: this color " "space is an RGB color space, developed by Kodak, that offers an " "especially large gamut designed for use with photographic outputs " "in mind.
  • " "
  • Custom: use a custom " "output color space profile.

")); d->outIccUrlEdit = new RFileSelector(d->colormanSettings); d->outIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile); d->outIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc; *.icm)")); colormanLayout->addWidget(d->inputColorSpaceLabel, 0, 0, 1, 1); colormanLayout->addWidget(d->inputColorSpaceComboBox, 0, 1, 1, 2); colormanLayout->addWidget(d->inIccUrlEdit, 1, 0, 1, 3); colormanLayout->addWidget(d->outputColorSpaceLabel, 2, 0, 1, 1); colormanLayout->addWidget(d->outputColorSpaceComboBox, 2, 1, 1, 2); colormanLayout->addWidget(d->outIccUrlEdit, 3, 0, 1, 3); colormanLayout->setRowStretch(4, 10); colormanLayout->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); colormanLayout->setMargin(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); addItem(d->colormanSettings, KisIconUtils::loadIcon("kdcraw").pixmap(16, 16), i18nc("@label", "Color Management"), QString("colormanagement"), false); if (! (advSettings & COLORSPACE)) removeItem(COLORMANAGEMENT); addStretch(); // --------------------------------------------------------------- connect(d->unclipColorComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotUnclipColorActivated); connect(d->whiteBalanceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotWhiteBalanceToggled); connect(d->noiseReductionComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotNoiseReductionChanged); connect(d->enableCACorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotCACorrectionToggled); connect(d->autoCACorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotAutoCAToggled); connect(d->blackPointCheckBox, SIGNAL(toggled(bool)), d->blackPointSpinBox, SLOT(setEnabled(bool))); connect(d->whitePointCheckBox, SIGNAL(toggled(bool)), d->whitePointSpinBox, SLOT(setEnabled(bool))); connect(d->sixteenBitsImage, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotsixteenBitsImageToggled); connect(d->inputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotInputColorSpaceChanged); connect(d->outputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotOutputColorSpaceChanged); connect(d->expoCorrectionBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::slotExposureCorrectionToggled); connect(d->expoCorrectionShiftSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::slotExpoCorrectionShiftChanged); // Wrapper to emit signal when something is changed in settings. connect(d->inIccUrlEdit->lineEdit(), &QLineEdit::textChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->outIccUrlEdit->lineEdit(), &QLineEdit::textChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whiteBalanceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->RAWQualityComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::slotRAWQualityChanged); connect(d->unclipColorComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->inputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->outputColorSpaceComboBox, static_cast(&RComboBox::activated), this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->blackPointCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whitePointCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->sixteenBitsImage, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->fixColorsHighlightsBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->autoBrightnessBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->fourColorCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->dontStretchPixelsCheckBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->refineInterpolationBox, &QCheckBox::toggled, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->customWhiteBalanceSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->reconstructSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->blackPointSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->whitePointSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->NRSpinBox1, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->NRSpinBox2, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->medianFilterPassesSpinBox, &RIntNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->customWhiteBalanceGreenSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->caRedMultSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->caBlueMultSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->brightnessSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); connect(d->expoCorrectionHighlightSpinBox, &RDoubleNumInput::valueChanged, this, &DcrawSettingsWidget::signalSettingsChanged); } DcrawSettingsWidget::~DcrawSettingsWidget() { delete d; } void DcrawSettingsWidget::updateMinimumWidth() { int width = 0; for (int i = 0; i < count(); i++) { if (widget(i)->width() > width) width = widget(i)->width(); } setMinimumWidth(width); } RFileSelector* DcrawSettingsWidget::inputProfileUrlEdit() const { return d->inIccUrlEdit; } RFileSelector* DcrawSettingsWidget::outputProfileUrlEdit() const { return d->outIccUrlEdit; } void DcrawSettingsWidget::resetToDefault() { setSettings(RawDecodingSettings()); } void DcrawSettingsWidget::slotsixteenBitsImageToggled(bool b) { setEnabledBrightnessSettings(!b); emit signalSixteenBitsImageToggled(d->sixteenBitsImage->isChecked()); } void DcrawSettingsWidget::slotWhiteBalanceToggled(int v) { if (v == 3) { d->customWhiteBalanceSpinBox->setEnabled(true); d->customWhiteBalanceGreenSpinBox->setEnabled(true); d->customWhiteBalanceLabel->setEnabled(true); d->customWhiteBalanceGreenLabel->setEnabled(true); } else { d->customWhiteBalanceSpinBox->setEnabled(false); d->customWhiteBalanceGreenSpinBox->setEnabled(false); d->customWhiteBalanceLabel->setEnabled(false); d->customWhiteBalanceGreenLabel->setEnabled(false); } } void DcrawSettingsWidget::slotUnclipColorActivated(int v) { if (v == 3) // Reconstruct Highlight method { d->reconstructLabel->setEnabled(true); d->reconstructSpinBox->setEnabled(true); } else { d->reconstructLabel->setEnabled(false); d->reconstructSpinBox->setEnabled(false); } } void DcrawSettingsWidget::slotNoiseReductionChanged(int item) { d->NRSpinBox1->setEnabled(true); d->NRLabel1->setEnabled(true); d->NRSpinBox2->setEnabled(true); d->NRLabel2->setEnabled(true); d->NRLabel1->setText(i18nc("@label", "Threshold:")); d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "Threshold" "

Set here the noise reduction threshold value to use.

")); switch(item) { case RawDecodingSettings::WAVELETSNR: case RawDecodingSettings::FBDDNR: case RawDecodingSettings::LINENR: d->NRSpinBox2->setVisible(false); d->NRLabel2->setVisible(false); break; case RawDecodingSettings::IMPULSENR: d->NRLabel1->setText(i18nc("@label", "Luminance:")); d->NRSpinBox1->setToolTip(i18nc("@info:whatsthis", "Luminance" "

Amount of Luminance impulse noise reduction.

")); d->NRLabel2->setText(i18nc("@label", "Chrominance:")); d->NRSpinBox2->setToolTip(i18nc("@info:whatsthis", "Chrominance" "

Amount of Chrominance impulse noise reduction.

")); d->NRSpinBox2->setVisible(true); d->NRLabel2->setVisible(true); break; default: d->NRSpinBox1->setEnabled(false); d->NRLabel1->setEnabled(false); d->NRSpinBox2->setEnabled(false); d->NRLabel2->setEnabled(false); d->NRSpinBox2->setVisible(false); d->NRLabel2->setVisible(false); break; } emit signalSettingsChanged(); } void DcrawSettingsWidget::slotCACorrectionToggled(bool b) { d->autoCACorrectionBox->setEnabled(b); slotAutoCAToggled(d->autoCACorrectionBox->isChecked()); } void DcrawSettingsWidget::slotAutoCAToggled(bool b) { if (b) { d->caRedMultSpinBox->setValue(0.0); d->caBlueMultSpinBox->setValue(0.0); } bool mult = (!b) && (d->autoCACorrectionBox->isEnabled()); d->caRedMultSpinBox->setEnabled(mult); d->caBlueMultSpinBox->setEnabled(mult); d->caRedMultLabel->setEnabled(mult); d->caBlueMultLabel->setEnabled(mult); emit signalSettingsChanged(); } void DcrawSettingsWidget::slotExposureCorrectionToggled(bool b) { d->expoCorrectionShiftLabel->setEnabled(b); d->expoCorrectionShiftSpinBox->setEnabled(b); d->expoCorrectionHighlightLabel->setEnabled(b); d->expoCorrectionHighlightSpinBox->setEnabled(b); slotExpoCorrectionShiftChanged(d->expoCorrectionShiftSpinBox->value()); } void DcrawSettingsWidget::slotExpoCorrectionShiftChanged(double ev) { // Only enable Highligh exposure correction if Shift correction is >= 1.0, else this settings do not take effect. bool b = (ev >= 1.0); d->expoCorrectionHighlightLabel->setEnabled(b); d->expoCorrectionHighlightSpinBox->setEnabled(b); emit signalSettingsChanged(); } void DcrawSettingsWidget::slotInputColorSpaceChanged(int item) { d->inIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMINPUTCS); } void DcrawSettingsWidget::slotOutputColorSpaceChanged(int item) { d->outIccUrlEdit->setEnabled(item == RawDecodingSettings::CUSTOMOUTPUTCS); } void DcrawSettingsWidget::slotRAWQualityChanged(int quality) { switch(quality) { case RawDecodingSettings::DCB: case RawDecodingSettings::VCD_AHD: - // These options can be only avaialble if Libraw use GPL2 pack. + // These options can be only available if Libraw use GPL2 pack. d->medianFilterPassesLabel->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); d->medianFilterPassesSpinBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); d->refineInterpolationBox->setEnabled(KDcraw::librawUseGPL2DemosaicPack()); break; case RawDecodingSettings::PL_AHD: case RawDecodingSettings::AFD: case RawDecodingSettings::VCD: case RawDecodingSettings::LMMSE: case RawDecodingSettings::AMAZE: d->medianFilterPassesLabel->setEnabled(false); d->medianFilterPassesSpinBox->setEnabled(false); d->refineInterpolationBox->setEnabled(false); break; default: // BILINEAR, VNG, PPG, AHD d->medianFilterPassesLabel->setEnabled(true); d->medianFilterPassesSpinBox->setEnabled(true); d->refineInterpolationBox->setEnabled(false); break; } emit signalSettingsChanged(); } void DcrawSettingsWidget::setEnabledBrightnessSettings(bool b) { d->brightnessLabel->setEnabled(b); d->brightnessSpinBox->setEnabled(b); } bool DcrawSettingsWidget::brightnessSettingsIsEnabled() const { return d->brightnessSpinBox->isEnabled(); } void DcrawSettingsWidget::setSettings(const RawDecodingSettings& settings) { d->sixteenBitsImage->setChecked(settings.sixteenBitsImage); switch(settings.whiteBalance) { case RawDecodingSettings::CAMERA: d->whiteBalanceComboBox->setCurrentIndex(1); break; case RawDecodingSettings::AUTO: d->whiteBalanceComboBox->setCurrentIndex(2); break; case RawDecodingSettings::CUSTOM: d->whiteBalanceComboBox->setCurrentIndex(3); break; default: d->whiteBalanceComboBox->setCurrentIndex(0); break; } slotWhiteBalanceToggled(d->whiteBalanceComboBox->currentIndex()); d->customWhiteBalanceSpinBox->setValue(settings.customWhiteBalance); d->customWhiteBalanceGreenSpinBox->setValue(settings.customWhiteBalanceGreen); d->fourColorCheckBox->setChecked(settings.RGBInterpolate4Colors); d->autoBrightnessBox->setChecked(settings.autoBrightness); d->fixColorsHighlightsBox->setChecked(settings.fixColorsHighlights); switch(settings.unclipColors) { case 0: d->unclipColorComboBox->setCurrentIndex(0); break; case 1: d->unclipColorComboBox->setCurrentIndex(1); break; case 2: d->unclipColorComboBox->setCurrentIndex(2); break; default: // Reconstruct Highlight method d->unclipColorComboBox->setCurrentIndex(3); d->reconstructSpinBox->setValue(settings.unclipColors-3); break; } slotUnclipColorActivated(d->unclipColorComboBox->currentIndex()); d->dontStretchPixelsCheckBox->setChecked(settings.DontStretchPixels); d->brightnessSpinBox->setValue(settings.brightness); d->blackPointCheckBox->setChecked(settings.enableBlackPoint); d->blackPointSpinBox->setEnabled(settings.enableBlackPoint); d->blackPointSpinBox->setValue(settings.blackPoint); d->whitePointCheckBox->setChecked(settings.enableWhitePoint); d->whitePointSpinBox->setEnabled(settings.enableWhitePoint); d->whitePointSpinBox->setValue(settings.whitePoint); int q = settings.RAWQuality; // If Libraw do not support GPL2 pack, reset to BILINEAR. if (!KDcraw::librawUseGPL2DemosaicPack()) { for (int i=RawDecodingSettings::DCB ; i <=RawDecodingSettings::LMMSE ; ++i) { if (q == i) { q = RawDecodingSettings::BILINEAR; - qCDebug(LIBKDCRAW_LOG) << "Libraw GPL2 pack not avaialble. Raw quality set to Bilinear"; + qCDebug(LIBKDCRAW_LOG) << "Libraw GPL2 pack not available. Raw quality set to Bilinear"; break; } } } // If Libraw do not support GPL3 pack, reset to BILINEAR. if (!KDcraw::librawUseGPL3DemosaicPack() && (q == RawDecodingSettings::AMAZE)) { q = RawDecodingSettings::BILINEAR; - qCDebug(LIBKDCRAW_LOG) << "Libraw GPL3 pack not avaialble. Raw quality set to Bilinear"; + qCDebug(LIBKDCRAW_LOG) << "Libraw GPL3 pack not available. Raw quality set to Bilinear"; } d->RAWQualityComboBox->setCurrentIndex(q); switch(q) { case RawDecodingSettings::DCB: d->medianFilterPassesSpinBox->setValue(settings.dcbIterations); d->refineInterpolationBox->setChecked(settings.dcbEnhanceFl); break; case RawDecodingSettings::VCD_AHD: d->medianFilterPassesSpinBox->setValue(settings.eeciRefine); d->refineInterpolationBox->setChecked(settings.eeciRefine); break; default: d->medianFilterPassesSpinBox->setValue(settings.medianFilterPasses); d->refineInterpolationBox->setChecked(false); // option not used. break; } slotRAWQualityChanged(q); d->inputColorSpaceComboBox->setCurrentIndex((int)settings.inputColorSpace); slotInputColorSpaceChanged((int)settings.inputColorSpace); d->outputColorSpaceComboBox->setCurrentIndex((int)settings.outputColorSpace); slotOutputColorSpaceChanged((int)settings.outputColorSpace); d->noiseReductionComboBox->setCurrentIndex(settings.NRType); slotNoiseReductionChanged(settings.NRType); d->NRSpinBox1->setValue(settings.NRThreshold); d->NRSpinBox2->setValue(settings.NRChroThreshold); d->enableCACorrectionBox->setChecked(settings.enableCACorrection); d->caRedMultSpinBox->setValue(settings.caMultiplier[0]); d->caBlueMultSpinBox->setValue(settings.caMultiplier[1]); d->autoCACorrectionBox->setChecked((settings.caMultiplier[0] == 0.0) && (settings.caMultiplier[1] == 0.0)); slotCACorrectionToggled(settings.enableCACorrection); d->expoCorrectionBox->setChecked(settings.expoCorrection); slotExposureCorrectionToggled(settings.expoCorrection); d->expoCorrectionShiftSpinBox->setValue(d->shiftExpoFromLinearToEv(settings.expoCorrectionShift)); d->expoCorrectionHighlightSpinBox->setValue(settings.expoCorrectionHighlight); d->inIccUrlEdit->lineEdit()->setText(settings.inputProfile); d->outIccUrlEdit->lineEdit()->setText(settings.outputProfile); } RawDecodingSettings DcrawSettingsWidget::settings() const { RawDecodingSettings prm; prm.sixteenBitsImage = d->sixteenBitsImage->isChecked(); switch(d->whiteBalanceComboBox->currentIndex()) { case 1: prm.whiteBalance = RawDecodingSettings::CAMERA; break; case 2: prm.whiteBalance = RawDecodingSettings::AUTO; break; case 3: prm.whiteBalance = RawDecodingSettings::CUSTOM; break; default: prm.whiteBalance = RawDecodingSettings::NONE; break; } prm.customWhiteBalance = d->customWhiteBalanceSpinBox->value(); prm.customWhiteBalanceGreen = d->customWhiteBalanceGreenSpinBox->value(); prm.RGBInterpolate4Colors = d->fourColorCheckBox->isChecked(); prm.autoBrightness = d->autoBrightnessBox->isChecked(); prm.fixColorsHighlights = d->fixColorsHighlightsBox->isChecked(); switch(d->unclipColorComboBox->currentIndex()) { case 0: prm.unclipColors = 0; break; case 1: prm.unclipColors = 1; break; case 2: prm.unclipColors = 2; break; default: // Reconstruct Highlight method prm.unclipColors = d->reconstructSpinBox->value()+3; break; } prm.DontStretchPixels = d->dontStretchPixelsCheckBox->isChecked(); prm.brightness = d->brightnessSpinBox->value(); prm.enableBlackPoint = d->blackPointCheckBox->isChecked(); prm.blackPoint = d->blackPointSpinBox->value(); prm.enableWhitePoint = d->whitePointCheckBox->isChecked(); prm.whitePoint = d->whitePointSpinBox->value(); prm.RAWQuality = (RawDecodingSettings::DecodingQuality)d->RAWQualityComboBox->currentIndex(); switch(prm.RAWQuality) { case RawDecodingSettings::DCB: prm.dcbIterations = d->medianFilterPassesSpinBox->value(); prm.dcbEnhanceFl = d->refineInterpolationBox->isChecked(); break; case RawDecodingSettings::VCD_AHD: prm.esMedPasses = d->medianFilterPassesSpinBox->value(); prm.eeciRefine = d->refineInterpolationBox->isChecked(); break; default: prm.medianFilterPasses = d->medianFilterPassesSpinBox->value(); break; } prm.NRType = (RawDecodingSettings::NoiseReduction)d->noiseReductionComboBox->currentIndex(); switch (prm.NRType) { case RawDecodingSettings::NONR: { prm.NRThreshold = 0; prm.NRChroThreshold = 0; break; } case RawDecodingSettings::WAVELETSNR: case RawDecodingSettings::FBDDNR: case RawDecodingSettings::LINENR: { prm.NRThreshold = d->NRSpinBox1->value(); prm.NRChroThreshold = 0; break; } default: // IMPULSENR { prm.NRThreshold = d->NRSpinBox1->value(); prm.NRChroThreshold = d->NRSpinBox2->value(); break; } } prm.enableCACorrection = d->enableCACorrectionBox->isChecked(); prm.caMultiplier[0] = d->caRedMultSpinBox->value(); prm.caMultiplier[1] = d->caBlueMultSpinBox->value(); prm.expoCorrection = d->expoCorrectionBox->isChecked(); prm.expoCorrectionShift = d->shiftExpoFromEvToLinear(d->expoCorrectionShiftSpinBox->value()); prm.expoCorrectionHighlight = d->expoCorrectionHighlightSpinBox->value(); prm.inputColorSpace = (RawDecodingSettings::InputColorSpace)(d->inputColorSpaceComboBox->currentIndex()); prm.outputColorSpace = (RawDecodingSettings::OutputColorSpace)(d->outputColorSpaceComboBox->currentIndex()); prm.inputProfile = d->inIccUrlEdit->lineEdit()->text(); prm.outputProfile = d->outIccUrlEdit->lineEdit()->text(); return prm; } void DcrawSettingsWidget::writeSettings(KConfigGroup& group) { RawDecodingSettings prm = settings(); prm.writeSettings(group); RExpanderBox::writeSettings(group); } void DcrawSettingsWidget::readSettings(KConfigGroup& group) { RawDecodingSettings prm; prm.readSettings(group); setSettings(prm); RExpanderBox::readSettings(group); } } // NameSpace KDcrawIface diff --git a/plugins/paintops/libpaintop/kis_airbrush_option.h b/plugins/paintops/libpaintop/kis_airbrush_option.h index 5d624d1b92..4ce46cb55f 100644 --- a/plugins/paintops/libpaintop/kis_airbrush_option.h +++ b/plugins/paintops/libpaintop/kis_airbrush_option.h @@ -1,66 +1,66 @@ /* * Copyright (c) 2010 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_AIRBRUSH_OPTION_H #define KIS_AIRBRUSH_OPTION_H #include #include /** * Allows the user to activate airbrushing of the brush mask (brush is painted at the same position over and over) - * Rate is set in miliseconds. + * Rate is set in milliseconds. */ class PAINTOP_EXPORT KisAirbrushOption : public KisPaintOpOption { Q_OBJECT public: KisAirbrushOption(bool enabled = true, bool canIgnoreSpacing = true); ~KisAirbrushOption() override; void writeOptionSetting(KisPropertiesConfigurationSP setting) const override; void readOptionSetting(const KisPropertiesConfigurationSP setting) override; /** * Returns the airbrushing interval, in milliseconds. This value should be ignored if the * KisAirbrushOption is not checked according to isChecked(). */ qreal airbrushInterval() const; /** * Returns true if regular distance-based spacing should be ignored and overridden by time-based * spacing. This value should be ignored if the KisAirbrushOption is not checked according to * isChecked(). */ bool ignoreSpacing() const; private Q_SLOTS: void slotIntervalChanged(); void slotIgnoreSpacingChanged(); private: // Reads the airbrush interval from the GUI. void updateInterval(); // Reads the "ignore spacing" setting from the GUI. void updateIgnoreSpacing(); struct Private; Private *const m_d; }; #endif diff --git a/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp b/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp index dcdc0a2be8..edfa32909e 100644 --- a/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp +++ b/plugins/tools/karbonplugins/tools/KarbonPatternEditStrategy.cpp @@ -1,392 +1,392 @@ /* This file is part of the KDE project * Copyright (C) 2007,2009 Jan Hambrecht * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KarbonPatternEditStrategy.h" #include #include #include #include #include #include uint KarbonPatternEditStrategyBase::m_handleRadius = 3; uint KarbonPatternEditStrategyBase::m_grabSensitivity = 3; KarbonPatternEditStrategyBase::KarbonPatternEditStrategyBase(KoShape *s, KoImageCollection *imageCollection) : m_selectedHandle(-1) , m_oldFill(new KoPatternBackground(imageCollection)) , m_newFill(new KoPatternBackground(imageCollection)) , m_shape(s) , m_imageCollection(imageCollection) , m_editing(false) , m_modified(false) { Q_ASSERT(m_shape); Q_ASSERT(imageCollection); // cache the shapes transformation matrix m_matrix = shape()->absoluteTransformation(0); } KarbonPatternEditStrategyBase::~KarbonPatternEditStrategyBase() { } void KarbonPatternEditStrategyBase::setEditing(bool on) { m_editing = on; // if we are going into editing mode, save the old background // for use inside the command emitted when finished if (on) { m_modified = false; QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill) { m_oldFill = fill; } } } void KarbonPatternEditStrategyBase::setModified() { m_modified = true; } bool KarbonPatternEditStrategyBase::isModified() const { return m_modified; } KUndo2Command *KarbonPatternEditStrategyBase::createCommand() { QSharedPointer fill = qSharedPointerDynamicCast(m_shape->background()); if (fill && isModified()) { fill = m_oldFill; QSharedPointer newFill(new KoPatternBackground(m_imageCollection)); newFill = m_newFill; return new KoShapeBackgroundCommand(m_shape, newFill, 0); } return 0; } void KarbonPatternEditStrategyBase::paintHandle(QPainter &painter, const KoViewConverter &converter, const QPointF &position) const { QRectF handleRect = converter.viewToDocument(QRectF(0, 0, 2 * m_handleRadius, 2 * m_handleRadius)); handleRect.moveCenter(position); painter.drawRect(handleRect); } bool KarbonPatternEditStrategyBase::mouseInsideHandle(const QPointF &mousePos, const QPointF &handlePos, const KoViewConverter &converter) const { qreal grabSensitivityInPt = converter.viewToDocumentX(m_grabSensitivity); if (mousePos.x() < handlePos.x() - grabSensitivityInPt) { return false; } if (mousePos.x() > handlePos.x() + grabSensitivityInPt) { return false; } if (mousePos.y() < handlePos.y() - grabSensitivityInPt) { return false; } if (mousePos.y() > handlePos.y() + grabSensitivityInPt) { return false; } return true; } void KarbonPatternEditStrategyBase::repaint() const { m_shape->update(); } KoShape *KarbonPatternEditStrategyBase::shape() const { return m_shape; } KoImageCollection *KarbonPatternEditStrategyBase::imageCollection() { return m_imageCollection; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// KarbonPatternEditStrategy::KarbonPatternEditStrategy(KoShape *s, KoImageCollection *imageCollection) : KarbonPatternEditStrategyBase(s, imageCollection) { // cache the shapes transformation matrix m_matrix = shape()->absoluteTransformation(0); QSizeF size = shape()->size(); // the fixed length of half the average shape dimension m_normalizedLength = 0.25 * (size.width() + size.height()); - // get the brush tranformation matrix + // get the brush transformation matrix QTransform brushMatrix; QSharedPointer fill = qSharedPointerDynamicCast(shape()->background()); if (fill) { brushMatrix = fill->transform(); } // the center handle at the center point of the shape //m_origin = QPointF( 0.5 * size.width(), 0.5 * size.height() ); m_handles.append(brushMatrix.map(QPointF())); // the direction handle with the length of half the average shape dimension QPointF dirVec = QPointF(m_normalizedLength, 0.0); m_handles.append(brushMatrix.map(dirVec)); } KarbonPatternEditStrategy::~KarbonPatternEditStrategy() { } void KarbonPatternEditStrategy::paint(QPainter &painter, const KoViewConverter &converter) const { QPointF centerPoint = m_matrix.map(m_origin + m_handles[center]); QPointF directionPoint = m_matrix.map(m_origin + m_handles[direction]); KoShape::applyConversion(painter, converter); painter.drawLine(centerPoint, directionPoint); paintHandle(painter, converter, centerPoint); paintHandle(painter, converter, directionPoint); } bool KarbonPatternEditStrategy::selectHandle(const QPointF &mousePos, const KoViewConverter &converter) { int handleIndex = 0; Q_FOREACH (const QPointF &handle, m_handles) { if (mouseInsideHandle(mousePos, m_matrix.map(m_origin + handle), converter)) { m_selectedHandle = handleIndex; return true; } handleIndex++; } m_selectedHandle = -1; return false; } void KarbonPatternEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers) if (m_selectedHandle == direction) { QPointF newPos = m_matrix.inverted().map(mouseLocation) - m_origin - m_handles[center]; // calculate the temporary length after handle movement qreal newLength = sqrt(newPos.x() * newPos.x() + newPos.y() * newPos.y()); // set the new direction vector with the new direction and normalized length m_handles[m_selectedHandle] = m_handles[center] + m_normalizedLength / newLength * newPos; } else if (m_selectedHandle == center) { QPointF diffPos = m_matrix.inverted().map(mouseLocation) - m_origin - m_handles[center]; m_handles[center] += diffPos; m_handles[direction] += diffPos; } else { return; } setModified(); QSharedPointer fill = qSharedPointerDynamicCast(shape()->background()); if (fill) { m_newFill = updatedBackground(); fill = m_newFill; } } QRectF KarbonPatternEditStrategy::boundingRect() const { // calculate the bounding rect of the handles QRectF bbox(m_matrix.map(m_origin + m_handles[0]), QSize(0, 0)); for (int i = 1; i < m_handles.count(); ++i) { QPointF handle = m_matrix.map(m_origin + m_handles[i]); bbox.setLeft(qMin(handle.x(), bbox.left())); bbox.setRight(qMax(handle.x(), bbox.right())); bbox.setTop(qMin(handle.y(), bbox.top())); bbox.setBottom(qMax(handle.y(), bbox.bottom())); } qreal hr = handleRadius(); return bbox.adjusted(-hr, -hr, hr, hr); } QSharedPointer KarbonPatternEditStrategy::updatedBackground() { // the direction vector controls the rotation of the pattern QPointF dirVec = m_handles[direction] - m_handles[center]; qreal angle = atan2(dirVec.y(), dirVec.x()) * 180.0 / M_PI; QTransform matrix; // the center handle controls the translation matrix.translate(m_handles[center].x(), m_handles[center].y()); matrix.rotate(angle); QSharedPointer newFill(new KoPatternBackground(imageCollection())); newFill->setTransform(matrix); return newFill; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// KarbonOdfPatternEditStrategy::KarbonOdfPatternEditStrategy(KoShape *s, KoImageCollection *imageCollection) : KarbonPatternEditStrategyBase(s, imageCollection) { m_handles.append(QPointF()); m_handles.append(QPointF()); updateHandles(qSharedPointerDynamicCast(shape()->background())); } KarbonOdfPatternEditStrategy::~KarbonOdfPatternEditStrategy() { } void KarbonOdfPatternEditStrategy::paint(QPainter &painter, const KoViewConverter &converter) const { KoShape::applyConversion(painter, converter); QSharedPointer fill = qSharedPointerDynamicCast(shape()->background()); if (!fill) { return; } painter.save(); painter.setTransform(m_matrix * painter.transform()); painter.setBrush(Qt::NoBrush); painter.drawRect(QRectF(m_handles[origin], m_handles[size])); painter.restore(); if (fill->repeat() == KoPatternBackground::Tiled) { paintHandle(painter, converter, m_matrix.map(m_handles[origin])); } if (fill->repeat() != KoPatternBackground::Stretched) { paintHandle(painter, converter, m_matrix.map(m_handles[size])); } } bool KarbonOdfPatternEditStrategy::selectHandle(const QPointF &mousePos, const KoViewConverter &converter) { QSharedPointer fill = qSharedPointerDynamicCast(shape()->background()); if (!fill) { return false; } if (fill->repeat() == KoPatternBackground::Stretched) { return false; } m_selectedHandle = -1; if (mouseInsideHandle(mousePos, m_matrix.map(m_handles[size]), converter)) { m_selectedHandle = size; return true; } if (fill->repeat() == KoPatternBackground::Original) { return false; } if (mouseInsideHandle(mousePos, m_matrix.map(m_handles[origin]), converter)) { m_selectedHandle = origin; return true; } return false; } void KarbonOdfPatternEditStrategy::handleMouseMove(const QPointF &mouseLocation, Qt::KeyboardModifiers modifiers) { Q_UNUSED(modifiers); QSharedPointer fill = qSharedPointerDynamicCast(shape()->background()); if (!fill) { return; } if (fill->repeat() == KoPatternBackground::Stretched) { return; } if (m_selectedHandle == origin) { if (fill->repeat() == KoPatternBackground::Original) { return; } QPointF diffPos = m_matrix.inverted().map(mouseLocation) - m_handles[origin]; m_handles[origin] += diffPos; m_handles[size] += diffPos; } else if (m_selectedHandle == size) { QPointF newPos = m_matrix.inverted().map(mouseLocation); newPos.setX(qMax(newPos.x(), m_handles[origin].x())); newPos.setY(qMax(newPos.y(), m_handles[origin].y())); if (fill->repeat() == KoPatternBackground::Original) { QPointF diffPos = newPos - m_handles[size]; m_handles[size] += 0.5 * diffPos; m_handles[origin] -= 0.5 * diffPos; } else { m_handles[size] = newPos; } } else { return; } setModified(); m_newFill = updatedBackground(); updateHandles(m_newFill); } QRectF KarbonOdfPatternEditStrategy::boundingRect() const { // calculate the bounding rect of the handles QRectF bbox(m_matrix.map(m_handles[origin]), m_matrix.map(m_handles[size])); qreal hr = handleRadius(); return bbox.adjusted(-hr, -hr, hr, hr); } QSharedPointer KarbonOdfPatternEditStrategy::updatedBackground() { QSizeF displaySize(m_handles[size].x() - m_handles[origin].x(), m_handles[size].y() - m_handles[origin].y()); qreal offsetX = 100.0 * (m_handles[origin].x() / displaySize.width()); qreal offsetY = 100.0 * (m_handles[origin].y() / displaySize.height()); QSharedPointer newFill(new KoPatternBackground(imageCollection())); newFill = m_oldFill; newFill->setReferencePoint(KoPatternBackground::TopLeft); newFill->setReferencePointOffset(QPointF(offsetX, offsetY)); newFill->setPatternDisplaySize(displaySize); return newFill; } void KarbonOdfPatternEditStrategy::updateHandles(QSharedPointer fill) { if (!fill) { return; } QRectF patternRect = fill->patternRectFromFillSize(shape()->size()); m_handles[origin] = patternRect.topLeft(); m_handles[size] = patternRect.bottomRight(); } void KarbonOdfPatternEditStrategy::updateHandles() { updateHandles(qSharedPointerDynamicCast(shape()->background())); }