diff --git a/3rdparty/README.md b/3rdparty/README.md index 25675c7f48..ca06b4f46e 100644 --- a/3rdparty/README.md +++ b/3rdparty/README.md @@ -1,269 +1,270 @@ # CMake external projects to build krita's dependencies on Linux, Windows or OSX If you need to build Krita's dependencies for the following reasons: * you develop on Windows and aren't using build-tools/windows/build.cmd or KDE's craft * you develop on OSX and aren't using the scripts in krita/packaging/osx or Homebrew * you want to build a generic, distro-agnostic version of Krita for Linux and aren't using the scripts in packaging/linux/appimage * you develop on Linux, but some dependencies aren't available for your distribution and aren't using the scripts in packaging/linux/appimage and you know what you're doing, you can use the following guide to build the dependencies that Krita needs. If you develop on Linux and your distribution has all dependencies available, YOU DO NOT NEED THIS GUIDE AND YOU SHOULD STOP READING NOW Otherwise you risk major confusion. ## Prerequisites Note: on all operating systems the entire procedure is done in a terminal window. 1. git: https://git-scm.com/downloads. Make sure git is in your path 2. CMake 3.3.2 or later: https://cmake.org/download/. Make sure cmake is in your path. * CMake 3.9 does not build Krita properly at the moment, please use 3.8 or 3.10 instead. 3. Make sure you have a compiler: * Linux: gcc, minimum version 4.8 * OSX: clang, you need to install xcode for this * Windows: mingw-w64 7.3 (by mingw-builds): https://sourceforge.net/projects/mingw-w64/ * The Files can be found under "Toolchains targetting Win64/Win32"/"Personal Builds". * For threading, select posix. * For exceptions, select seh (64-bit) or dwarf (32-bit). * Install mingw to something like C:\mingw; the full path must not contain any spaces. * Make sure mingw's bin folder is in your path. It might be a good idea to create a batch file which sets the path and start cmd. * MSVC is *not* supported at the moment. 4. On Windows, you will also need a release of Python 3.6 (*not* 3.7 or any other versions): https://www.python.org. Make sure to have that version of python.exe in your path. This version of Python will be used for two things: to configure Qt and to build the Python scripting module. Make sure that this version of Python comes first in your path. Do not set PYTHONHOME or PYTHONPATH. * Make sure that your Python will have the correct architecture for the version you are trying to build. If building for 32-bit target, you need the 32-bit release of Python. 5. On Windows, if you want to compile Qt with ANGLE support, you will need to install Windows 10 SDK and have the environment variable `WindowsSdkDir` set to it (typically `C:\Program Files (x86)\Windows Kits\10`) ## Setup your environment ## Prepare your directory layout 1. Make a toplevel build directory, say $HOME/dev or c:\dev. We'll refer to this directory as BUILDROOT. You can use a variable for this, on WINDOWS %BUILDROOT%, on OSX and Linux $BUILDROOT. You will have to replace a bare BUILDROOT with $BUILDROOT or %BUILDROOT% whenever you copy and paste a command, depending on your operating system. 2. Checkout krita in BUILDROOT ``` cd BUILDROOT git clone git://anongit.kde.org/krita.git ``` 3. Create the build directory ``` mkdir BUILDROOT/b ``` 4. Create the downloads directory ``` mkdir BUILDROOT/d ``` 5. Create the install directory ``` mkdir BUILDROOT/i ``` ## Prepare the externals build 1. Enter the BUILDROOT/b directory 2. Run cmake: * Linux: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty \ -DINSTALL_ROOT=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DCMAKE_INSTALL_PREFIX=BUILDROOT/i ``` * OSX: ``` export PATH=$BUILDROOT/i/bin:$PATH export PYTHONHOME=$BUILDROOT/i (only if you want to build your own python) cmake ../krita/3rdparty/ \ -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i \ -DEXTERNALS_DOWNLOAD_DIR=$BUILDROOT/d \ -DINSTALL_ROOT=$BUILDROOT/i ``` * Windows 32-bit / 64-bit: Note that the cmake command needs to point to your BUILDROOT like /dev/d, not c:\dev\d. ``` set PATH=%BUILDROOT%\i\bin\;%BUILDROOT%\i\lib;%PATH% cmake ..\krita\3rdparty -DEXTERNALS_DOWNLOAD_DIR=/dev/d -DINSTALL_ROOT=/dev/i -G "MinGW Makefiles" ``` - If you want to build Qt and some other dependencies with parallel jobs, add `-DSUBMAKE_JOBS=` to this cmake command where is the number of jobs to run (if your PC has 4 CPU cores, you might want to set it to 5). For other jobs, you might need to manually add a -- -j N option, where N is the number of jobs. - If you don't have Windows 10 SDK and don't want to build Qt with ANGLE, add `-DQT_ENABLE_DYNAMIC_OPENGL=OFF` to the CMake command line args. 3. Build the packages: On Windows: ``` cmake --build . --config RelWithDebInfo --target ext_patch cmake --build . --config RelWithDebInfo --target ext_png2ico ``` On OSX and Windows: ``` cmake --build . --config RelWithDebInfo --target ext_gettext cmake --build . --config RelWithDebInfo --target ext_openssl ``` On all operating systems: ``` cmake --build . --config RelWithDebInfo --target ext_qt cmake --build . --config RelWithDebInfo --target ext_zlib cmake --build . --config RelWithDebInfo --target ext_boost Note about boost: check if the headers are installed into i/include/boost, but not into i/include/boost-1.61/boost cmake --build . --config RelWithDebInfo --target ext_eigen3 cmake --build . --config RelWithDebInfo --target ext_exiv2 cmake --build . --config RelWithDebInfo --target ext_fftw3 cmake --build . --config RelWithDebInfo --target ext_ilmbase cmake --build . --config RelWithDebInfo --target ext_jpeg cmake --build . --config RelWithDebInfo --target ext_lcms2 cmake --build . --config RelWithDebInfo --target ext_ocio cmake --build . --config RelWithDebInfo --target ext_openexr ``` OSX Note: You need to first build openexr; that will fail; then you need to set the rpath for the two utilities correctly, then try to build openexr again. ``` install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./b44ExpLogTable install_name_tool -add_rpath $BUILD_ROOT/i/lib $BUILD_ROOT/b/ext_openexr/ext_openexr-prefix/src/ext_openexr-build/IlmImf/./dwaLookups ``` On All operating systems ``` cmake --build . --config RelWithDebInfo --target ext_png cmake --build . --config RelWithDebInfo --target ext_tiff cmake --build . --config RelWithDebInfo --target ext_gsl cmake --build . --config RelWithDebInfo --target ext_vc cmake --build . --config RelWithDebInfo --target ext_libraw cmake --build . --config RelWithDebInfo --target ext_giflib cmake --build . --config RelWithDebInfo --target ext_openjpeg + ``` On Linux (if you want to build your own SIP and PyQt instead of the system one) ``` cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` On Windows ``` cmake --build . --config RelWithDebInfo --target ext_freetype cmake --build . --config RelWithDebInfo --target ext_poppler ``` On Linux ``` cmake --build . --config RelWithDebInfo --target ext_kcrash ``` On Windows (if you want to include DrMingw for dumping backtrace on crash) ``` cmake --build . --config RelWithDebInfo --target ext_drmingw ``` On Windows (if you want to include Python scripting) ``` cmake --build . --config RelWithDebInfo --target ext_python cmake --build . --config RelWithDebInfo --target ext_sip cmake --build . --config RelWithDebInfo --target ext_pyqt ``` On Windows and Linux (if you want to include gmic-qt) ``` cmake --build . --config RelWithDebInfo --target ext_gmic ``` Linux Note: poppler should be buildable on Linux as well with a home-built freetype and fontconfig, but I don't know how to make fontconfig find freetype, and on Linux, fontconfig is needed for poppler. Poppler is needed for PDF import. OSX Note: In order to build fontconfig on macOS, you need to have pkg-config installed. You probably need homebrew for that... See http://macappstore.org/pkg-config/ . archives from: files.kde.org/krita/build/dependencies: On Windows and OSX ``` cmake --build . --config RelWithDebInfo --target ext_kwindowsystem ``` ## Build Krita 1. Make a krita build directory: mkdir BUILDROOT/build 2. Enter the BUILDROOT/build 3. Configure the build: On Windows ``` cmake ..\krita -G "MinGW Makefiles" -DBoost_DEBUG=OFF -DBOOST_INCLUDEDIR=c:\dev\i\include -DBOOST_DEBUG=ON -DBOOST_ROOT=c:\dev\i -DBOOST_LIBRARYDIR=c:\dev\i\lib -DCMAKE_INSTALL_PREFIX=c:\dev\i -DCMAKE_PREFIX_PATH=c:\dev\i -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DHAVE_MEMORY_LEAK_TRACKER=OFF -Wno-dev -DDEFINE_NO_DEPRECATED=1 ``` On Linux ``` cmake ../krita -DCMAKE_INSTALL_PREFIX=BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo # Troubleshooting: if you built your own SIP and CMake fails to find it, please set # the following environment variable to the SIP installation directory: export PYTHONPATH=$BUILDROOT/i/sip/ # If you also have KIO installed in the system, don't forget to disable it by bassing to cmake: # cmake -DCMAKE_DISABLE_FIND_PACKAGE_KF5KIO=true . ``` On OSX ``` cmake ../krita -DCMAKE_INSTALL_PREFIX=$BUILDROOT/i -DDEFINE_NO_DEPRECATED=1 -DBUILD_TESTING=OFF -DKDE4_BUILD_TESTS=OFF -DBUNDLE_INSTALL_DIR=$BUILDROOT/i/bin -DCMAKE_BUILD_TYPE=RelWithDebInfo ``` 4. Run the build: On Linux and OSX ``` make make install ``` On Windows (replace 4 with the number of jobs to run in parallel) ``` cmake --build . --target install -- -j4 ``` 6. Run krita: On Linux ``` BUILDROOT/i/bin/krita ``` On Windows ``` BUILDROOT\i\bin\krita.exe ``` On OSX ``` BUILDROOT/i/bin/krita.app/Contents/MacOS/krita ``` ## Packaging a Windows Build If you want to create a stripped down version of Krita to distribute, after building everything just run the packaging/windows/package-complete.cmd script. That script will copy the necessary files into the specified folder and leave out developer related files. After the script runs there will be two new ZIP files that contain a small portable version of Krita and a separate portable debug version. diff --git a/3rdparty/ext_eigen3/CMakeLists.txt b/3rdparty/ext_eigen3/CMakeLists.txt index f32af14560..2c2d16f346 100644 --- a/3rdparty/ext_eigen3/CMakeLists.txt +++ b/3rdparty/ext_eigen3/CMakeLists.txt @@ -1,14 +1,14 @@ SET(EXTPREFIX_eigen3 "${EXTPREFIX}" ) ExternalProject_Add( ext_eigen3 DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} # eigen 3.3.4: bitbucket does weird things when downloading. - URL http://files.kde.org/krita/build/dependencies/eigen-eigen-5a0156e40feb.tar.gz - URL_MD5 1a47e78efe365a97de0c022d127607c3 + URL http://files.kde.org/krita/build/dependencies/eigen-3.3.7.tar.bz2 + URL_MD5 05b1f7511c93980c385ebe11bd3c93fa INSTALL_DIR ${EXTPREFIX_eigen3} PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/dart.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/no_tests.diff CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_eigen3} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} UPDATE_COMMAND "" ) diff --git a/3rdparty/ext_eigen3/dart.diff b/3rdparty/ext_eigen3/dart.diff index 15770fbe18..4df1bdf519 100644 --- a/3rdparty/ext_eigen3/dart.diff +++ b/3rdparty/ext_eigen3/dart.diff @@ -1,24 +1,24 @@ diff --git a/cmake/EigenConfigureTesting.cmake b/cmake/EigenConfigureTesting.cmake -index 2b11d83..8cf56ff 100644 +index 3a82439..4c28246 100644 --- a/cmake/EigenConfigureTesting.cmake +++ b/cmake/EigenConfigureTesting.cmake -@@ -26,19 +26,6 @@ include(CTest) - - set(EIGEN_TEST_BUILD_FLAGS " " CACHE STRING "Options passed to the build command of unit tests") +@@ -21,19 +21,6 @@ set(EIGEN_TEST_BUILD_FLAGS "" CACHE STRING "Options passed to the build command + set(EIGEN_DASHBOARD_BUILD_TARGET "buildtests" CACHE STRING "Target to be built in dashboard mode, default is buildtests") + set(EIGEN_CTEST_ERROR_EXCEPTION "" CACHE STRING "Regular expression for build error messages to be filtered out") -# Overwrite default DartConfiguration.tcl such that ctest can build our unit tests. -# Recall that our unit tests are not in the "all" target, so we have to explicitely ask ctest to build our custom 'buildtests' target. -# At this stage, we can also add custom flags to the build tool through the user defined EIGEN_TEST_BUILD_FLAGS variable. -file(READ "${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl" EIGEN_DART_CONFIG_FILE) -# try to grab the default flags -string(REGEX MATCH "MakeCommand:.*-- (.*)\nDefaultCTestConfigurationType" EIGEN_DUMMY ${EIGEN_DART_CONFIG_FILE}) -if(NOT CMAKE_MATCH_1) -string(REGEX MATCH "MakeCommand:.*[^c]make (.*)\nDefaultCTestConfigurationType" EIGEN_DUMMY ${EIGEN_DART_CONFIG_FILE}) -endif() --string(REGEX REPLACE "MakeCommand:.*DefaultCTestConfigurationType" "MakeCommand: ${CMAKE_COMMAND} --build . --target buildtests --config \"\${CTEST_CONFIGURATION_TYPE}\" -- ${CMAKE_MATCH_1} ${EIGEN_TEST_BUILD_FLAGS}\nDefaultCTestConfigurationType" +-string(REGEX REPLACE "MakeCommand:.*DefaultCTestConfigurationType" "MakeCommand: ${CMAKE_COMMAND} --build . --target ${EIGEN_DASHBOARD_BUILD_TARGET} --config \"\${CTEST_CONFIGURATION_TYPE}\" -- ${CMAKE_MATCH_1} ${EIGEN_TEST_BUILD_FLAGS}\nDefaultCTestConfigurationType" - EIGEN_DART_CONFIG_FILE2 ${EIGEN_DART_CONFIG_FILE}) -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/DartConfiguration.tcl" ${EIGEN_DART_CONFIG_FILE2}) - configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CTestCustom.cmake.in ${CMAKE_BINARY_DIR}/CTestCustom.cmake) # some documentation of this function would be nice diff --git a/3rdparty/ext_eigen3/no_tests.diff b/3rdparty/ext_eigen3/no_tests.diff index d943dab6d5..d20f5a0110 100644 --- a/3rdparty/ext_eigen3/no_tests.diff +++ b/3rdparty/ext_eigen3/no_tests.diff @@ -1,35 +1,13 @@ -commit 09b46c2a9acb0b7cb52968599499cb19fa5d8904 -Author: Boudewijn Rempt -Date: Fri Jan 5 16:09:39 2018 +0100 - - Disable tests: they need blas - diff --git a/CMakeLists.txt b/CMakeLists.txt -index f584002..dcb461b 100644 +index 2bfb6d5..81bffbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -418,24 +418,6 @@ add_subdirectory(doc EXCLUDE_FROM_ALL) +@@ -435,7 +435,7 @@ add_subdirectory(Eigen) - include(EigenConfigureTesting) + add_subdirectory(doc EXCLUDE_FROM_ALL) + +-option(BUILD_TESTING "Enable creation of Eigen tests." ON) ++option(BUILD_TESTING "Enable creation of Eigen tests." OFF) + if(BUILD_TESTING) + include(EigenConfigureTesting) --# fixme, not sure this line is still needed: --enable_testing() # must be called from the root CMakeLists, see man page -- -- --if(EIGEN_LEAVE_TEST_IN_ALL_TARGET) -- add_subdirectory(test) # can't do EXCLUDE_FROM_ALL here, breaks CTest --else() -- add_subdirectory(test EXCLUDE_FROM_ALL) --endif() -- --if(EIGEN_LEAVE_TEST_IN_ALL_TARGET) -- add_subdirectory(blas) -- add_subdirectory(lapack) --else() -- add_subdirectory(blas EXCLUDE_FROM_ALL) -- add_subdirectory(lapack EXCLUDE_FROM_ALL) --endif() -- - # add SYCL - option(EIGEN_TEST_SYCL "Add Sycl support." OFF) - if(EIGEN_TEST_SYCL) diff --git a/CMakeLists.txt b/CMakeLists.txt index 037e31b48f..0e28beaaeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,927 +1,926 @@ project(krita) message(STATUS "Using CMake version: ${CMAKE_VERSION}") cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR) set(MIN_QT_VERSION 5.9.0) set(MIN_FRAMEWORKS_VERSION 5.44.0) set( CMAKE_CXX_STANDARD 11 ) set( CMAKE_CXX_STANDARD_REQUIRED ON ) if (POLICY CMP0002) cmake_policy(SET CMP0002 OLD) endif() if (POLICY CMP0017) cmake_policy(SET CMP0017 NEW) endif () if (POLICY CMP0022) cmake_policy(SET CMP0022 OLD) endif () if (POLICY CMP0026) cmake_policy(SET CMP0026 OLD) endif() if (POLICY CMP0042) cmake_policy(SET CMP0042 NEW) endif() if (POLICY CMP0046) cmake_policy(SET CMP0046 OLD) endif () if (POLICY CMP0059) cmake_policy(SET CMP0059 OLD) endif() if (POLICY CMP0063) cmake_policy(SET CMP0063 OLD) endif() if (POLICY CMP0054) cmake_policy(SET CMP0054 OLD) endif() if (POLICY CMP0064) cmake_policy(SET CMP0064 OLD) endif() if (POLICY CMP0071) cmake_policy(SET CMP0071 OLD) endif() if (APPLE) set(APPLE_SUPPRESS_X11_WARNING TRUE) set(KDE_SKIP_RPATH_SETTINGS TRUE) set(CMAKE_MACOSX_RPATH 1) set(BUILD_WITH_INSTALL_RPATH 1) add_definitions(-mmacosx-version-min=10.12 -Wno-macro-redefined -Wno-deprecated-register) endif() if (CMAKE_COMPILER_IS_GNUCXX AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9 AND NOT WIN32) add_compile_options($<$:-Wno-suggest-override> -Wextra -Wno-class-memaccess) endif() ###################### ####################### ## Constants defines ## ####################### ###################### # define common versions of Krita applications, used to generate kritaversion.h # update these version for every release: set(KRITA_VERSION_STRING "4.3.0-prealpha") # Major version: 3 for 3.x, 4 for 4.x, etc. set(KRITA_STABLE_VERSION_MAJOR 4) # Minor version: 0 for 4.0, 1 for 4.1, etc. set(KRITA_STABLE_VERSION_MINOR 3) # Bugfix release version, or 0 for before the first stable release set(KRITA_VERSION_RELEASE 0) # the 4th digit, really only used for the Windows installer: # - [Pre-]Alpha: Starts from 0, increment 1 per release # - Beta: Starts from 50, increment 1 per release # - Stable: Set to 100, bump to 101 if emergency update is needed set(KRITA_VERSION_REVISION 0) set(KRITA_ALPHA 1) # uncomment only for Alpha #set(KRITA_BETA 1) # uncomment only for Beta #set(KRITA_RC 1) # uncomment only for RC -set(KRITA_YEAR 2018) # update every year if(NOT DEFINED KRITA_ALPHA AND NOT DEFINED KRITA_BETA AND NOT DEFINED KRITA_RC) set(KRITA_STABLE 1) # do not edit endif() message(STATUS "Krita version: ${KRITA_VERSION_STRING}") # Define the generic version of the Krita libraries here # This makes it easy to advance it when the next Krita release comes. # 14 was the last GENERIC_KRITA_LIB_VERSION_MAJOR of the previous Krita series # (2.x) so we're starting with 15 in 3.x series, 16 in 4.x series if(KRITA_STABLE_VERSION_MAJOR EQUAL 4) math(EXPR GENERIC_KRITA_LIB_VERSION_MAJOR "${KRITA_STABLE_VERSION_MINOR} + 16") else() # let's make sure we won't forget to update the "16" message(FATAL_ERROR "Reminder: please update offset == 16 used to compute GENERIC_KRITA_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_KRITA_LIB_VERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}.0.0") set(GENERIC_KRITA_LIB_SOVERSION "${GENERIC_KRITA_LIB_VERSION_MAJOR}") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") LIST (APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/kde_macro") # fetch git revision for the current build set(KRITA_GIT_SHA1_STRING "") set(KRITA_GIT_BRANCH_STRING "") include(GetGitRevisionDescription) get_git_head_hash(GIT_SHA1) get_git_branch(GIT_BRANCH) if(GIT_SHA1) string(SUBSTRING ${GIT_SHA1} 0 7 GIT_SHA1) set(KRITA_GIT_SHA1_STRING ${GIT_SHA1}) if(GIT_BRANCH) set(KRITA_GIT_BRANCH_STRING ${GIT_BRANCH}) else() set(KRITA_GIT_BRANCH_STRING "(detached HEAD)") endif() endif() # create test make targets enable_testing() # collect list of broken tests, empty here to start fresh with each cmake run set(KRITA_BROKEN_TESTS "" CACHE INTERNAL "KRITA_BROKEN_TESTS") ############ ############# ## Options ## ############# ############ include(FeatureSummary) if (WIN32) option(USE_DRMINGW "Support the Dr. Mingw crash handler (only on windows)" ON) add_feature_info("Dr. Mingw" USE_DRMINGW "Enable the Dr. Mingw crash handler") if (MINGW) option(USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags (mingw-w64)" ON) add_feature_info("Linker Security Flags" USE_MINGW_HARDENING_LINKER "Enable DEP (NX), ASLR and high-entropy ASLR linker flags") if (USE_MINGW_HARDENING_LINKER) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--dynamicbase -Wl,--nxcompat -Wl,--disable-auto-image-base") if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") # Enable high-entropy ASLR for 64-bit # The image base has to be >4GB for HEASLR to be enabled. # The values used here are kind of arbitrary. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x140000000") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--high-entropy-va -Wl,--image-base,0x180000000") endif ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") else (USE_MINGW_HARDENING_LINKER) message(WARNING "Linker Security Flags not enabled!") endif (USE_MINGW_HARDENING_LINKER) endif (MINGW) endif () option(HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal." ON) configure_file(config-hide-safe-asserts.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hide-safe-asserts.h) add_feature_info("Hide Safe Asserts" HIDE_SAFE_ASSERTS "Don't show message box for \"safe\" asserts, just ignore them automatically and dump a message to the terminal.") option(USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking." ON) configure_file(config-hash-table-implementaion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hash-table-implementaion.h) add_feature_info("Lock free hash table" USE_LOCK_FREE_HASH_TABLE "Use lock free hash table instead of blocking.") option(FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true." OFF) add_feature_info("Foundation Build" FOUNDATION_BUILD "A Foundation build is a binary release build that can package some extra things like color themes. Linux distributions that build and install Krita into a default system location should not define this option to true.") option(KRITA_ENABLE_BROKEN_TESTS "Enable tests that are marked as broken" OFF) add_feature_info("Enable Broken Tests" KRITA_ENABLE_BROKEN_TESTS "Runs broken test when \"make test\" is invoked (use -DKRITA_ENABLE_BROKEN_TESTS=ON to enable).") option(LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode" ON) configure_file(config-limit-long-tests.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-limit-long-tests.h) add_feature_info("Limit long tests" LIMIT_LONG_TESTS "Run long running unittests in a limited quick mode") option(ENABLE_PYTHON_2 "Enables the compiler to look for Python 2.7 instead of Python 3. Some packaged scripts are not compatible with Python 2 and this should only be used if you really have to use 2.7." OFF) option(BUILD_KRITA_QT_DESIGNER_PLUGINS "Build Qt Designer plugins for Krita widgets" OFF) add_feature_info("Build Qt Designer plugins" BUILD_KRITA_QT_DESIGNER_PLUGINS "Builds Qt Designer plugins for Krita widgets (use -DBUILD_KRITA_QT_DESIGNER_PLUGINS=ON to enable).") include(MacroJPEG) ######################################################### ## Look for Python3 It is also searched by KF5, ## ## so we should request the correct version in advance ## ######################################################### function(TestCompileLinkPythonLibs OUTPUT_VARNAME) include(CheckCXXSourceCompiles) set(CMAKE_REQUIRED_INCLUDES ${PYTHON_INCLUDE_PATH}) set(CMAKE_REQUIRED_LIBRARIES ${PYTHON_LIBRARIES}) if (MINGW) set(CMAKE_REQUIRED_DEFINITIONS -D_hypot=hypot) endif (MINGW) unset(${OUTPUT_VARNAME} CACHE) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { Py_InitializeEx(0); }" ${OUTPUT_VARNAME}) endfunction() if(MINGW) if(ENABLE_PYTHON_2) message(FATAL_ERROR "Python 2.7 is not supported on Windows at the moment.") else(ENABLE_PYTHON_2) find_package(PythonInterp 3.6 EXACT) find_package(PythonLibs 3.6 EXACT) endif(ENABLE_PYTHON_2) if (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) if(ENABLE_PYTHON_2) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonLibrary 3.6) endif(ENABLE_PYTHON_2) TestCompileLinkPythonLibs(CAN_USE_PYTHON_LIBS) if (NOT CAN_USE_PYTHON_LIBS) message(FATAL_ERROR "Compiling with Python library failed, please check whether the architecture is correct. Python will be disabled.") endif (NOT CAN_USE_PYTHON_LIBS) endif (PYTHONLIBS_FOUND AND PYTHONINTERP_FOUND) else(MINGW) if(ENABLE_PYTHON_2) find_package(PythonInterp 2.7) find_package(PythonLibrary 2.7) else(ENABLE_PYTHON_2) find_package(PythonInterp 3.0) find_package(PythonLibrary 3.0) endif(ENABLE_PYTHON_2) endif(MINGW) ######################## ######################### ## Look for KDE and Qt ## ######################### ######################## find_package(ECM 5.22 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(ECMOptionalAddSubdirectory) include(ECMAddAppIcon) include(ECMSetupVersion) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(ECMMarkAsTest) include(ECMInstallIcons) include(CMakePackageConfigHelpers) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) # do not reorder to be alphabetical: this is the order in which the frameworks # depend on each other. find_package(KF5 ${MIN_FRAMEWORKS_VERSION} REQUIRED COMPONENTS Config WidgetsAddons Completion CoreAddons GuiAddons I18n ItemModels ItemViews WindowSystem Archive ) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test Concurrent Sql ) if (ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED COMPONENTS AndroidExtras ) endif() if (WIN32) set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI); } " QT_HAS_WINTAB_SWITCH ) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) option(USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt." ON) add_feature_info("Use Qt's Windows Tablet Support" USE_QT_TABLET_WINDOWS "Do not use Krita's forked Wintab and Windows Ink support on Windows, but leave everything to Qt.") configure_file(config_use_qt_tablet_windows.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_use_qt_tablet_windows.h) endif () set(CMAKE_REQUIRED_INCLUDES ${Qt5Core_INCLUDE_DIRS} ${Qt5Gui_INCLUDE_DIRS}) set(CMAKE_REQUIRED_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES}) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QSurfaceFormat fmt; fmt.setColorSpace(QSurfaceFormat::scRGBColorSpace); fmt.setColorSpace(QSurfaceFormat::bt2020PQColorSpace); } " HAVE_HDR ) configure_file(config-hdr.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-hdr.h) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } " HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY ) configure_file(config-high-dpi-scale-factor-rounding-policy.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-high-dpi-scale-factor-rounding-policy.h) if (WIN32) CHECK_CXX_SOURCE_COMPILES(" #include int main(int argc, char *argv[]) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } " HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT ) configure_file(config-set-has-border-in-full-screen-default.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-set-has-border-in-full-screen-default.h) endif (WIN32) unset(CMAKE_REQUIRED_INCLUDES) unset(CMAKE_REQUIRED_LIBRARIES) include (MacroAddFileDependencies) include (MacroBoolTo01) include (MacroEnsureOutOfSourceBuild) macro_ensure_out_of_source_build("Compiling Krita inside the source directory is not possible. Please refer to the build instruction https://community.kde.org/Krita#Build_Instructions") # Note: OPTIONAL_COMPONENTS does not seem to be reliable # (as of ECM 5.15.0, CMake 3.2) find_package(Qt5Multimedia ${MIN_QT_VERSION}) set_package_properties(Qt5Multimedia PROPERTIES DESCRIPTION "Qt multimedia integration" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide sound support for animations") macro_bool_to_01(Qt5Multimedia_FOUND HAVE_QT_MULTIMEDIA) configure_file(config-qtmultimedia.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-qtmultimedia.h ) if (NOT APPLE) find_package(Qt5Quick ${MIN_QT_VERSION}) set_package_properties(Qt5Quick PROPERTIES DESCRIPTION "QtQuick" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") macro_bool_to_01(Qt5Quick_FOUND HAVE_QT_QUICK) find_package(Qt5QuickWidgets ${MIN_QT_VERSION}) set_package_properties(Qt5QuickWidgets PROPERTIES DESCRIPTION "QtQuickWidgets" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used for the touch gui for Krita") endif() if (NOT WIN32 AND NOT APPLE AND NOT ANDROID) find_package(Qt5 ${MIN_QT_VERSION} REQUIRED X11Extras) find_package(Qt5DBus ${MIN_QT_VERSION}) set(HAVE_DBUS ${Qt5DBus_FOUND}) set_package_properties(Qt5DBus PROPERTIES DESCRIPTION "Qt DBUS integration" URL "https://www.qt.io/" TYPE OPTIONAL PURPOSE "Optionally used to provide a dbus api on Linux") find_package(KF5Crash ${MIN_FRAMEWORKS_VERSION}) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES DESCRIPTION "KDE's Crash Handler" URL "https://api.kde.org/frameworks-api/frameworks5-apidocs/kcrash/html/index.html" TYPE OPTIONAL PURPOSE "Optionally used to provide crash reporting on Linux") find_package(X11 REQUIRED COMPONENTS Xinput) set(HAVE_X11 TRUE) add_definitions(-DHAVE_X11) else() set(HAVE_DBUS FALSE) set(HAVE_X11 FALSE) endif() add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_STRICT_ITERATORS -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) #if (${Qt5_VERSION} VERSION_GREATER "5.14.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50F00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.13.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50E00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.12.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50D00) #elseif (${Qt5_VERSION} VERSION_GREATER "5.11.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50C00) #if(${Qt5_VERSION} VERSION_GREATER "5.10.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50B00) #if(${Qt5_VERSION} VERSION_GREATER "5.9.0" ) # add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50A00) #else() add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x50900) #endif() add_definitions(-DQT_DEPRECATED_WARNINGS) add_definitions(-DTRANSLATION_DOMAIN=\"krita\") # # The reason for this mode is that the Debug mode disable inlining # if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fext-numeric-literals") endif() option(KRITA_DEVS "For Krita developers. This modifies the DEBUG build type to use -O3 -g, while still enabling Q_ASSERT. This is necessary because the Qt5 cmake modules normally append QT_NO_DEBUG to any build type that is not labeled Debug") if (KRITA_DEVS) set(CMAKE_CXX_FLAGS_DEBUG "-O3 -g" CACHE STRING "" FORCE) endif() if(UNIX) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES};m") endif() if(WIN32) if(MSVC) # C4522: 'class' : multiple assignment operators specified set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4522") endif() endif() # KDECompilerSettings adds the `--export-all-symbols` linker flag. # We don't really need it. if(MINGW) string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") string(REPLACE "-Wl,--export-all-symbols" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") endif(MINGW) if(MINGW) # Hack CMake's variables to tell AR to create thin archives to reduce unnecessary writes. # Source of definition: https://github.com/Kitware/CMake/blob/v3.14.1/Modules/Platform/Windows-GNU.cmake#L128 # Thin archives: https://sourceware.org/binutils/docs/binutils/ar.html#index-thin-archives macro(mingw_use_thin_archive lang) foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) string(REGEX REPLACE "( [^ T]+) " "\\1T " CMAKE_${lang}_${rule} "${CMAKE_${lang}_${rule}}") endforeach() endmacro() mingw_use_thin_archive(CXX) endif(MINGW) # enable exceptions globally kde_enable_exceptions() set(KRITA_DEFAULT_TEST_DATA_DIR ${CMAKE_SOURCE_DIR}/sdk/tests/data/) macro(macro_add_unittest_definitions) add_definitions(-DFILES_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data/") add_definitions(-DFILES_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}") add_definitions(-DFILES_DEFAULT_DATA_DIR="${KRITA_DEFAULT_TEST_DATA_DIR}") add_definitions(-DSYSTEM_RESOURCES_DATA_DIR="${CMAKE_SOURCE_DIR}/krita/data/") endmacro() # overcome some platform incompatibilities if(WIN32) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/winquirks) add_definitions(-D_USE_MATH_DEFINES) add_definitions(-DNOMINMAX) set(WIN32_PLATFORM_NET_LIBS ws2_32.lib netapi32.lib) endif() # set custom krita plugin installdir if (ANDROID) # use default ABI if (NOT ANDROID_ABI) set (ANDROID_ABI armeabi-v7a) endif() set (ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}) # set (DATA_INSTALL_DIR ${CMAKE_INSTALL_PREFIX}/assets) else() set (KRITA_PLUGIN_INSTALL_DIR ${LIB_INSTALL_DIR}/kritaplugins) endif() ########################### ############################ ## Required dependencies ## ############################ ########################### # FIXME: Still hardcoded if (ANDROID) set (Boost_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/include/boost-1_69) set (Boost_LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/i/${ANDROID_ABI}/lib) set (KF5_LIBRARIES ${CMAKE_CURRENT_BINARY_DIR}/kf5/kde/install/lib) endif() find_package(PNG REQUIRED) list (APPEND ANDROID_EXTRA_LIBS ${PNG_LIBRARY}) if (APPLE) # this is not added correctly on OSX -- see https://forum.kde.org/viewtopic.php?f=139&t=101867&p=221242#p221242 include_directories(SYSTEM ${PNG_INCLUDE_DIR}) endif() add_definitions(-DBOOST_ALL_NO_LIB) find_package(Boost 1.55 REQUIRED COMPONENTS system) include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) ## ## Test for GNU Scientific Library ## find_package(GSL) set_package_properties(GSL PROPERTIES URL "https://www.gnu.org/software/gsl" TYPE RECOMMENDED PURPOSE "Required by Krita's Transform tool.") macro_bool_to_01(GSL_FOUND HAVE_GSL) configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h ) if (GSL_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES}) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### find_package(ZLIB) set_package_properties(ZLIB PROPERTIES DESCRIPTION "Compression library" URL "https://www.zlib.net/" TYPE OPTIONAL PURPOSE "Optionally used by the G'Mic and the PSD plugins") macro_bool_to_01(ZLIB_FOUND HAVE_ZLIB) find_package(OpenEXR) set_package_properties(OpenEXR PROPERTIES DESCRIPTION "High dynamic-range (HDR) image file format" URL "https://www.openexr.com" TYPE OPTIONAL PURPOSE "Required by the Krita OpenEXR filter") macro_bool_to_01(OPENEXR_FOUND HAVE_OPENEXR) set(LINK_OPENEXR_LIB) if(OPENEXR_FOUND) include_directories(SYSTEM ${OPENEXR_INCLUDE_DIR}) set(LINK_OPENEXR_LIB ${OPENEXR_LIBRARIES}) add_definitions(${OPENEXR_DEFINITIONS}) endif() find_package(TIFF) set_package_properties(TIFF PROPERTIES DESCRIPTION "TIFF Library and Utilities" URL "http://www.libtiff.org" TYPE OPTIONAL PURPOSE "Required by the Krita TIFF filter") if (TIFF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${TIFF_LIBRARY}) endif() find_package(JPEG) set_package_properties(JPEG PROPERTIES DESCRIPTION "Free library for JPEG image compression. Note: libjpeg8 is NOT supported." URL "https://www.libjpeg-turbo.org" TYPE OPTIONAL PURPOSE "Required by the Krita JPEG filter") if (JPEG_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${JPEG_LIBRARY}) endif() find_package(GIF) set_package_properties(GIF PROPERTIES DESCRIPTION "Library for loading and saving gif files." URL "http://giflib.sourceforge.net/" TYPE OPTIONAL PURPOSE "Required by the Krita GIF filter") if (GIF_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${GIF_LIBRARY}) endif() find_package(HEIF "1.3.0") set_package_properties(HEIF PROPERTIES DESCRIPTION "Library for loading and saving heif files." URL "https://github.com/strukturag/libheif" TYPE OPTIONAL PURPOSE "Required by the Krita HEIF filter") find_package(OpenJPEG "2.3.0") set_package_properties(OpenJPEG PROPERTIES DESCRIPTION "Library for loading and saving jp2000 files." URL "https://www.openjpeg.org/" TYPE OPTIONAL PURPOSE "Required by the Krita JP2000 filter") set(LIBRAW_MIN_VERSION "0.16") find_package(LibRaw ${LIBRAW_MIN_VERSION}) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library to decode RAW images" URL "https://www.libraw.org/" TYPE OPTIONAL PURPOSE "Required to build the raw import plugin") find_package(FFTW3) set_package_properties(FFTW3 PROPERTIES DESCRIPTION "A fast, free C FFT library" URL "http://www.fftw.org/" TYPE OPTIONAL PURPOSE "Required by the Krita for fast convolution operators and some G'Mic features") macro_bool_to_01(FFTW3_FOUND HAVE_FFTW3) if (FFTW3_FOUND) list (APPEND ANDROID_EXTRA_LIBS ${FFTW3_LIBRARY}) endif() find_package(OCIO) set_package_properties(OCIO PROPERTIES DESCRIPTION "The OpenColorIO Library" URL "https://www.opencolorio.org" TYPE OPTIONAL PURPOSE "Required by the Krita LUT docker") macro_bool_to_01(OCIO_FOUND HAVE_OCIO) set_package_properties(PythonLibrary PROPERTIES DESCRIPTION "Python Library" URL "https://www.python.org" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYTHONLIBS_FOUND HAVE_PYTHONLIBS) find_package(SIP "4.19.13") set_package_properties(SIP PROPERTIES DESCRIPTION "Support for generating SIP Python bindings" URL "https://www.riverbankcomputing.com/software/sip/download" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(SIP_FOUND HAVE_SIP) find_package(PyQt5 "5.6.0") set_package_properties(PyQt5 PROPERTIES DESCRIPTION "Python bindings for Qt5." URL "https://www.riverbankcomputing.com/software/pyqt/download5" TYPE OPTIONAL PURPOSE "Required by the Krita PyQt plugin") macro_bool_to_01(PYQT5_FOUND HAVE_PYQT5) ## ## Look for OpenGL ## # TODO: see if there is a better check for QtGui being built with opengl support (and thus the QOpenGL* classes) if(Qt5Gui_OPENGL_IMPLEMENTATION) message(STATUS "Found QtGui OpenGL support") else() message(FATAL_ERROR "Did NOT find QtGui OpenGL support. Check your Qt configuration. You cannot build Krita without Qt OpenGL support.") endif() ## ## Test for eigen3 ## find_package(Eigen3 3.0 REQUIRED) set_package_properties(Eigen3 PROPERTIES DESCRIPTION "C++ template library for linear algebra" URL "http://eigen.tuxfamily.org" TYPE REQUIRED) ## ## Test for exiv2 ## find_package(LibExiv2 0.16 REQUIRED) if (ANDROID) list (APPEND ANDROID_EXTRA_LIBS ${LibExiv2_LIBRARIES}) # because libexiv2 depends on libexpat and it is installed in the same folder get_filename_component (_base_dir ${LibExiv2_LIBRARIES} DIRECTORY) list (APPEND ANDROID_EXTRA_LIBS ${_base_dir}/libexpat.so) endif() ## ## Test for lcms ## find_package(LCMS2 2.4 REQUIRED) set_package_properties(LCMS2 PROPERTIES DESCRIPTION "LittleCMS Color management engine" URL "http://www.littlecms.com" TYPE REQUIRED PURPOSE "Will be used for color management and is necessary for Krita") if(LCMS2_FOUND) if(NOT ${LCMS2_VERSION} VERSION_LESS 2040 ) set(HAVE_LCMS24 TRUE) endif() set(HAVE_REQUIRED_LCMS_VERSION TRUE) set(HAVE_LCMS2 TRUE) endif() list (APPEND ANDROID_EXTRA_LIBS ${LCMS2_LIBRARIES}) ## ## Test for Vc ## set(OLD_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ) set(HAVE_VC FALSE) if (NOT ${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") if(NOT MSVC) find_package(Vc 1.1.0) set_package_properties(Vc PROPERTIES DESCRIPTION "Portable, zero-overhead SIMD library for C++" URL "https://github.com/VcDevel/Vc" TYPE OPTIONAL PURPOSE "Required by the Krita for vectorization") macro_bool_to_01(Vc_FOUND HAVE_VC) endif() endif() configure_file(config-vc.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-vc.h ) if(HAVE_VC) message(STATUS "Vc found!") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/vc") include (VcMacros) if(Vc_COMPILER_IS_CLANG) set(ADDITIONAL_VC_FLAGS "-ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() elseif (NOT MSVC) set(ADDITIONAL_VC_FLAGS "-fabi-version=0 -ffp-contract=fast") if(NOT WIN32) set(ADDITIONAL_VC_FLAGS "${ADDITIONAL_VC_FLAGS} -fPIC") endif() endif() macro(ko_compile_for_all_implementations_no_scalar _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() macro(ko_compile_for_all_implementations _objs _src) vc_compile_for_all_implementations(${_objs} ${_src} FLAGS ${ADDITIONAL_VC_FLAGS} ONLY Scalar SSE2 SSSE3 SSE4_1 AVX AVX2+FMA+BMI2) endmacro() endif() set(CMAKE_MODULE_PATH ${OLD_CMAKE_MODULE_PATH} ) add_definitions(${QT_DEFINITIONS} ${QT_QTDBUS_DEFINITIONS}) ## ## Test endianness ## include (TestBigEndian) test_big_endian(CMAKE_WORDS_BIGENDIAN) ## ## Test for qt-poppler ## find_package(Poppler COMPONENTS Qt5) set_package_properties(Poppler PROPERTIES DESCRIPTION "A PDF rendering library" URL "https://poppler.freedesktop.org/" TYPE OPTIONAL PURPOSE "Required by the Krita PDF filter.") ## ## Test for quazip ## find_package(QuaZip 0.6) set_package_properties(QuaZip PROPERTIES DESCRIPTION "A library for reading and writing zip files" URL "https://stachenov.github.io/quazip/" TYPE REQUIRED PURPOSE "Needed for reading and writing KRA and ORA files" ) # FIXME: better way to do this? list (APPEND ANDROID_EXTRA_LIBS ${QUAZIP_LIBRARIES} ${EXPAT_LIBRARY} ${KF5_LIBRARIES}/libKF5Completion.so ${KF5_LIBRARIES}/libKF5WindowSystem.so ${KF5_LIBRARIES}/libKF5WidgetsAddons.so ${KF5_LIBRARIES}/libKF5ItemViews.so ${KF5_LIBRARIES}/libKF5ItemModels.so ${KF5_LIBRARIES}/libKF5GuiAddons.so ${KF5_LIBRARIES}/libKF5I18n.so ${KF5_LIBRARIES}/libKF5CoreAddons.so ${KF5_LIBRARIES}/libKF5ConfigGui.so ${KF5_LIBRARIES}/libKF5ConfigCore.so ${KF5_LIBRARIES}/libKF5Archive.so) ## ## Test for Atomics ## include(CheckAtomic) ############################ ############################# ## Add Krita helper macros ## ############################# ############################ include(MacroKritaAddBenchmark) #################### ##################### ## Define includes ## ##################### #################### # for config.h and includes (if any?) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces ) add_subdirectory(libs) add_subdirectory(plugins) if (BUILD_TESTING) add_subdirectory(benchmarks) endif() add_subdirectory(krita) configure_file(KoConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KoConfig.h ) configure_file(config_convolution.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config_convolution.h) configure_file(config-ocio.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ocio.h ) check_function_exists(powf HAVE_POWF) configure_file(config-powf.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-powf.h) if(WIN32) include(${CMAKE_CURRENT_LIST_DIR}/packaging/windows/installer/ConfigureInstallerNsis.cmake) endif() message("\nBroken tests:") foreach(tst ${KRITA_BROKEN_TESTS}) message(" * ${tst}") endforeach() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/po OR EXISTS ${CMAKE_CURRENT_BINARY_DIR}/po ) find_package(KF5I18n CONFIG REQUIRED) ki18n_install(po) endif() if(DEFINED QTANDROID_EXPORTED_TARGET AND NOT TARGET "create-apk") set (_CMAKE_ANDROID_DIR "${ECM_DIR}/../toolchain") list(LENGTH QTANDROID_EXPORTED_TARGET targetsCount) include(${_CMAKE_ANDROID_DIR}/ECMAndroidDeployQt.cmake) math(EXPR last "${targetsCount}-1") foreach(idx RANGE 0 ${last}) list(GET QTANDROID_EXPORTED_TARGET ${idx} exportedTarget) list(GET ANDROID_APK_DIR ${idx} APK_DIR) if(APK_DIR AND NOT EXISTS "${ANDROID_APK_DIR}/AndroidManifest.xml" AND IS_ABSOLUTE ANDROID_APK_DIR) message(FATAL_ERROR "Cannot find ${APK_DIR}/AndroidManifest.xml according to ANDROID_APK_DIR. ${ANDROID_APK_DIR} ${exportedTarget}") elseif(NOT APK_DIR) get_filename_component(_qt5Core_install_prefix "${Qt5Core_DIR}/../../../" ABSOLUTE) set(APK_DIR "${_qt5Core_install_prefix}/src/android/templates/") endif() ecm_androiddeployqt("${exportedTarget}" "${ECM_ADDITIONAL_FIND_ROOT_PATH}") set_target_properties(create-apk-${exportedTarget} PROPERTIES ANDROID_APK_DIR "${APK_DIR}") endforeach() elseif(ANDROID) message(STATUS "You can export a target by specifying -DQTANDROID_EXPORTED_TARGET= and -DANDROID_APK_DIR=") endif() diff --git a/README.md b/README.md index 4c87ca2901..bf67829595 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,51 @@ ![Picture](https://krita.org/wp-content/uploads/2019/04/krita-logo-2019.png) | Jenkins CI Name | Master | Stable | | --------------- | ------ | ------ | | OpenSuse Qt 5.12 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20SUSEQt5.12/) |[![Build Status](https://build.kde.org/buildStatus/icon?job=Extragear%2Fkrita%2Fstable-kf5-qt5+SUSEQt5.12)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20SUSEQt5.12/)| | FreeBSD Qt 5.13 | [![Build Status](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/kf5-qt5%20FreeBSDQt5.13/) |[![Build Status](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/badge/icon)](https://build.kde.org/job/Extragear/job/krita/job/stable-kf5-qt5%20FreeBSDQt5.13/)| Krita is a free and open source digital painting application. It is for artists who want to create professional work from start to end. Krita is used by comic book artists, illustrators, concept artists, matte and texture painters and in the digital VFX industry. If you are reading this on Github, be aware that this is just a mirror. Our real code repository is provided by KDE: https://invent.kde.org/kde/krita ![Picture](https://krita.org/wp-content/uploads/2016/04/krita-30-screenshot.jpg) ### User Manual https://docs.krita.org/en/user_manual.html ### Development Notes and Build Instructions If you're building on Windows or OSX you'll need to build some third-party dependencies first. You should look at the README in the 3rdparty folder for directions. -If you're building on Linux, please follow David Revoy's Cat Guide: http://www.davidrevoy.com/article193/guide-building-krita-on-linux-for-cats +If you're building on Linux, please follow [the online documentation](https://docs.krita.org/en/untranslatable_pages/building_krita.html). Other developer guides, notes and wiki: https://docs.krita.org/en/untranslatable_pages.html Apidox: https://api.kde.org/extragear-api/graphics-apidocs/krita/html/index.html ### Bugs and Wishes https://bugs.kde.org/buglist.cgi?bug_status=UNCONFIRMED&bug_status=CONFIRMED&bug_status=ASSIGNED&bug_status=REOPENED&list_id=1315444&product=krita&query_format=advanced ### Discussion Forum http://forum.kde.org/viewforum.php?f=136 ### IRC channel Most of the developers hang out here. If you are interested in helping with the project this is a great place to start. Many of the developers based in Europe so they may be offline depending on when you join. irc.freenode.net, #krita ### Project Website http://www.krita.org ### License Krita as a whole is licensed under the GNU Public License, Version 3. Individual files may have a different, but compatible license. diff --git a/bla.png b/bla.png new file mode 100644 index 0000000000..8da281efb6 Binary files /dev/null and b/bla.png differ diff --git a/krita/data/templates/animation/.directory b/krita/data/templates/animation/.directory index 6cbbe623b2..d8cd91b610 100644 --- a/krita/data/templates/animation/.directory +++ b/krita/data/templates/animation/.directory @@ -1,29 +1,30 @@ [Desktop Entry] Name=Animation Templates Name[ar]=قوالب الحركات Name[ca]=Plantilles d'animació Name[ca@valencia]=Plantilles d'animació Name[cs]=Šablony animací: Name[de]=Animations-Vorlagen Name[el]=Πρότυπα εφέ κίνησης Name[en_GB]=Animation Templates Name[es]=Plantillas de animación +Name[et]=Animatsioonimallid Name[eu]=Animazio-txantiloiak Name[fi]=Animaatiopohjat Name[fr]=Modèles pour animation Name[gl]=Modelos de animación Name[is]=Sniðmát fyrir hreyfimyndir Name[it]=Modelli di animazioni Name[ko]=애니메이션 템플릿 Name[nl]=Animatiesjablonen Name[nn]=Animasjonsmalar Name[pl]=Szablony animacji Name[pt]=Modelos de Animações Name[pt_BR]=Modelos de animação Name[sv]=Animeringsmallar Name[tr]=Canlandırma Şablonları Name[uk]=Шаблони анімацій Name[x-test]=xxAnimation Templatesxx Name[zh_CN]=动画模板 Name[zh_TW]=動畫範本 X-KDE-DefaultTab=true diff --git a/krita/main.cc b/krita/main.cc index d2d84e3d89..1523923528 100644 --- a/krita/main.cc +++ b/krita/main.cc @@ -1,598 +1,598 @@ /* * Copyright (c) 1999 Matthias Elter * 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 #include #include #include #include "data/splash/splash_screen.xpm" #include "data/splash/splash_holidays.xpm" #include "data/splash/splash_screen_x2.xpm" #include "data/splash/splash_holidays_x2.xpm" #include "KisDocument.h" #include "kis_splash_screen.h" #include "KisPart.h" #include "KisApplicationArguments.h" #include #include "input/KisQtWidgetsTweaker.h" #include #include #ifdef Q_OS_ANDROID #include #endif #if defined Q_OS_WIN #include "config_use_qt_tablet_windows.h" #include #ifndef USE_QT_TABLET_WINDOWS #include #include #else #include #endif #include "config-high-dpi-scale-factor-rounding-policy.h" #include "config-set-has-border-in-full-screen-default.h" #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT #include #endif #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 #ifdef Q_OS_WIN namespace { typedef enum ORIENTATION_PREFERENCE { ORIENTATION_PREFERENCE_NONE = 0x0, ORIENTATION_PREFERENCE_LANDSCAPE = 0x1, ORIENTATION_PREFERENCE_PORTRAIT = 0x2, ORIENTATION_PREFERENCE_LANDSCAPE_FLIPPED = 0x4, ORIENTATION_PREFERENCE_PORTRAIT_FLIPPED = 0x8 } ORIENTATION_PREFERENCE; typedef BOOL WINAPI (*pSetDisplayAutoRotationPreferences_t)( ORIENTATION_PREFERENCE orientation ); void resetRotation() { QLibrary user32Lib("user32"); if (!user32Lib.load()) { qWarning() << "Failed to load user32.dll! This really should not happen."; return; } pSetDisplayAutoRotationPreferences_t pSetDisplayAutoRotationPreferences = reinterpret_cast(user32Lib.resolve("SetDisplayAutoRotationPreferences")); if (!pSetDisplayAutoRotationPreferences) { dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences"; return; } bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE); dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result; } } // namespace #endif #ifdef Q_OS_ANDROID extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisPart *kisPart = KisPart::instance(); QList> list = kisPart->documents(); for (QPointer &doc: list) { doc->autoSaveOnPause(); } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } extern "C" JNIEXPORT void JNICALL Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/, jobject /*obj*/, jint /*n*/) { if (!KisPart::exists()) return; KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow(); mainWindow->viewFullscreen(false); } __attribute__ ((visibility ("default"))) #endif extern "C" int main(int argc, char **argv) { // The global initialization of the random generator qsrand(time(0)); bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty(); #if defined HAVE_X11 qputenv("QT_QPA_PLATFORM", "xcb"); #endif // Workaround a bug in QNetworkManager qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); // A per-user unique string, without /, because QLocalServer cannot use names with a / in it QString key = "Krita4" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_"); key = key.replace(":", "_").replace("\\","_"); QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true); #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY // This rounding policy depends on a series of patches to Qt related to // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied // in ext_qt for WIndows (patches 0031-0036). // // The rounding policy can be set externally by setting the environment // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following: // Round: Round up for .5 and above. // Ceil: Always round up. // Floor: Always round down. // RoundPreferFloor: Round up for .75 and above. // PassThrough: Don't round. // // The default is set to RoundPreferFloor for better behaviour than before, // but can be overridden by the above environment variable. QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); #endif #ifdef Q_OS_ANDROID const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE"; const QStringList permissions = { write_permission }; const QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync(QStringList(permissions)); if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) { // TODO: show a dialog and graciously exit dbgKrita << "Permission denied by the user"; } else { dbgKrita << "Permission granted"; } #endif const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); bool singleApplication = true; bool enableOpenGLDebug = false; bool openGLDebugSynchronous = false; bool logUsage = true; { singleApplication = kritarc.value("EnableSingleApplication", true).toBool(); if (kritarc.value("EnableHiDPI", true).toBool()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } if (!qgetenv("KRITA_HIDPI").isEmpty()) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY if (kritarc.value("EnableHiDPIFractionalScaling", true).toBool()) { QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); } #endif if (!qgetenv("KRITA_OPENGL_DEBUG").isEmpty()) { enableOpenGLDebug = true; } else { enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool(); } if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) { openGLDebugSynchronous = true; } KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc); KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto; logUsage = kritarc.value("LogUsage", true).toBool(); #ifdef Q_OS_WIN const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString(); #else const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString(); #endif preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString); const KisOpenGL::RendererConfig config = KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug); KisOpenGL::setDefaultSurfaceConfig(config); KisOpenGL::setDebugSynchronous(openGLDebugSynchronous); #ifdef Q_OS_WIN // HACK: https://bugs.kde.org/show_bug.cgi?id=390651 resetRotation(); #endif } if (logUsage) { KisUsageLogger::initialize(); } QString root; QString language; { // Create a temporary application to get the root QCoreApplication app(argc, argv); Q_UNUSED(app); root = KoResourcePaths::getApplicationRoot(); QSettings languageoverride(configPath + QStringLiteral("/klanguageoverridesrc"), QSettings::IniFormat); languageoverride.beginGroup(QStringLiteral("Language")); language = languageoverride.value(qAppName(), "").toString(); } #ifdef Q_OS_LINUX { QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS"); if (originalXdgDataDirs.isEmpty()) { // We don't want to completely override the default originalXdgDataDirs = "/usr/local/share/:/usr/share/"; } qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs); } #else qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share")); #endif dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS"); // Now that the paths are set, set the language. First check the override from the language // selection dialog. dbgKrita << "Override language:" << language; bool rightToLeft = false; if (!language.isEmpty()) { KLocalizedString::setLanguages(language.split(":")); // And override Qt's locale, too qputenv("LANG", language.split(":").first().toLocal8Bit()); QLocale locale(language.split(":").first()); QLocale::setDefault(locale); const QStringList rtlLanguages = QStringList() << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi"; if (rtlLanguages.contains(language.split(':').first())) { rightToLeft = true; } } else { dbgKrita << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG"); // And if there isn't one, check the one set by the system. QLocale locale = QLocale::system(); if (locale.name() != QStringLiteral("en")) { QStringList uiLanguages = locale.uiLanguages(); for (QString &uiLanguage : uiLanguages) { // This list of language codes that can have a specifier should // be extended whenever we have translations that need it; right // now, only en, pt, zh are in this situation. if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) { uiLanguage.replace(QChar('-'), QChar('_')); } else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) { uiLanguage = "zh_TW"; } else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) { uiLanguage = "zh_CN"; } } for (int i = 0; i < uiLanguages.size(); i++) { QString uiLanguage = uiLanguages[i]; // Strip the country code int idx = uiLanguage.indexOf(QChar('-')); if (idx != -1) { uiLanguage = uiLanguage.left(idx); uiLanguages.replace(i, uiLanguage); } } dbgKrita << "Converted ui languages:" << uiLanguages; qputenv("LANG", uiLanguages.first().toLocal8Bit()); #ifdef Q_OS_MAC // See https://bugs.kde.org/show_bug.cgi?id=396370 KLocalizedString::setLanguages(QStringList() << uiLanguages.first()); #else KLocalizedString::setLanguages(QStringList() << uiLanguages); #endif } } #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc); QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab); if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") && qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) { QRect customTabletRect; KisDlgCustomTabletResolution::Mode tabletMode = KisDlgCustomTabletResolution::getTabletMode(&customTabletRect); KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect); } #endif // first create the application so we can create a pixmap KisApplication app(key, argc, argv); KisUsageLogger::writeHeader(); KisOpenGL::initialize(); #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) { QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true); } #endif if (!language.isEmpty()) { if (rightToLeft) { app.setLayoutDirection(Qt::RightToLeft); } else { app.setLayoutDirection(Qt::LeftToRight); } } KLocalizedString::setApplicationDomain("krita"); dbgKrita << "Available translations" << KLocalizedString::availableApplicationTranslations(); dbgKrita << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita"); #ifdef Q_OS_WIN QDir appdir(KoResourcePaths::getApplicationRoot()); QString path = qgetenv("PATH"); qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";" + appdir.absolutePath() + "/lib" + ";" + appdir.absolutePath() + "/Frameworks" + ";" + appdir.absolutePath() + ";" + path)); dbgKrita << "PATH" << qgetenv("PATH"); #endif if (qApp->applicationDirPath().contains(KRITA_BUILD_DIR)) { qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location."); } #if defined HAVE_KCRASH KCrash::initialize(); #elif defined USE_DRMINGW tryInitDrMingw(); #endif // If we should clear the config, it has to be done as soon as possible after // KisApplication has been created. Otherwise the config file may have been read // and stored in a KConfig object we have no control over. app.askClearConfig(); KisApplicationArguments args(app); if (singleApplication && app.isRunning()) { // only pass arguments to main instance if they are not for batch processing // any batch processing would be done in this separate instance const bool batchRun = args.exportAs() || args.exportSequence(); if (!batchRun) { QByteArray ba = args.serialize(); if (app.sendMessage(ba)) { return 0; } } } if (!runningInKDE) { // Icons in menus are ugly and distracting app.setAttribute(Qt::AA_DontShowIconsInMenus); } #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) app.setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif app.installEventFilter(KisQtWidgetsTweaker::instance()); if (!args.noSplash()) { // then create the pixmap from an xpm: we cannot get the // location of our datadir before we've started our components, // so use an xpm. QDate currentDate = QDate::currentDate(); QWidget *splash = 0; if (currentDate > QDate(currentDate.year(), 12, 4) || currentDate < QDate(currentDate.year(), 1, 9)) { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_holidays_xpm), QPixmap(splash_holidays_x2_xpm)); } else { splash = new KisSplashScreen(app.applicationVersion(), QPixmap(splash_screen_xpm), QPixmap(splash_screen_x2_xpm)); } app.setSplashScreen(splash); } #if defined Q_OS_WIN KisConfig cfg(false); bool supportedWindowsVersion = true; QOperatingSystemVersion osVersion = QOperatingSystemVersion::current(); if (osVersion.type() == QOperatingSystemVersion::Windows) { if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) { supportedWindowsVersion = true; } else { supportedWindowsVersion = false; if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running an unsupported version of Windows: %1.\n" "This is not recommended. Do not report any bugs.\n" "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name())); cfg.writeEntry("WarnedAboutUnsupportedWindows", true); } } } #ifndef USE_QT_TABLET_WINDOWS { if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) { cfg.setUseWin8PointerInput(false); } if (!cfg.useWin8PointerInput()) { bool hasWinTab = KisTabletSupportWin::init(); if (!hasWinTab && supportedWindowsVersion) { if (KisTabletSupportWin8::isPenDeviceAvailable()) { // Use WinInk automatically cfg.setUseWin8PointerInput(true); } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) { if (KisTabletSupportWin8::isAvailable()) { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } else { QMessageBox::information(nullptr, i18n("Krita Tablet Support"), i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."), QMessageBox::Ok, QMessageBox::Ok); } cfg.writeEntry("WarnedAboutMissingWinTab", true); } } } if (cfg.useWin8PointerInput()) { KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8(); if (penFilter->init()) { // penFilter.registerPointerDeviceNotifications(); app.installNativeEventFilter(penFilter); dbgKrita << "Using Win8 Pointer Input for tablet support"; } else { dbgKrita << "No Win8 Pointer Input available"; delete penFilter; } } } #elif defined QT_HAS_WINTAB_SWITCH // Check if WinTab/WinInk has actually activated const bool useWinTabAPI = app.testAttribute(Qt::AA_MSWindowsUseWinTabAPI); if (useWinTabAPI != !cfg.useWin8PointerInput()) { cfg.setUseWin8PointerInput(useWinTabAPI); } #endif #endif app.setAttribute(Qt::AA_CompressHighFrequencyEvents, false); // Set up remote arguments. QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)), &app, SLOT(remoteArguments(QByteArray,QObject*))); QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), &app, SLOT(fileOpenRequested(QString))); // Hardware information - KisUsageLogger::write("\nHardware Information\n"); - KisUsageLogger::write(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); - KisUsageLogger::write(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); - KisUsageLogger::write(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); - KisUsageLogger::write(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); + KisUsageLogger::writeSysInfo("\nHardware Information\n"); + KisUsageLogger::writeSysInfo(QString(" GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString())); + KisUsageLogger::writeSysInfo(QString(" Memory: %1 Mb").arg(KisImageConfig(true).totalRAM())); + KisUsageLogger::writeSysInfo(QString(" Number of Cores: %1").arg(QThread::idealThreadCount())); + KisUsageLogger::writeSysInfo(QString(" Swap Location: %1\n").arg(KisImageConfig(true).swapDir())); KisConfig(true).logImportantSettings(); if (!app.start(args)) { KisUsageLogger::log("Could not start Krita Application"); return 1; } int state = app.exec(); { QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", "OPENGL_SUCCESS"); } if (logUsage) { KisUsageLogger::close(); } return state; } diff --git a/krita/org.kde.krita.appdata.xml b/krita/org.kde.krita.appdata.xml index c549db0adc..3535cf4b23 100644 --- a/krita/org.kde.krita.appdata.xml +++ b/krita/org.kde.krita.appdata.xml @@ -1,381 +1,389 @@ org.kde.krita org.kde.krita.desktop CC0-1.0 GPL-3.0-only Krita Foundation Fundació Krita Fundació Krita Krita Foundation Krita Foundation Krita Foundation Fundación Krita + Krita sihtasutus Krita Fundazioa Krita Foundation La Fondation Krita Fundación Krita Asas Krita Fondazione Krita Krita Foundation Krita Foundation Krita Foundation Fundacja Krity Fundação do Krita Krita Foundation Nadácia Krita Krita-stiftelsen Krita Vakfı Фундація Krita xxKrita Foundationxx Krita 基金会 Krita 基金會 foundation@krita.org Krita كريتا Krita Krita Krita Krita Krita Krita Krita + Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita Krita xxKritaxx Krita Krita Digital Painting, Creative Freedom رسم رقميّ، حريّة إبداعيّة Digitalno crtanje, kreativna sloboda Dibuix digital, Llibertat creativa Dibuix digital, Llibertat creativa Digitální malování, svoboda tvorby Digital tegning, kunstnerisk frihed Digitales Malen, kreative Freiheit Ψηφιακή ζωγραφική, δημιουργική ελευθερία Digital Painting, Creative Freedom Pintura digital, libertad creativa Digitaalne joonistamine, loominguline vabadus Margolan digitala, sormen askatasuna Digitaalimaalaus, luova vapaus Peinture numérique, liberté créatrice Debuxo dixital, liberdade creativa Pictura digital, Libertate creative Pelukisan Digital, Kebebasan Berkreatif Pittura digitale, libertà creativa 디지털 페인팅, 자유로운 창의성 Digital Painting, Creative Freedom Digital teikning – kreativ fridom Cyfrowe malowanie, Wolność Twórcza Pintura Digital, Liberdade Criativa Pintura digital, liberdade criativa Цифровое рисование. Творческая свобода Digitálne maľovanie, kreatívna sloboda Digital målning, kreativ frihet Sayısal Boyama, Yaratıcı Özgürlük Цифрове малювання, творча свобода xxDigital Painting, Creative Freedomxx 自由挥洒数字绘画的无限创意 數位繪畫,創作自由

Krita is the full-featured digital art studio.

Krita je potpuni digitalni umjetnički studio.

El Krita és l'estudi d'art digital ple de funcionalitats.

El Krita és l'estudi d'art digital ple de funcionalitats.

Krita ist ein digitales Designstudio mit umfangreichen Funktionen.

Το Krita είναι ένα πλήρες χαρακτηριστικών ψηφιακό ατελιέ.

Krita is the full-featured digital art studio.

Krita es un estudio de arte digital completo

Krita on rohkete võimalustega digitaalkunstistuudio.

Krita arte lantegi digital osoa da.

Krita on täyspiirteinen digitaiteen ateljee.

Krita est le studio d'art numérique complet.

Krita é un estudio completo de arte dixital.

Krita es le studio de arte digital complete.

Krita adalah studio seni digital yang penuh dengan fitur.

Krita è uno studio d'arte digitale completo.

Krita は、フル機能を備えたデジタルなアートスタジオです。

Krita는 디지털 예술 스튜디오입니다.

Krita is de digitale kunststudio vol mogelijkheden.

Krita er ei funksjonsrik digital teiknestove.

Krita jest pełnowymiarowym, cyfrowym studiem artystycznym

O Krita é o estúdio de arte digital completo.

O Krita é o estúdio de arte digital completo.

Krita — полнофункциональный инструмент для создания цифровой графики.

Krita je plne vybavené digitálne umelecké štúdio.

Krita är den fullfjädrade digitala konststudion.

Krita, tam özellikli dijital sanat stüdyosudur.

Krita — повноцінний комплекс для створення цифрових художніх творів.

xxKrita is the full-featured digital art studio.xx

Krita 是一款功能齐全的数字绘画工作室软件。

Krita 是全功能的數位藝術工作室。

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

On je savršen za skiciranje i slikanje i predstavlja finalno rješenje za kreiranje digitalnih slika od nule s majstorima

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

És perfecte per fer esbossos i pintar, i presenta una solució final per crear fitxers de dibuix digital des de zero per a mestres.

Είναι ιδανικό για σκιτσογραφία και ζωγραφική, και παρουσιάζει μια από άκρη σε άκρη λύση για τη δημιουργία από το μηδέν αρχείων ψηφιακης ζωγραφικής από τους δασκάλους της τέχνης.

It is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.

Es perfecto para diseñar y pintar, y ofrece una solución completa para crear desde cero archivos de pintura digital apta para profesionales.

See on suurepärane töövahend visandite ja joonistuste valmistamiseks ning annab andekatele kunstnikele võimaluse luua digitaalpilt algusest lõpuni just oma käe järgi.

Zirriborratzeko eta margotzeko ezin hobea da, eta margolan digitalen fitxategiak hutsetik sortzeko muturretik-muturrera konponbide bat aurkezten du, maisuentzako mailakoa.

Se on täydellinen luonnosteluun ja maalaukseen ja tarjoaa kokonaisratkaisun digitaalisten kuvatiedostojen luomiseen alusta alkaen.

Il est parfait pour crayonner et peindre, et constitue une solution de bout en bout pour créer des fichier de peinture numérique depuis la feuille blanche jusqu'au épreuves finales.

Resulta perfecto para debuxar e pintar, e presenta unha solución completa que permite aos mestres crear ficheiros de debuxo dixital desde cero.

Illo es perfecte pro schizzar e pinger, e presenta un solution ab fin al fin pro crear files de pictura digital ab grattamentos per maestros.

Ini adalah sempurna untuk mensketsa dan melukis, dan menghadirkan sebuah solusi untuk menciptakan file-file pelukisan digital dari goresan si pelukis ulung.

Perfetto per fare schizzi e dipingere, prevede una soluzione completa che consente agli artisti di creare file di dipinti digitali partendo da zero.

스케치, 페인팅을 위한 완벽한 도구이며, 생각에서부터 디지털 페인팅 파일을 만들어 낼 수 있는 종합적인 도구를 제공합니다.

Het is perfect voor schetsen en schilderen en zet een end–to–end oplossing voor het maken van digitale bestanden voor schilderingen vanuit het niets door meesters.

Passar perfekt for både teikning og måling, og dekkjer alle ledd i prosessen med å laga digitale måleri frå grunnen av.

Nadaje się perfekcyjnie do szkicowania i malowania i dostarcza zupełnego rozwiązania dla tworzenia plików malowideł cyfrowych od zalążka.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar ficheiros de pintura digital do zero por mestres.

É perfeito para desenhos e pinturas, oferecendo uma solução final para criar arquivos de desenho digital feitos a partir do zero por mestres.

Она превосходно подходит для набросков и рисования, предоставляя мастерам самодостаточный инструмент для создания цифровой живописи с нуля.

Je ideálna na skicovanie a maľovanie a poskytuje end-to-end riešenie na vytváranie súborov digitálneho maľovania od základu od profesionálov.

Den är perfekt för att skissa och måla, samt erbjuder en helomfattande lösning för att skapa digitala målningsfiler från grunden av mästare.

Eskiz ve boyama için mükemmeldir ve ustaların sıfırdan dijital boyama dosyaları oluşturmak için uçtan-uca bir çözüm sunar.

Цей комплекс чудово пасує для створення ескізів та художніх зображень і є самодостатнім набором для створення файлів цифрових полотен «з нуля» для справжніх художників.

xxIt is perfect for sketching and painting, and presents an end–to–end solution for creating digital painting files from scratch by masters.xx

它专门为数字绘画设计,为美术工作者提供了一个从起草、上色到完成作品等整个创作流程的完整解决方案。

它是素描和繪畫的完美選擇,並提供了一個從零開始建立數位繪畫檔的端到端解決方案。

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita je odličan izbor za kreiranje konceptualne umjetnosti, stripove, teksture za obradu i mat slike. Krita podržava mnoge prostore boja kao RGB i CMIK na 8 i 16 bitnim cjelobrojnim kanalimaa, kao i 16 i 32 bita floating point kanalima.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

El Krita és una gran elecció per crear art conceptual, còmics, textures per renderitzar i pintures «matte». El Krita permet molts espais de color com el RGB i el CMYK a 8 i 16 bits de canals sencers, així com 16 i 32 bits de canals de coma flotant.

Το Krita είναι μια εξαιρετική επιλογή για τη δημιουργία αφηρημένης τέχνης, ιστοριών με εικόνες, υφής για ζωγραφική αποτύπωσης και διάχυσης φωτός. Το Krita υποστηρίζει πολλούς χρωματικούς χώρους όπως τα RGB και CMYK σε 8 και 16 bit κανάλια ακεραίων καθώς επίσης και σε 16 και 32 bit κανάλια κινητής υποδιαστολής,

Krita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colourspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.

Krita es una gran elección para crear arte conceptual, cómics, texturas para renderizar y «matte paintings». Krita permite el uso de muchos espacios de color, como, por ejemplo, RGB y CMYK, tanto en canales de enteros de 8 y 16 bits, así como en canales de coma flotante de 16 y 32 bits.

Krita on üks paremaid valikuid kontseptuaalkunsti, koomiksite, tekstuuride ja digitaalmaalide loomiseks. Krita toetab paljusid värviruume, näiteks RGB ja CMYK 8 ja 16 täisarvulise bitiga kanali kohta, samuti 16 ja 32 ujukomabitiga kanali kohta.

Krita aukera bikaina da kontzeptuzko artea, komikiak, errendatzeko ehundurak eta «matte» margolanak sortzeko. Kritak kolore-espazio ugari onartzen ditu hala nola GBU eta CMYK, 8 eta 16 biteko osoko kanaletan, baita 16 eta 32 biteko koma-higikorreko kanaletan.

Krita on hyvä valinta konseptikuvituksen, sarjakuvien, pintakuvioiden ja maalausten luomiseen. Krita tukee useita väriavaruuksia kuten RGB:tä ja CMYK:ta 8 ja 16 bitin kokonaisluku- samoin kuin 16 ja 32 bitin liukulukukanavin.

Krita est un très bon choix pour créer des concepts arts, des bandes-dessinées, des textures de rendu et des peintures. Krita prend en charge plusieurs espaces de couleurs comme RVB et CMJN avec les canaux de 8 et 16 bits entiers ainsi que les canaux de 16 et 32 bits flottants.

Krita é unha gran opción para crear arte conceptual, texturas para renderización e pinturas mate. Krita permite usar moitos espazos de cores como RGB e CMYK con canles de 8 e 16 bits, así como canles de coma flotante de 16 e 32 bits.

Krita es un grande selection pro crear arte de concepto, comics, texturas pro rendering e picturas opac. Krita supporta multe spatios de colores como RGB e CMYK con canales de integer a 8 e 16 bits, como anque canales floating point a 16 e 32 bits.

Krita adalah pilihan yang cocok untuk menciptakan konsep seni, komik, tekstur untuk rendering dan lukisan matte. Krita mendukung banyak ruang warna seperti RGB dan CMYK pada channel integer 8 dan 16 bit, serta channel floating point 16 dan 32 bit.

Krita rappresenta una scelta ottimale per la creazione di arte concettuale, fumetti e texture per il rendering e il matte painting. Krita supporta molti spazi colori come RGB e CMYK a 8 e 16 bit per canali interi e 16 e 32 bit per canali a virgola mobile.

コンセプトアート、コミック、3DCG 用テクスチャ、マットペイントを制作する方にとって、Krita は最適な選択です。Krita は、8/16 ビット整数/チャンネル、および 16/32 ビット浮動小数点/チャンネルの RGB や CMYK をはじめ、さまざまな色空間をサポートしています。

Krita는 컨셉 아트, 만화, 렌더링용 텍스처, 풍경화 등을 그릴 때 사용할 수 있는 완벽한 도구입니다. RGB, CMYK와 같은 여러 색 공간 및 8비트/16비트 정수 채널, 16비트/32비트 부동 소수점 채널을 지원합니다.

Krita is een goede keuze voor het maken van kunstconcepten, strips, textuur voor weergeven en matte schilderijen. Krita ondersteunt vele kleurruimten zoals RGB en CMYK in 8 en 16 bits kanalen met gehele getallen, evenals 16 en 32 bits kanalen met drijvende komma.

Krita er det ideelle valet dersom du vil laga konseptskisser, teikneseriar, teksturar for 3D-rendering eller «matte paintings». Programmet støttar fleire fargerom, både RGB- og CMYK-baserte, med 8- og 16-bits heiltals- eller flyttalskanalar.

Krita jest świetnym wyborem przy tworzeniu koncepcyjnej sztuki, komiksów, tekstur do wyświetlania i kaszet. Krita obsługuje wiele przestrzeni barw takich jak RGB oraz CMYK dla kanałów 8 oraz 16 bitowych wyrażonych w l. całkowitych, a także 16 oraz 32 bitowych wyrażonych w l. zmiennoprzecinkowych.

O Krita é uma óptima escolha para criar arte conceptual, banda desenhada, texturas para desenho e pinturas. O Krita suporta diversos espaços de cores como o RGB e o CMYK com canais de cores inteiros a 8 e 16 bits, assim como canais de vírgula flutuante a 16 e a 32 bits.

O Krita é uma ótima escolha para criação de arte conceitual, histórias em quadrinhos, texturas para desenhos e pinturas. O Krita tem suporte a diversos espaços de cores como RGB e CMYK com canais de cores inteiros de 8 e 16 bits, assim como canais de ponto flutuante de 16 e 32 bits.

Krita — отличный выбор для создания концепт-артов, комиксов, текстур для рендеринга и рисования. Она поддерживает множество цветовых пространств включая RGB и CMYK с 8 и 16 целыми битами на канал, а также 16 и 32 битами с плавающей запятой на канал.

Krita je výborná voľba pre vytváranie konceptového umenia, textúr na renderovanie a matné kresby. Krita podporuje mnoho farebných priestorov ako RGB a CMYK na 8 a 16 bitových celočíselných kanáloch ako aj 16 a 32 bitových reálnych kanáloch.

Krita är ett utmärkt val för att skapa concept art, serier, strukturer för återgivning och bakgrundsmålningar. Krita stöder många färgrymder som RGB och CMYK med 8- och 16-bitars heltal, samt 16- och 32-bitars flyttal.

Krita, konsept sanat, çizgi roman, kaplama ve mat resimler için dokular oluşturmak için mükemmel bir seçimdir. Krita, 8 ve 16 bit tamsayı kanallarında RGB ve CMYK gibi birçok renk alanını ve 16 ve 32 bit kayan nokta kanallarını desteklemektedir.

Krita — чудовий інструмент для створення концептуального живопису, коміксів, текстур для моделей та декорацій. У Krita передбачено підтримку багатьох просторів кольорів, зокрема RGB та CMYK з 8-бітовими та 16-бітовими цілими значеннями, а також 16-бітовими та 32-бітовими значеннями з рухомою крапкою для каналів кольорів.

xxKrita is a great choice for creating concept art, comics, textures for rendering and matte paintings. Krita supports many colorspaces like RGB and CMYK at 8 and 16 bits integer channels, as well as 16 and 32 bits floating point channels.xx

Krita 是绘制概念美术、漫画、纹理和电影布景的理想选择。Krita 支持多种色彩空间,如 8 位和 16 位整数及 16 位和 32 位浮点的 RGB 和 CMYK 颜色模型。

Krita 是創造概念藝術、漫畫、彩現紋理和場景繪畫的絕佳選擇。Krita 在 8 位元和 16 位元整數色版,以及 16 位元和 32 位元浮點色板中支援 RGB 和 CMYK 等多種色彩空間。

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Zabavite se kreirajući napredne pogone četki, filtere i mnoge praktične osobine koje čine Krita vrlo produktivnim.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Gaudiu pintant amb els motors avançats de pinzells, filtres impressionants i moltes característiques útils que fan el Krita molt productiu.

Διασκεδάστε ζωγραφίζοντας με τις προηγμένες μηχανές πινέλων, με εκπληκτικά φίλτρα και πολλά εύκολης χρήσης χαρακτηριστικά που παρέχουν στο Krita εξαιρετικά αυξημένη παραγωγικότητα.

Have fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.

Diviértase pintando con los avanzados motores de pinceles, los espectaculares filtros y muchas funcionalidades prácticas que hacen que Krita sea enormemente productivo.

Joonistamise muudavad tunduvalt lõbusamaks võimsad pintslimootorid, imetabased filtrid ja veel paljud käepärased võimalused, mis muudavad Krita kasutaja tohutult tootlikuks.

Marrazten ondo pasa ezazu, isipu motor aurreratuekin, iragazki txundigarriekin eta eginbide praktiko ugariekin, zeintzuek Krita ikaragarri emankorra egiten duten.

Pidä hauskaa maalatessasi edistyneillä sivellinmoottoreilla, hämmästyttävillä suotimilla ja monilla muilla kätevillä ominaisuuksilla, jotka tekevät Kritasta tavattoman tehokkaan.

Amusez-vous à peindre avec les outils de brosse avancés, les filtres incroyables et les nombreuses fonctionnalités pratiques qui rendent Krita extrêmement productif.

Goza debuxando con motores de pincel avanzados, filtros fantásticos e moitas outras funcionalidades útiles que fan de Krita un programa extremadamente produtivo.

Amusa te a pinger con le motores de pincel avantiate, filtros stupende e multe characteristicas amical que face Krita enormemente productive.

Bersenang-senanglah melukis dengan mesin kuas canggih, filter luar biasa dan banyak fitur berguna yang membuat Krita sangat produktif.

Divertiti a dipingere con gli avanzati sistemi di pennelli, i sorprendenti filtri e molte altre utili caratteristiche che fanno di Krita un software enormemente produttivo.

Krita のソフトウェアとしての生産性を高めている先進的なブラシエンジンや素晴らしいフィルタのほか、便利な機能の数々をお楽しみください。

Krita의 고급 브러시 엔진, 다양한 필터, 여러 도움이 되는 기능으로 생산성을 즐겁게 향상시킬 수 있습니다.

Veel plezier met schilderen met the geavanceerde penseel-engines, filters vol verbazing en vele handige mogelijkheden die maken dat Krita enorm productief is.

Leik deg med avanserte penselmotorar og fantastiske biletfilter – og mange andre nyttige funksjonar som gjer deg produktiv med Krita.

Baw się przy malowaniu przy użyciu zaawansowanych silników pędzli, zadziwiających filtrów i wielu innych przydatnych cech, które czynią z Krity bardzo produktywną.

Divirta-se a pintar com os motores de pincéis avançados, os filtros espantosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Divirta-se pintando com os mecanismos de pincéis avançados, filtros maravilhosos e muitas outras funcionalidades úteis que tornam o Krita altamente produtivo.

Получайте удовольствие от использования особых кистевых движков, впечатляющих фильтров и множества других функций, делающих Krita сверхпродуктивной.

Užívajte si maľovanie s pokročilými kresliacimi enginmi, úžasnými filtrami a mnohými užitočnými funkciami, ktoré robia Kritu veľmi produktívnu.

Ha det så kul vid målning med de avancerade penselfunktionerna, fantastiska filtren och många praktiska funktioner som gör Krita så enormt produktiv.

Gelişmiş fırça motorları, şaşırtıcı filtreler ve Krita'yı son derece üretken yapan bir çok kullanışlı özellikli boya ile iyi eğlenceler.

Отримуйте задоволення від малювання за допомогою пензлів з найширшими можливостями, чудових фільтрів та багатьох зручних можливостей, які роблять Krita надзвичайно продуктивним засобом малювання.

xxHave fun painting with the advanced brush engines, amazing filters and many handy features that make Krita enormously productive.xx

Krita 具有功能强大的笔刷引擎、种类繁多的滤镜以及便于操作的交互设计,可让你尽情、高效地挥洒无限创意。

使用先進的筆刷引擎、驚人的濾鏡和許多方便的功能來開心地繪畫,讓 Krita 擁有巨大的生產力。

https://www.krita.org/ https://docs.krita.org/KritaFAQ.html https://krita.org/support-us/donations/ https://docs.krita.org/ https://docs.krita.org/en/untranslatable_pages/reporting_bugs.html Krita is a full-featured digital painting studio. Krita és un estudi de pintura digital ple de funcionalitats. Krita és un estudi de pintura digital ple de funcionalitats. Krita ist ein digitales Zeichenstudio mit umfangreichen Funktionen. Krita is a full-featured digital painting studio. Krita es un completo estudio de dibujo digital. + Krita on rohkete võimalustega digitaalkunstistuudio. Krita é un estudio completo de debuxo dixital. Krita adalah studio pelukisan digital dengan fitur yang lengkap. Krita è uno studio d'arte digitale completo. Krita는 다기능 디지털 예술 스튜디오입니다. Krita is een digitale schilderstudio vol mogelijkheden. Krita er ei funksjonsrik digital teiknestove. Krita jest pełnowymiarowym, cyfrowym studiem artystycznym. O Krita é um estúdio de arte digital completo. O Krita é um estúdio de pintura digital completo. Krita je plnohodnotné digitálne maliarske štúdio. Krita är en fullfjädrad digital konststudio. Krita — повноцінний комплекс для цифрового малювання. xxKrita is a full-featured digital painting studio.xx Krita 是一款功能齐全的数字绘画工作室软件。 Krita 是全功能的數位繪圖工作室。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_001.png The startup window now also gives you the latest news about Krita. La finestra d'inici ara proporciona les darreres notícies quant al Krita. La finestra d'inici ara proporciona les darreres noticies quant al Krita. The startup window now also gives you the latest news about Krita. La ventana de bienvenida también le proporciona ahora las últimas noticias sobre Krita. + Käivitusaken jagab nüüd ka Krita värskemaid uudiseid. Agora a xanela de inicio tamén fornece as últimas novas sobre Krita. Window pemulaian kini juga memberikan kamu kabar terkini tentang Krita. La finestra di avvio ora fornisce anche le ultime novità su Krita. 시작 창에서 Krita의 최신 소식을 볼 수 있습니다. Het opstartvenster geeft u nu ook you het laatste nieuws over Krita. Oppstartsvindauget viser no siste nytt om Krita. Okno początkowe teraz wyświetla wieści o Kricie. A janela inicial agora também lhe dá as últimas notícias sobre o Krita. A janela de inicialização agora também mostra as últimas notícias sobre o Krita. V úvodnom okne sa tiež nachádzajú najnovšie správy o Krita. Startfönstret ger nu också senaste nytt om Krita. У початковому вікні програми ви можете бачити найсвіжіші новини щодо Krita. xxThe startup window now also gives you the latest news about Krita.xx 启动画面现在可以为你呈现与 Krita 有关的最新资讯。 開始視窗也提供給您關於 Krita 的最新消息。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_002.png There are over ten immensely powerful brush engines. Hi ha uns deu motors de pinzell immensament potents. Hi ha uns deu motors de pinzell immensament potents. There are over ten immensely powerful brush engines. Existen unos diez inmensamente potentes motores de pinceles. + Üle kümne ääretult võimeka pintslimootori. Hai máis de dez motores de pinceis inmensamente potentes. Ada lebih dari sepuluh mesin kuas yang sangat manjur. Ci sono oltre dieci motori di pennelli incredibilmente potenti. 10가지 종류의 강력한 브러시 엔진을 사용할 수 있습니다. Er zijn meer dan tien immens krachtige penseelengines. Det finst meir enn ti enormt kraftige penselmotorar. Istnieje ponad dziesięć zaawansowanych silników pędzli. Existem mais de dez motores de pincéis extremamente poderosos. Mais de dez engines de pincéis incrivelmente poderosos disponíveis. Existuje viac ako desať nesmierne výkonných štetcových enginov. Det finns mer än tio enormt kraftfulla penselgränssnitt. У програмі передбачено понад десяток надзвичайно потужних рушіїв пензлів. xxThere are over ten immensely powerful brush engines.xx 它具备超过十种相当强大的笔刷引擎。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_003.png Create and use gamut masks to give your images a coherent feel. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Creeu i useu màscares de gamma per donar a les imatges una aparença coherent. Create and use gamut masks to give your images a coherent feel. Cree y use gamas para proporcionar a sus imágenes un aspecto coherente. + Värviulatuse maskide loomine ja kasutamine piltidele kooskõlalise välimuse andmiseks. Crea e usa máscaras de gama para dar ás túas imaxes un aspecto coherente. Ciptakan dan gunakan masker gamut untuk memberikan gambarmu sebuah suasana koheren. Crea e utilizza maschere gamut per dare alle tue immagini un aspetto coerente. 색역 마스크를 만들고 사용할 수 있습니다. Maak en gebruik gamut-maskers om uw afbeeldingen een coherent gevoel te geven. Bruk fargeområde-masker for å gje bileta eit heilsleg uttrykk. Stwórz i używaj masek gamut, aby nadać swoim obrazom spójny wygląd. Crie e use máscaras de gamute para dar às suas imagens uma aparência coerente. Crie e use máscaras de gama para dar um senso de coerência às suas imagens. Vytvorte a používajte gamutové masky, aby vašim obrázkom poskytli ucelený pocit. Att skapa och använda färgomfångsmasker ger bilder en sammanhängande känsla. Створюйте маски палітри і користуйтеся ними для надання вашим малюнкам однорідного вигляду. xxCreate and use gamut masks to give your images a coherent feel.xx 创建并使用色域蒙版可以为你的图像带来更加一致的观感。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_004.png Into animation? Krita provides everything you need for traditional, hand-drawn animation. Esteu amb animacions? El Krita proporciona tot el que cal per a l'animació a mà tradicional. Esteu amb animacions? El Krita proporciona tol el que cal per a l'animació a mà tradicional. Into animation? Krita provides everything you need for traditional, hand-drawn animation. ¿Trabaja con animación? Krita proporciona todo lo necesario para la animación manual tradicional. + Sind huvitab animatsioon? Krita pakub kõike, mida läheb tarvis traditsioonilise käsitsi loodud animatsiooni jaoks. Gusta das animacións? Krita fornece todo o necesario para animacións tradicionais debuxadas a man. Soal animasi? krita menyediakan apa pun yang kamu perlukan untuk animasi gambar-tangan, tradisional. Per le animazioni? Krita fornisce tutto ciò che ti server per l'animazione tradizionale, disegnata a mano. 애니메이션을 만들 계획이 있으신가요? Krita를 통해서 수작업 애니메이션을 작업할 수 있습니다. Naar animatie? Krita biedt alles wat u nodig hebt voor traditionele, met de hand getekende animatie. Interessert i animasjon? Krita har alt du treng for tradisjonelle, handteikna animasjonar. Zajmujesz się animacjami? Krita zapewnia wszystko czego potrzebujesz do tworzenia tradycyjnych, ręcznie rysowanych animacji. Gosta de animação? O Krita oferece tudo o que precisa para o desenho animado tradicional e desenhado à mão. Curte animação? O Krita fornece tudo necessário para você poder trabalhar com animação tradicional ou feita à mão. Ste do animácie? Krita poskytuje všetko, čo potrebujete pre tradičné ručne kreslené animácie. Gillar du animering? Krita tillhandahåller allt som behövs för traditionella, handritade animeringar. Працюєте із анімацією? У Krita ви знайдете усе, що потрібно для створення традиційної, намальованої вручну анімації. xxInto animation? Krita provides everything you need for traditional, hand-drawn animation.xx 喜欢制作动画吗?Krita 提供了制作传统手绘动画的全套工具。 想做動畫?Krita 提供您在傳統、手繪動畫所需的任何東西。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_005.png If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. Si sou nou a la pintura digital, o voleu conèixer més quant a les possibilitats del Krita, hi ha un ampli manual actualitzat. If you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual. Si está empezando con el dibujo digital o si quiere saber más sobre la posibilidades de Krita, dispone de un extenso y actualizado manual. + Kui oled digitaalkunstis alles uustulnuk või tunned huvi Krita võimaluste vastu, on meil välja pakkuda mahukas ajakohane käsiraamat. Se está a empezar co debuxo dixital, ou quere saber máis sobre as posibilidades de Krita, existe un manual exhaustivo e actualizado. Jika kamu baru dalam pelukisan digital, atau ingin mengetahui selebihnya tentang Krita, di situ ada manual yang update dan luas. Se sei nuovo del disegno digitale, o vuoi saperne di più sulle possibilità di Krita, è disponibile un manuale completo e aggiornato. 디지털 페인팅을 처음 시작하시거나, Krita의 기능을 더 알아 보려면 사용 설명서를 참조하십시오. Als u nieuw bent in digitaal schilderen of u wilt meer weten over de mogelijkheden van Krita, dan is er een uitgebreide, bijgewerkte handleiding. Viss du er nybegynnar innan digital teikning, eller ønskjer å veta meir om kva som er mogleg med Krita, finst det ei omfattande og oppdatert brukarhandbok. Jeśli cyfrowe malowanie to dla ciebie nowość, lub jeśli chcesz dowiedzieć się więcej o możliwościach Krity, to dostępny jest wyczerpująca i aktualna instrukcja obsługi. Se é novo na pintura digital, ou deseja saber mais sobre as possibilidades do Krita, existe um manual extenso e actualizado. Se você for iniciante em pintura digital ou gostaria de saber mais sobre as possibilidades que o Krita oferece, há um extenso e atualizado manual para isso. Ak ste v oblasti digitálnej maľby nováčikom alebo sa chcete dozvedieť viac o možnostiach programu Krita, existuje o tom rozsiahla a aktuálna príručka. Om digital målning är nytt för dig, eller om du vill veta mer om Kritas möjligheter, finns en omfattande, aktuell handbok. Якщо ви не маєте достатнього досвіду у цифровому малюванні або хочете дізнатися більше про можливості Krita, скористайтеся нашим докладним і актуальним підручником. xxIf you're new to digital painting, or want to know more about Krita's possibilities, there's an extensive, up-to-date manual.xx 不管你是数字绘画的新手,还是想发现 Krita 更多的用法,你都可以在我们详尽并持续更新的使用手册中找到答案。 https://cdn.kde.org/screenshots/krita/2018-03-17_screenshot_006.png Graphics 2DGraphics RasterGraphics KDE krita org.kde.krita.desktop https://www.microsoft.com/store/apps/9n6x57zgrw96
diff --git a/libs/global/KisUsageLogger.cpp b/libs/global/KisUsageLogger.cpp index fdce7c3901..2739d7e2e6 100644 --- a/libs/global/KisUsageLogger.cpp +++ b/libs/global/KisUsageLogger.cpp @@ -1,206 +1,236 @@ /* * Copyright (c) 2019 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 "KisUsageLogger.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include - +#include Q_GLOBAL_STATIC(KisUsageLogger, s_instance) const QString KisUsageLogger::s_sectionHeader("================================================================================\n"); struct KisUsageLogger::Private { bool active {false}; QFile logFile; + QFile sysInfoFile; }; KisUsageLogger::KisUsageLogger() : d(new Private) { d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); + d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log"); rotateLog(); + d->logFile.open(QFile::Append | QFile::Text); + d->sysInfoFile.open(QFile::WriteOnly | QFile::Text); } KisUsageLogger::~KisUsageLogger() { if (d->active) { close(); } } void KisUsageLogger::initialize() { s_instance->d->active = true; + + QString systemInfo; + + // NOTE: This is intentionally not translated! + + // Krita version info + systemInfo.append("Krita\n"); + systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); + systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", ")); + systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false"); + systemInfo.append("\n\n"); + + systemInfo.append("Qt\n"); + systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); + systemInfo.append("\n Version (loaded): ").append(qVersion()); + systemInfo.append("\n\n"); + + // OS information + systemInfo.append("OS Information\n"); + systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); + systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); + systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); + systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); + systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); + systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); + systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); + systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); + systemInfo.append("\n\n"); + + s_instance->d->sysInfoFile.write(systemInfo.toUtf8()); } void KisUsageLogger::close() { log("CLOSING SESSION"); s_instance->d->active = false; s_instance->d->logFile.flush(); s_instance->d->logFile.close(); + s_instance->d->sysInfoFile.flush(); + s_instance->d->sysInfoFile.close(); } void KisUsageLogger::log(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8()); s_instance->d->logFile.write(": "); write(message); } void KisUsageLogger::write(const QString &message) { if (!s_instance->d->active) return; if (!s_instance->d->logFile.isOpen()) return; s_instance->d->logFile.write(message.toUtf8()); s_instance->d->logFile.write("\n"); s_instance->d->logFile.flush(); } -void KisUsageLogger::writeSectionHeader() +void KisUsageLogger::writeSysInfo(const QString &message) { - s_instance->d->logFile.write(s_sectionHeader.toUtf8()); + if (!s_instance->d->active) return; + if (!s_instance->d->sysInfoFile.isOpen()) return; + + s_instance->d->sysInfoFile.write(message.toUtf8()); + s_instance->d->sysInfoFile.write("\n"); + + s_instance->d->sysInfoFile.flush(); + } + void KisUsageLogger::writeHeader() { - Q_ASSERT(s_instance->d->logFile.isOpen()); + Q_ASSERT(s_instance->d->sysInfoFile.isOpen()); + s_instance->d->logFile.write(s_sectionHeader.toUtf8()); QString sessionHeader = QString("SESSION: %1. Executing %2\n\n") .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)) .arg(qApp->arguments().join(' ')); - QString disclaimer = i18n("WARNING: This file contains information about your system and the\n" - "images you have been working with.\n" - "\n" - "If you have problems with Krita, the Krita developers might ask\n" - "you to share this file with them. The information in this file is\n" - "not shared automatically with the Krita developers in any way. You\n" - "can disable logging to this file in Krita's Configure Krita Dialog.\n" - "\n" - "Please review the contents of this file before sharing this file with\n" - "anyone.\n\n"); - - QString systemInfo; - - // NOTE: This is intentionally not translated! - - // Krita version info - systemInfo.append("Krita\n"); - systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); - systemInfo.append("\n Languages: ").append(KLocalizedString::languages().join(", ")); - systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false"); - systemInfo.append("\n\n"); - - systemInfo.append("Qt\n"); - systemInfo.append("\n Version (compiled): ").append(QT_VERSION_STR); - systemInfo.append("\n Version (loaded): ").append(qVersion()); - systemInfo.append("\n\n"); - - // OS information - systemInfo.append("OS Information\n"); - systemInfo.append("\n Build ABI: ").append(QSysInfo::buildAbi()); - systemInfo.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); - systemInfo.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); - systemInfo.append("\n Kernel Type: ").append(QSysInfo::kernelType()); - systemInfo.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); - systemInfo.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); - systemInfo.append("\n Product Type: ").append(QSysInfo::productType()); - systemInfo.append("\n Product Version: ").append(QSysInfo::productVersion()); - systemInfo.append("\n\n"); - - writeSectionHeader(); s_instance->d->logFile.write(sessionHeader.toUtf8()); - s_instance->d->logFile.write(disclaimer.toUtf8()); - s_instance->d->logFile.write(systemInfo.toUtf8()); + s_instance->d->logFile.flush(); +} +QString KisUsageLogger::screenInformation() +{ + QList screens = qobject_cast(qApp)->screens(); + + QString info; + info.append("Display Information\n\n"); + info.append("\tNumber of screens: ").append(QString::number(screens.size())); + + for (int i = 0; i < screens.size(); ++i ) { + QScreen *screen = screens[i]; + info.append("\nScreen ").append(i).append(":\n"); + info.append("\n\tName: ").append(screen->name()); + info.append("\n\tDepth: ").append(QString::number(screen->depth())); + info.append("\n\tScale: ").append(QString::number(screen->devicePixelRatio())); + info.append("\n\tResolution in pixels: ").append(QString::number(screen->geometry().width())) + .append("x") + .append(QString::number(screen->geometry().height())); + info.append("\n\tManufacturer: ").append(screen->manufacturer()); + info.append("\n\tModel: ").append(screen->model()); + info.append("\n\tRefresh Rate: ").append(QString::number(screen->refreshRate())); + } + info.append("\n"); + qDebug() << info; + return info; } void KisUsageLogger::rotateLog() { if (d->logFile.exists()) { { // Check for CLOSING SESSION d->logFile.open(QFile::ReadOnly); QString log = QString::fromUtf8(d->logFile.readAll()); - if (!log.split("\n").last().contains("CLOSING SESSION")) { + if (!log.split(s_sectionHeader).last().contains("CLOSING SESSION")) { log.append("\nKRITA DID NOT CLOSE CORRECTLY\n"); QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log"); if (QFileInfo(crashLog).exists()) { QFile f(crashLog); f.open(QFile::ReadOnly); QString crashes = QString::fromUtf8(f.readAll()); f.close(); QStringList crashlist = crashes.split("-------------------"); log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size())); if (crashes.size() > 0) { log.append(crashlist.last()); } } d->logFile.close(); d->logFile.open(QFile::WriteOnly); d->logFile.write(log.toUtf8()); } d->logFile.flush(); d->logFile.close(); } { // Rotate d->logFile.open(QFile::ReadOnly); QString log = QString::fromUtf8(d->logFile.readAll()); int sectionCount = log.count(s_sectionHeader); int nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); while(sectionCount >= s_maxLogs) { log = log.remove(0, log.indexOf(s_sectionHeader, nextSectionIndex)); nextSectionIndex = log.indexOf(s_sectionHeader, s_sectionHeader.length()); sectionCount = log.count(s_sectionHeader); } d->logFile.close(); d->logFile.open(QFile::WriteOnly); d->logFile.write(log.toUtf8()); d->logFile.flush(); d->logFile.close(); } } } diff --git a/libs/global/KisUsageLogger.h b/libs/global/KisUsageLogger.h index 34dd84755f..7241be3f6a 100644 --- a/libs/global/KisUsageLogger.h +++ b/libs/global/KisUsageLogger.h @@ -1,64 +1,67 @@ /* * Copyright (c) 2019 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 KISUSAGELOGGER_H #define KISUSAGELOGGER_H #include #include #include "kritaglobal_export.h" /** * @brief The KisUsageLogger class logs messages to a logfile */ class KRITAGLOBAL_EXPORT KisUsageLogger { public: KisUsageLogger(); ~KisUsageLogger(); static void initialize(); static void close(); /// Logs with date/time static void log(const QString &message); /// Writes without date/time static void write(const QString &message); - static void writeSectionHeader(); + static void writeSysInfo(const QString &message); static void writeHeader(); + /// Returns information about all available screens + static QString screenInformation(); + private: void rotateLog(); Q_DISABLE_COPY(KisUsageLogger) struct Private; const QScopedPointer d; static const QString s_sectionHeader; static const int s_maxLogs {10}; }; #endif // KISUSAGELOGGER_H diff --git a/libs/image/kis_async_merger.cpp b/libs/image/kis_async_merger.cpp index c0790e8986..b20080f160 100644 --- a/libs/image/kis_async_merger.cpp +++ b/libs/image/kis_async_merger.cpp @@ -1,380 +1,384 @@ /* Copyright (c) Dmitry Kazakov , 2009 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_async_merger.h" #include #include #include #include #include "kis_node_visitor.h" #include "kis_painter.h" #include "kis_layer.h" #include "kis_group_layer.h" #include "kis_adjustment_layer.h" #include "generator/kis_generator_layer.h" #include "kis_external_layer_iface.h" #include "kis_paint_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "kis_selection.h" #include "kis_clone_layer.h" #include "kis_processing_information.h" #include "kis_busy_progress_indicator.h" #include "kis_merge_walker.h" #include "kis_refresh_subtree_walker.h" #include "kis_abstract_projection_plane.h" //#define DEBUG_MERGER #ifdef DEBUG_MERGER #define DEBUG_NODE_ACTION(message, type, leaf, rect) \ qDebug() << message << type << ":" << leaf->node()->name() << rect #else #define DEBUG_NODE_ACTION(message, type, leaf, rect) #endif class KisUpdateOriginalVisitor : public KisNodeVisitor { public: KisUpdateOriginalVisitor(const QRect &updateRect, KisPaintDeviceSP projection, const QRect &cropRect) : m_updateRect(updateRect), m_cropRect(cropRect), m_projection(projection) { } ~KisUpdateOriginalVisitor() override { } public: using KisNodeVisitor::visit; bool visit(KisAdjustmentLayer* layer) override { if (!layer->visible()) return true; if (!m_projection) { warnImage << "ObligeChild mechanism has been activated for " "an adjustment layer! Do nothing..."; layer->original()->clear(); return true; } const QRect originalUpdateRect = layer->projectionPlane()->needRectForOriginal(m_updateRect); KisPaintDeviceSP originalDevice = layer->original(); originalDevice->clear(originalUpdateRect); const QRect applyRect = originalUpdateRect & m_projection->extent(); // If the intersection of the updaterect and the projection extent is // null, we are finish here. if(applyRect.isNull()) return true; KisFilterConfigurationSP filterConfig = layer->filter(); if (!filterConfig) { /** * When an adjustment layer is just created, it may have no * filter inside. Then the layer has work as a pass-through * node. Just copy the merged data to the layer's original. */ KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect); return true; } KisSelectionSP selection = layer->fetchComposedInternalSelection(applyRect); const QRect filterRect = selection ? applyRect & selection->selectedRect() : applyRect; KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name()); if (!filter) return false; KisPaintDeviceSP dstDevice = originalDevice; if (selection) { dstDevice = new KisPaintDevice(originalDevice->colorSpace()); } if (!filterRect.isEmpty()) { KIS_ASSERT_RECOVER_NOOP(layer->busyProgressIndicator()); layer->busyProgressIndicator()->update(); // We do not create a transaction here, as srcDevice != dstDevice filter->process(m_projection, dstDevice, 0, filterRect, filterConfig.data(), 0); } if (selection) { KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect); KisPainter::copyAreaOptimized(filterRect.topLeft(), dstDevice, originalDevice, filterRect, selection); } return true; } bool visit(KisExternalLayer*) override { return true; } bool visit(KisGeneratorLayer*) override { return true; } bool visit(KisPaintLayer*) override { return true; } bool visit(KisGroupLayer*) override { return true; } bool visit(KisCloneLayer *layer) override { QRect emptyRect; KisRefreshSubtreeWalker walker(emptyRect); KisAsyncMerger merger; KisLayerSP srcLayer = layer->copyFrom(); QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y()); QRegion prepareRegion(srcRect); prepareRegion -= m_cropRect; /** * If a clone has complicated masks, we should prepare additional * source area to ensure the rect is prepared. */ QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect); if (!needRectOnSource.isEmpty()) { prepareRegion += needRectOnSource; } + if (srcLayer.isNull()) { + return true; + } + Q_FOREACH (const QRect &rect, prepareRegion.rects()) { walker.collectRects(srcLayer, rect); merger.startMerge(walker, false); } return true; } bool visit(KisNode*) override { return true; } bool visit(KisFilterMask*) override { return true; } bool visit(KisTransformMask*) override { return true; } bool visit(KisTransparencyMask*) override { return true; } bool visit(KisSelectionMask*) override { return true; } bool visit(KisColorizeMask*) override { return true; } private: QRect m_updateRect; QRect m_cropRect; KisPaintDeviceSP m_projection; }; /*********************************************************************/ /* KisAsyncMerger */ /*********************************************************************/ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) { KisMergeWalker::LeafStack &leafStack = walker.leafStack(); const bool useTempProjections = walker.needRectVaries(); while(!leafStack.isEmpty()) { KisMergeWalker::JobItem item = leafStack.pop(); KisProjectionLeafSP currentLeaf = item.m_leaf; /** * In some unidentified cases teh nodes might be removed * while the updates are still running. We have no proof * of it yet, so just add a safety assert here. */ KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf); KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->node()); // All the masks should be filtered by the walkers KIS_SAFE_ASSERT_RECOVER_RETURN(currentLeaf->isLayer()); QRect applyRect = item.m_applyRect; if (currentLeaf->isRoot()) { currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); continue; } if(item.m_position & KisMergeWalker::N_EXTRA) { // The type of layers that will not go to projection. DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentLeaf, applyRect); KisUpdateOriginalVisitor originalVisitor(applyRect, m_currentProjection, walker.cropRect()); currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node()); continue; } if (!m_currentProjection) { setupProjection(currentLeaf, applyRect, useTempProjections); } KisUpdateOriginalVisitor originalVisitor(applyRect, m_currentProjection, walker.cropRect()); if(item.m_position & KisMergeWalker::N_FILTHY) { DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentLeaf, applyRect); if (currentLeaf->visible() || currentLeaf->hasClones()) { currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); } } else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) { DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentLeaf, applyRect); if(currentLeaf->dependsOnLowerNodes()) { if (currentLeaf->visible() || currentLeaf->hasClones()) { currentLeaf->accept(originalVisitor); currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node()); } } } else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) { DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentLeaf, applyRect); if (currentLeaf->visible() || currentLeaf->hasClones()) { currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode()); } } else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ { DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentLeaf, applyRect); /* nothing to do */ } compositeWithProjection(currentLeaf, applyRect); if(item.m_position & KisMergeWalker::N_TOPMOST) { writeProjection(currentLeaf, useTempProjections, applyRect); resetProjection(); } // FIXME: remove it from the inner loop and/or change to a warning! Q_ASSERT(currentLeaf->projection()->defaultBounds()->currentLevelOfDetail() == walker.levelOfDetail()); } if(notifyClones) { doNotifyClones(walker); } if(m_currentProjection) { warnImage << "BUG: The walker hasn't reached the root layer!"; warnImage << " Start node:" << walker.startNode() << "Requested rect:" << walker.requestedRect(); warnImage << " An inconsistency in the walkers occurred!"; warnImage << " Please report a bug describing how you got this message."; // reset projection to avoid artifacts in next merges and allow people to work further resetProjection(); } } void KisAsyncMerger::resetProjection() { m_currentProjection = 0; m_finalProjection = 0; } void KisAsyncMerger::setupProjection(KisProjectionLeafSP currentLeaf, const QRect& rect, bool useTempProjection) { KisPaintDeviceSP parentOriginal = currentLeaf->parent()->original(); if (parentOriginal != currentLeaf->projection()) { if (useTempProjection) { if(!m_cachedPaintDevice) m_cachedPaintDevice = new KisPaintDevice(parentOriginal->colorSpace()); m_currentProjection = m_cachedPaintDevice; m_currentProjection->prepareClone(parentOriginal); m_finalProjection = parentOriginal; } else { parentOriginal->clear(rect); m_finalProjection = m_currentProjection = parentOriginal; } } else { /** * It happened so that our parent uses our own projection as * its original. It means obligeChild mechanism works. * We won't initialise m_currentProjection. This will cause * writeProjection() and compositeWithProjection() do nothing * when called. */ /* NOP */ } } void KisAsyncMerger::writeProjection(KisProjectionLeafSP topmostLeaf, bool useTempProjection, const QRect &rect) { Q_UNUSED(useTempProjection); Q_UNUSED(topmostLeaf); if (!m_currentProjection) return; if(m_currentProjection != m_finalProjection) { KisPainter::copyAreaOptimized(rect.topLeft(), m_currentProjection, m_finalProjection, rect); } DEBUG_NODE_ACTION("Writing projection", "", topmostLeaf->parent(), rect); } bool KisAsyncMerger::compositeWithProjection(KisProjectionLeafSP leaf, const QRect &rect) { if (!m_currentProjection) return true; if (!leaf->visible()) return true; KisPainter gc(m_currentProjection); leaf->projectionPlane()->apply(&gc, rect); DEBUG_NODE_ACTION("Compositing projection", "", leaf, rect); return true; } void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) { KisBaseRectsWalker::CloneNotificationsVector &vector = walker.cloneNotifications(); KisBaseRectsWalker::CloneNotificationsVector::iterator it; for(it = vector.begin(); it != vector.end(); ++it) { (*it).notify(); } } diff --git a/libs/image/kis_brush_mask_applicator_factories.cpp b/libs/image/kis_brush_mask_applicator_factories.cpp index 860440aead..8fefa3fde1 100644 --- a/libs/image/kis_brush_mask_applicator_factories.cpp +++ b/libs/image/kis_brush_mask_applicator_factories.cpp @@ -1,648 +1,645 @@ /* * Copyright (c) 2012 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_brush_mask_applicator_factories.h" #include "vc_extra_math.h" #include "kis_circle_mask_generator.h" #include "kis_circle_mask_generator_p.h" #include "kis_gauss_circle_mask_generator_p.h" #include "kis_curve_circle_mask_generator_p.h" #include "kis_gauss_rect_mask_generator_p.h" #include "kis_curve_rect_mask_generator_p.h" #include "kis_rect_mask_generator_p.h" #include "kis_brush_mask_applicators.h" #include "kis_brush_mask_applicator_base.h" #define a(_s) #_s #define b(_s) a(_s) template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskScalarApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } template<> template<> MaskApplicatorFactory::ReturnType MaskApplicatorFactory::create(ParamType maskGenerator) { return new KisBrushMaskVectorApplicator(maskGenerator); } #if defined HAVE_VC struct KisCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCircleMaskGenerator::Private *d; }; template<> void KisCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { const bool useSmoothing = d->copyOfAntialiasEdges; float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vTransformedFadeX(d->transformedFadeX); Vc::float_v vTransformedFadeY(d->transformedFadeY); Vc::float_v vOne(Vc::One); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v n = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); Vc::float_m outsideMask = n > vOne; if (!outsideMask.isFull()) { if (useSmoothing) { xr = Vc::abs(xr) + vOne; yr = Vc::abs(yr) + vOne; } Vc::float_v vNormFade = pow2(xr * vTransformedFadeX) + pow2(yr * vTransformedFadeY); Vc::float_m vNormLowMask = vNormFade < vOne; vNormFade.setZero(vNormLowMask); //255 * n * (normeFade - 1) / (normeFade - n) Vc::float_v vFade = n * (vNormFade - vOne) / (vNormFade - n); // Mask in the inner circle of the mask Vc::float_m mask = vNormFade < vOne; vFade.setZero(mask); // Mask out the outer circle of the mask vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } else { // Mask out everything outside the circle vOne.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisGaussCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisGaussCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisGaussCircleMaskGenerator::Private *d; }; template<> void KisGaussCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCenter(d->center); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vDistfactor(d->distfactor); Vc::float_v vAlphafactor(d->alphafactor); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v dist = sqrt(pow2(xr) + pow2(yr * vYCoeff)); // Apply FadeMaker mask and operations Vc::float_m excludeMask = d->fadeMaker.needFade(dist); if (!excludeMask.isFull()) { Vc::float_v valDist = dist * vDistfactor; Vc::float_v fullFade = vAlphafactor * ( VcExtraMath::erf(valDist + vCenter) - VcExtraMath::erf(valDist - vCenter)); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outer circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), precision errors. Vc::float_v vFade = (vValMax - fullFade) / vValMax; // return original dist values before vFade transform vFade(excludeMask) = dist; vFade.store(bufferPointer, Vc::Aligned); } else { dist.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisCurveCircleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCurveCircleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCurveCircleMaskGenerator::Private *d; }; template<> void KisCurveCircleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; qreal* curveDataPointer = d->curveData.data(); Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoef); Vc::float_v vXCoeff(d->xcoef); Vc::float_v vCurveResolution(d->curveResolution); Vc::float_v vCurvedData(Vc::Zero); Vc::float_v vCurvedData1(Vc::Zero); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = x_ * vSina + vCosaY_; Vc::float_v dist = pow2(xr * vXCoeff) + pow2(yr * vYCoeff); // Apply FadeMaker mask and operations Vc::float_m excludeMask = d->fadeMaker.needFade(dist); if (!excludeMask.isFull()) { Vc::float_v valDist = dist * vCurveResolution; // truncate Vc::float_v::IndexType vAlphaValue(valDist); Vc::float_v vFloatAlphaValue = vAlphaValue; Vc::float_v alphaValueF = valDist - vFloatAlphaValue; vCurvedData.gather(curveDataPointer,vAlphaValue); vCurvedData1.gather(curveDataPointer,vAlphaValue + 1); // Vc::float_v vCurvedData1(curveDataPointer,vAlphaValue + 1); // vAlpha Vc::float_v fullFade = ( (vOne - alphaValueF) * vCurvedData + alphaValueF * vCurvedData1); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask outer circle of mask mask = fullFade >= vOne; Vc::float_v vFade = (vOne - fullFade); vFade.setZero(mask); // return original dist values before vFade transform vFade(excludeMask) = dist; vFade.store(bufferPointer, Vc::Aligned); } else { dist.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisGaussRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisGaussRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisGaussRectangleMaskGenerator::Private *d; }; struct KisRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisRectangleMaskGenerator::Private *d; }; template<> void KisRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { const bool useSmoothing = d->copyOfAntialiasEdges; float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vXCoeff(d->xcoeff); Vc::float_v vYCoeff(d->ycoeff); Vc::float_v vTransformedFadeX(d->transformedFadeX); Vc::float_v vTransformedFadeY(d->transformedFadeY); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vTolerance(10000.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = Vc::abs(x_ * vCosa - vSinaY_); Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); Vc::float_v nxr = xr * vXCoeff; Vc::float_v nyr = yr * vYCoeff; Vc::float_m outsideMask = (nxr > vOne) || (nyr > vOne); if (!outsideMask.isFull()) { if (useSmoothing) { xr = Vc::abs(xr) + vOne; yr = Vc::abs(yr) + vOne; } Vc::float_v fxr = xr * vTransformedFadeX; Vc::float_v fyr = yr * vTransformedFadeY; Vc::float_v fxrNorm = nxr * (fxr - vOne) / (fxr - nxr); Vc::float_v fyrNorm = nyr * (fyr - vOne) / (fyr - nyr); Vc::float_v vFade(vZero); - Vc::float_v::IndexType fxrInt(fxr * vTolerance); - Vc::float_v::IndexType fyrInt(fyr * vTolerance); - - Vc::float_m fadeXMask = (fxr > vOne) && ((fxrInt >= fyrInt) || fyr < vOne); - Vc::float_m fadeYMask = (fyr > vOne) && ((fyrInt > fxrInt) || fxr < vOne); - - vFade(fadeXMask) = fxrNorm; - vFade(!fadeXMask && fadeYMask) = fyrNorm; + Vc::float_m vFadeMask = fxrNorm < fyrNorm; + Vc::float_v vMaxVal = vFade; + vMaxVal(fxr > vOne) = fxrNorm; + vMaxVal(vFadeMask && fyr > vOne) = fyrNorm; + vFade = vMaxVal; // Mask out the outer circle of the mask vFade(outsideMask) = vOne; vFade.store(bufferPointer, Vc::Aligned); } else { // Mask out everything outside the circle vOne.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } template<> void KisGaussRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vhalfWidth(d->halfWidth); Vc::float_v vhalfHeight(d->halfHeight); Vc::float_v vXFade(d->xfade); Vc::float_v vYFade(d->yfade); Vc::float_v vAlphafactor(d->alphafactor); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); Vc::float_v vValue; // check if we need to apply fader on values Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr); vValue(excludeMask) = vOne; if (!excludeMask.isFull()) { Vc::float_v fullFade = vValMax - (vAlphafactor * (VcExtraMath::erf((vhalfWidth + xr) * vXFade) + VcExtraMath::erf((vhalfWidth - xr) * vXFade)) * (VcExtraMath::erf((vhalfHeight + yr) * vYFade) + VcExtraMath::erf((vhalfHeight - yr) * vYFade))); // apply antialias fader d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outer circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), precision errors. Vc::float_v vFade = fullFade / vValMax; // return original vValue values before vFade transform vFade(excludeMask) = vValue; vFade.store(bufferPointer, Vc::Aligned); } else { vValue.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } struct KisCurveRectangleMaskGenerator::FastRowProcessor { FastRowProcessor(KisCurveRectangleMaskGenerator *maskGenerator) : d(maskGenerator->d.data()) {} template void process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY); KisCurveRectangleMaskGenerator::Private *d; }; template<> void KisCurveRectangleMaskGenerator:: FastRowProcessor::process(float* buffer, int width, float y, float cosa, float sina, float centerX, float centerY) { float y_ = y - centerY; float sinay_ = sina * y_; float cosay_ = cosa * y_; float* bufferPointer = buffer; qreal* curveDataPointer = d->curveData.data(); Vc::float_v currentIndices = Vc::float_v::IndexesFromZero(); Vc::float_v increment((float)Vc::float_v::size()); Vc::float_v vCenterX(centerX); Vc::float_v vCosa(cosa); Vc::float_v vSina(sina); Vc::float_v vCosaY_(cosay_); Vc::float_v vSinaY_(sinay_); Vc::float_v vYCoeff(d->ycoeff); Vc::float_v vXCoeff(d->xcoeff); Vc::float_v vCurveResolution(d->curveResolution); Vc::float_v vOne(Vc::One); Vc::float_v vZero(Vc::Zero); Vc::float_v vValMax(255.f); for (int i=0; i < width; i+= Vc::float_v::size()){ Vc::float_v x_ = currentIndices - vCenterX; Vc::float_v xr = x_ * vCosa - vSinaY_; Vc::float_v yr = Vc::abs(x_ * vSina + vCosaY_); Vc::float_v vValue; // check if we need to apply fader on values Vc::float_m excludeMask = d->fadeMaker.needFade(xr,yr); vValue(excludeMask) = vOne; if (!excludeMask.isFull()) { // We need to mask the extra area given for aliniation // the next operation should never give values above 1 Vc::float_v preSIndex = Vc::abs(xr) * vXCoeff; Vc::float_v preTIndex = Vc::abs(yr) * vYCoeff; preSIndex(preSIndex > vOne) = vOne; preTIndex(preTIndex > vOne) = vOne; Vc::float_v::IndexType sIndex( round(preSIndex * vCurveResolution)); Vc::float_v::IndexType tIndex( round(preTIndex * vCurveResolution)); Vc::float_v::IndexType sIndexInverted = vCurveResolution - sIndex; Vc::float_v::IndexType tIndexInverted = vCurveResolution - tIndex; Vc::float_v vCurvedDataSIndex(curveDataPointer, sIndex); Vc::float_v vCurvedDataTIndex(curveDataPointer, tIndex); Vc::float_v vCurvedDataSIndexInv(curveDataPointer, sIndexInverted); Vc::float_v vCurvedDataTIndexInv(curveDataPointer, tIndexInverted); Vc::float_v fullFade = vValMax * (vOne - (vCurvedDataSIndex * (vOne - vCurvedDataSIndexInv) * vCurvedDataTIndex * (vOne - vCurvedDataTIndexInv))); // apply antialias fader d->fadeMaker.apply2DFader(fullFade,excludeMask,xr,yr); Vc::float_m mask; // Mask in the inner circle of the mask mask = fullFade < vZero; fullFade.setZero(mask); // Mask the outer circle mask = fullFade > 254.974f; fullFade(mask) = vValMax; // Mask (value - value), precision errors. Vc::float_v vFade = fullFade / vValMax; // return original vValue values before vFade transform vFade(excludeMask) = vValue; vFade.store(bufferPointer, Vc::Aligned); } else { vValue.store(bufferPointer, Vc::Aligned); } currentIndices = currentIndices + increment; bufferPointer += Vc::float_v::size(); } } #endif /* defined HAVE_VC */ diff --git a/libs/image/kis_rect_mask_generator.cpp b/libs/image/kis_rect_mask_generator.cpp index 1d6200a39b..7b98fcad8f 100644 --- a/libs/image/kis_rect_mask_generator.cpp +++ b/libs/image/kis_rect_mask_generator.cpp @@ -1,158 +1,160 @@ /* * Copyright (c) 2004,2007,2008,2009.2010 Cyrille Berger * Copyright (c) 2018 Ivan Santa Maria * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include //MSVC requires that Vc come first #include #include #ifdef HAVE_VC #if defined(__clang__) #pragma GCC diagnostic ignored "-Wundef" #pragma GCC diagnostic ignored "-Wlocal-type-template-args" #endif #if defined _MSC_VER // Lets shut up the "possible loss of data" and "forcing value to bool 'true' or 'false' #pragma warning ( push ) #pragma warning ( disable : 4244 ) #pragma warning ( disable : 4800 ) #endif #include #include #if defined _MSC_VER #pragma warning ( pop ) #endif #endif #include #include "kis_fast_math.h" #include "kis_rect_mask_generator.h" #include "kis_rect_mask_generator_p.h" #include "kis_base_mask_generator.h" #include "kis_brush_mask_applicator_factories.h" #include "kis_brush_mask_applicator_base.h" #include KisRectangleMaskGenerator::KisRectangleMaskGenerator(qreal radius, qreal ratio, qreal fh, qreal fv, int spikes, bool antialiasEdges) : KisMaskGenerator(radius, ratio, fh, fv, spikes, antialiasEdges, RECTANGLE, DefaultId), d(new Private) { setScale(1.0, 1.0); // store the variable locally to allow vector implementation read it easily d->copyOfAntialiasEdges = antialiasEdges; d->applicator.reset(createOptimizedClass >(this)); } KisRectangleMaskGenerator::KisRectangleMaskGenerator(const KisRectangleMaskGenerator &rhs) : KisMaskGenerator(rhs), d(new Private(*rhs.d)) { d->applicator.reset(createOptimizedClass >(this)); } KisMaskGenerator* KisRectangleMaskGenerator::clone() const { return new KisRectangleMaskGenerator(*this); } KisRectangleMaskGenerator::~KisRectangleMaskGenerator() { } void KisRectangleMaskGenerator::setScale(qreal scaleX, qreal scaleY) { KisMaskGenerator::setScale(scaleX, scaleY); d->xcoeff = 2.0 / effectiveSrcWidth(); d->ycoeff = 2.0 / effectiveSrcHeight(); d->xfadecoeff = (horizontalFade() == 0) ? 1 : (2.0 / (horizontalFade() * effectiveSrcWidth())); d->yfadecoeff = (verticalFade() == 0) ? 1 : (2.0 / (verticalFade() * effectiveSrcHeight())); setSoftness(this->softness()); } void KisRectangleMaskGenerator::setSoftness(qreal softness) { KisMaskGenerator::setSoftness(softness); qreal safeSoftnessCoeff = qreal(1.0) / qMax(qreal(0.01), softness); d->transformedFadeX = d->xfadecoeff * safeSoftnessCoeff; d->transformedFadeY = d->yfadecoeff * safeSoftnessCoeff; } bool KisRectangleMaskGenerator::shouldSupersample() const { return effectiveSrcWidth() < 10 || effectiveSrcHeight() < 10; } bool KisRectangleMaskGenerator::shouldVectorize() const { return !shouldSupersample() && spikes() == 2; } KisBrushMaskApplicatorBase* KisRectangleMaskGenerator::applicator() { return d->applicator.data(); } void KisRectangleMaskGenerator::resetMaskApplicator(bool forceScalar) { d->applicator.reset(createOptimizedClass >(this,forceScalar)); } quint8 KisRectangleMaskGenerator::valueAt(qreal x, qreal y) const { if (isEmpty()) return 255; qreal xr = qAbs(x /*- m_xcenter*/); qreal yr = qAbs(y /*- m_ycenter*/); fixRotation(xr, yr); xr = qAbs(xr); yr = qAbs(yr); qreal nxr = xr * d->xcoeff; qreal nyr = yr * d->ycoeff; if (nxr > 1.0 || nyr > 1.0) return 255; if (antialiasEdges()) { xr += 1.0; yr += 1.0; } qreal fxr = xr * d->transformedFadeX; qreal fyr = yr * d->transformedFadeY; - int fxrInt = fxr * 1e4; - int fyrInt = fyr * 1e4; + qreal fxnorm = nxr * (fxr - 1.0) / (fxr - nxr); + qreal fynorm = nyr * (fyr - 1.0) / (fyr - nyr); - if (fxr > 1.0 && (fxrInt >= fyrInt || fyr < 1.0)) { - return 255 * nxr * (fxr - 1.0) / (fxr - nxr); - } + qreal retValue = 0; - if (fyr > 1.0 && (fyrInt > fxrInt || fxr < 1.0)) { - return 255 * nyr * (fyr - 1.0) / (fyr - nyr); - } + if(fxr > 1.0) { + retValue = fxnorm; + } + + if (fxnorm < fynorm && fyr > 1.0) { + retValue = fynorm; + } - return 0; + return retValue * 255; } diff --git a/libs/image/processing/kis_assign_profile_processing_visitor.cpp b/libs/image/processing/kis_assign_profile_processing_visitor.cpp index ae3d56c807..52eb83d185 100644 --- a/libs/image/processing/kis_assign_profile_processing_visitor.cpp +++ b/libs/image/processing/kis_assign_profile_processing_visitor.cpp @@ -1,88 +1,87 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_assign_profile_processing_visitor.h" #include "kis_external_layer_iface.h" #include "kis_paint_device.h" #include "kis_transaction.h" #include "kis_undo_adapter.h" #include "kis_transform_mask.h" #include "lazybrush/kis_colorize_mask.h" #include #include "kis_projection_leaf.h" #include "kis_paint_layer.h" #include "kis_time_range.h" #include KisAssignProfileProcessingVisitor::KisAssignProfileProcessingVisitor(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace) : m_srcColorSpace(srcColorSpace) , m_dstColorSpace(dstColorSpace) { } void KisAssignProfileProcessingVisitor::visitExternalLayer(KisExternalLayer *layer, KisUndoAdapter *undoAdapter) { undoAdapter->addCommand(layer->setProfile(m_dstColorSpace->profile())); } void KisAssignProfileProcessingVisitor::visitNodeWithPaintDevice(KisNode *node, KisUndoAdapter *undoAdapter) { if (!node->projectionLeaf()->isLayer()) return; if (*m_dstColorSpace == *node->colorSpace()) return; QSet paintDevices; paintDevices.insert(node->paintDevice()); paintDevices.insert(node->original()); paintDevices.insert(node->projection()); KUndo2Command *parentConversionCommand = new KUndo2Command(); Q_FOREACH (KisPaintDeviceSP dev, paintDevices) { if (dev->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) { dev->setProfile(m_dstColorSpace->profile(), parentConversionCommand); } } undoAdapter->addCommand(parentConversionCommand); node->invalidateFrames(KisTimeRange::infinite(0), node->extent()); } void KisAssignProfileProcessingVisitor::visit(KisTransformMask *mask, KisUndoAdapter *undoAdapter) { mask->threadSafeForceStaticImageUpdate(); KisSimpleProcessingVisitor::visit(mask, undoAdapter); } void KisAssignProfileProcessingVisitor::visitColorizeMask(KisColorizeMask *mask, KisUndoAdapter *undoAdapter) { if (mask->colorSpace()->colorModelId() == m_srcColorSpace->colorModelId()) { KUndo2Command *parentConversionCommand = new KUndo2Command(); mask->setProfile(m_dstColorSpace->profile(), parentConversionCommand); undoAdapter->addCommand(parentConversionCommand); mask->invalidateFrames(KisTimeRange::infinite(0), mask->extent()); } - KisSimpleProcessingVisitor::visit(mask, undoAdapter); } diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 69af500440..1449987b9f 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,909 +1,921 @@ /* * Copyright (C) 1998, 1999 Torben Weis * 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 "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_MACOS #include "osx.h" #endif #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 "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; + QVector earlyRemoteArguments; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_MACOS setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("krita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { - QStringList styles = QStringList() /*<< "breeze"*/ << "fusion" << "plastique"; + QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType(ResourceType::Brushes, "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType(ResourceType::PaintOpPresets, "data", "/paintoppresets/"); KoResourcePaths::addResourceType(ResourceType::Workspaces, "data", "/workspaces/"); KoResourcePaths::addResourceType(ResourceType::WindowLayouts, "data", "/windowlayouts/"); KoResourcePaths::addResourceType(ResourceType::Sessions, "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType(ResourceType::Patterns, "data", "/patterns/", true); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/"); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/", true); KoResourcePaths::addResourceType(ResourceType::Palettes, "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("kis_actions", "data", "/pykrita"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType(ResourceType::FilterEffects, "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType(ResourceType::Symbols, "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType(ResourceType::GamutMasks, "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir(ResourceType::Gradients, "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Gradients, QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Patterns, "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Patterns, QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Brushes, "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Brushes, QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Palettes, "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir(ResourceType::Palettes, QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } bool KisApplication::registerResources() { KisResourceLoaderRegistry *reg = KisResourceLoaderRegistry::instance(); reg->add(new KisResourceLoader(ResourceType::PaintOpPresets, ResourceType::PaintOpPresets, i18n("Brush presets"), QStringList() << "application/x-krita-paintoppreset")); reg->add(new KisResourceLoader(ResourceSubType::GbrBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); reg->add(new KisResourceLoader(ResourceSubType::GihBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush-animated")); reg->add(new KisResourceLoader(ResourceSubType::SvgBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceSubType::PngBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/png")); reg->add(new KisResourceLoader(ResourceSubType::SegmentedGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-gimp-gradient")); reg->add(new KisResourceLoader(ResourceSubType::StopGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-karbon-gradient" << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::Palettes, ResourceType::Palettes, i18n("Palettes"), QStringList() << KisMimeDatabase::mimeTypeForSuffix("kpl") << KisMimeDatabase::mimeTypeForSuffix("gpl") << KisMimeDatabase::mimeTypeForSuffix("pal") << KisMimeDatabase::mimeTypeForSuffix("act") << KisMimeDatabase::mimeTypeForSuffix("aco") << KisMimeDatabase::mimeTypeForSuffix("css") << KisMimeDatabase::mimeTypeForSuffix("colors") << KisMimeDatabase::mimeTypeForSuffix("xml") << KisMimeDatabase::mimeTypeForSuffix("sbz"))); QList src = QImageReader::supportedMimeTypes(); QStringList allImageMimes; Q_FOREACH(const QByteArray ba, src) { if (QImageWriter::supportedMimeTypes().contains(ba)) { allImageMimes << QString::fromUtf8(ba); } } allImageMimes << KisMimeDatabase::mimeTypeForSuffix("pat"); reg->add(new KisResourceLoader(ResourceType::Patterns, ResourceType::Patterns, i18n("Patterns"), allImageMimes)); reg->add(new KisResourceLoader(ResourceType::Workspaces, ResourceType::Workspaces, i18n("Workspaces"), QStringList() << "application/x-krita-workspace")); reg->add(new KisResourceLoader(ResourceType::Symbols, ResourceType::Symbols, i18n("SVG symbol libraries"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::WindowLayouts, ResourceType::WindowLayouts, i18n("Window layouts"), QStringList() << "application/x-krita-windowlayout")); reg->add(new KisResourceLoader(ResourceType::Sessions, ResourceType::Sessions, i18n("Sessions"), QStringList() << "application/x-krita-session")); reg->add(new KisResourceLoader(ResourceType::GamutMasks, ResourceType::GamutMasks, i18n("Gamut masks"), QStringList() << "application/x-krita-gamutmasks")); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), i18n("%1\n\nKrita will quit now.").arg(KisResourceCacheDb::lastError())); //return false; } KisResourceLocator::LocatorError r = KisResourceLocator::instance()->initialize(KoResourcePaths::getApplicationRoot() + "/share/krita"); connect(KisResourceLocator::instance(), SIGNAL(progressMessage(const QString&)), this, SLOT(setSplashScreenLoadingText(const QString&))); if (r != KisResourceLocator::LocatorError::Ok ) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), KisResourceLocator::instance()->errorMessages().join('\n') + i18n("\n\nKrita will quit now.")); //return false; } return true; } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); } void KisApplication::loadGuiPlugins() { // qDebug() << "loadGuiPlugins();"; // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); processEvents(); // qDebug() << "loading tools"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); processEvents(); // qDebug() << "loading dockers"; KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS" && opengl != "TRY_OPENGL") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources if (!registerResources()) { return false; } // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResourceSP windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace->resourceId()); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.image(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return false; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); bool result = doc->openUrl(QUrl::fromLocalFile(fileName)); if (!result) { errKrita << "Could not load " << fileName << ":" << doc->errorMessage(); QTimer::singleShot(0, this, SLOT(quit())); return false; } if (exportFileName.isEmpty()) { errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option"; QTimer::singleShot(0, this, SLOT(quit())); return false; } qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return false; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } + Q_FOREACH(const QByteArray &message, d->earlyRemoteArguments) { + executeRemoteArguments(message, d->mainWindow); + } + // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { KisResourceCacheDb::deleteTemporaryResources(); } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(const QString &textToLoad) { if (d->splashScreen) { d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } -void KisApplication::remoteArguments(QByteArray message, QObject *socket) +void KisApplication::executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow) { - Q_UNUSED(socket); - - // check if we have any mainwindow - KisMainWindow *mw = qobject_cast(qApp->activeWindow()); - if (!mw) { - mw = KisPart::instance()->mainWindows().first(); - } - - if (!mw) { - return; - } - KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { - createNewDocFromTemplate(filename, mw); + createNewDocFromTemplate(filename, mainWindow); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; - mw->openDocument(QUrl::fromLocalFile(filename), flags); + mainWindow->openDocument(QUrl::fromLocalFile(filename), flags); } } } } + +void KisApplication::remoteArguments(QByteArray message, QObject *socket) +{ + Q_UNUSED(socket); + + // check if we have any mainwindow + KisMainWindow *mw = qobject_cast(qApp->activeWindow()); + + if (!mw) { + mw = KisPart::instance()->mainWindows().first(); + } + + if (!mw) { + d->earlyRemoteArguments << message; + return; + } + executeRemoteArguments(message, mw); +} + void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Visible autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { KisUsageLogger::log(QString("Removing autosave file %1").arg(dir.absolutePath() + "/" + autosaveFile)); QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/KisApplication.h b/libs/ui/KisApplication.h index f5335b844b..f07c6817fa 100644 --- a/libs/ui/KisApplication.h +++ b/libs/ui/KisApplication.h @@ -1,121 +1,122 @@ /* * Copyright (C) 1998, 1999 Torben Weis * 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. */ #ifndef KIS_APPLICATION_H #define KIS_APPLICATION_H #include #include #include #include "kritaui_export.h" class KisMainWindow; class KisApplicationPrivate; class QWidget; class KisApplicationArguments; class KisAutoSaveRecoveryDialog; #include /** * @brief Base class for the %Krita app * * This class handles arguments given on the command line and * shows a generic about dialog for the Krita app. * * In addition it adds the standard directories where Krita * can find its images etc. * * If the last mainwindow becomes closed, KisApplication automatically * calls QApplication::quit. */ class KRITAUI_EXPORT KisApplication : public QtSingleApplication { Q_OBJECT public: /** * Creates an application object, adds some standard directories and * initializes kimgio. */ explicit KisApplication(const QString &key, int &argc, char **argv); /** * Destructor. */ ~KisApplication() override; /** * Call this to start the application. * * Parses command line arguments and creates the initial main windowss and docs * from them (or an empty doc if no cmd-line argument is specified ). * * You must call this method directly before calling QApplication::exec. * * It is valid behaviour not to call this method at all. In this case you * have to process your command line parameters by yourself. */ virtual bool start(const KisApplicationArguments &args); /** * Checks if user is holding ctrl+alt+shift keys and asks if the settings file should be cleared. * * Typically called during startup before reading the config. */ void askClearConfig(); /** * Tell KisApplication to show this splashscreen when you call start(); * when start returns, the splashscreen is hidden. Use KSplashScreen * to have the splash show correctly on Xinerama displays. */ void setSplashScreen(QWidget *splash); void hideSplashScreen(); /// Overridden to handle exceptions from event handlers. bool notify(QObject *receiver, QEvent *event) override; void addResourceTypes(); bool registerResources(); void loadPlugins(); void loadGuiPlugins(); void initializeGlobals(const KisApplicationArguments &args); public Q_SLOTS: + void executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow); void remoteArguments(QByteArray message, QObject*socket); void fileOpenRequested(const QString & url); void setSplashScreenLoadingText(const QString&); private: /// @return the number of autosavefiles opened void checkAutosaveFiles(); bool createNewDocFromTemplate(const QString &fileName, KisMainWindow *m_mainWindow); void clearConfig(); private: class Private; QScopedPointer d; class ResetStarting; friend class ResetStarting; }; #endif diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index 894fde96b1..c58a6e76a6 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2813 +1,2822 @@ /* 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 #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_ANDROID #include #endif #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include "KisCanvasWindow.h" #include "kis_action.h" #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new KisSignalMapper(parent)) , documentMapper(new KisSignalMapper(parent)) #ifdef Q_OS_ANDROID , fileManager(new KisAndroidFileManager(parent)) #endif { if (id.isNull()) this->id = QUuid::createUuid(); welcomeScroller = new QScrollArea(); welcomeScroller->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); welcomeScroller->setWidget(welcomePage); welcomeScroller->setWidgetResizable(true); widgetStack->addWidget(welcomeScroller); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *toggleDetachCanvas {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KisResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; QScrollArea *welcomeScroller {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; KisSignalMapper *windowMapper; KisSignalMapper *documentMapper; KisCanvasWindow *canvasWindow {0}; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; #ifdef Q_OS_ANDROID KisAndroidFileManager *fileManager; #endif KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { d->workspacemodel = KisResourceModelProvider::resourceModel(ResourceType::Workspaces); connect(d->workspacemodel, SIGNAL(afterResourcesLayoutReset()), this, SLOT(updateWindowMenu())); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(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->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); d->canvasWindow = new KisCanvasWindow(this); actionCollection()->addAssociatedWidget(d->canvasWindow); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); connect(guiFactory(), SIGNAL(makingChanges(bool)), SLOT(slotXmlGuiMakingChanges(bool))); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace->resourceId()); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view, QMdiSubWindow *subWindow) { if (d->activeView == view && !subWindow) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view, subWindow); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In this case we should clear all the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView, QMdiSubWindow *subwin) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); if (!subwin) { subwin = d->mdiArea->addSubWindow(imageView); } else { subwin->setWidget(imageView); } imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); + if (d->mdiArea->subWindowList().size() == 1) { + imageView->showMaximized(); + } + else { + imageView->show(); + } + /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); - if (d->mdiArea->subWindowList().size() == 1) { - imageView->showMaximized(); - } - else { - imageView->show(); - } - // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { QScopedPointer dlgPreferences(new KisDlgPreferences(this)); if (!dlgPreferences->editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer 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(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } bool KisMainWindow::canvasDetached() const { return centralWidget() != d->widgetStack; } void KisMainWindow::setCanvasDetached(bool detach) { if (detach == canvasDetached()) return; QWidget *outgoingWidget = centralWidget() ? takeCentralWidget() : nullptr; QWidget *incomingWidget = d->canvasWindow->swapMainWidget(outgoingWidget); if (incomingWidget) { setCentralWidget(incomingWidget); } if (detach) { d->canvasWindow->show(); } else { d->canvasWindow->hide(); } } void KisMainWindow::slotFileSelected(QString path) { QString url = path; if (!url.isEmpty()) { bool res = openDocument(QUrl::fromLocalFile(url), Import); if (!res) { warnKrita << "Loading" << url << "failed"; } } } void KisMainWindow::slotEmptyFilePath() { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The chosen file's location could not be found. Does it exist?")); } QWidget * KisMainWindow::canvasWindow() const { return d->canvasWindow; } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); d->welcomePage->populateRecentDocuments(); } void KisMainWindow::removeRecentUrl(const QUrl &url) { d->recentFiles->removeUrl(url); KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); caption = "RESOURCES REWRITE GOING ON " + caption; if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; + // XXX: Why this duplication of of OpenFlags... if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); - if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } + + if (flags & RecoveryFile && + ( url.toLocalFile().startsWith(QDir::tempPath()) + || url.toLocalFile().startsWith(QDir::homePath())) + ) { + newdoc->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/" + QFileInfo(url.toLocalFile()).fileName())); + newdoc->save(false, 0); + } + return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document, QMdiSubWindow *subWindow) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, d->viewManager, this); addView(view, subWindow); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; 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(QString)), this, SLOT(slotLoadCanceled(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(QString)), this, SLOT(slotSaveCanceled(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::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } QImage KisMainWindow::layoutThumbnail() { int size = 256; qreal scale = qreal(size)/qreal(qMax(geometry().width(), geometry().height())); QImage layoutThumbnail = QImage(qRound(geometry().width()*scale), qRound(geometry().height()*scale), QImage::Format_ARGB32); QPainter gc(&layoutThumbnail); gc.fillRect(0, 0, layoutThumbnail.width(), layoutThumbnail.height(), this->palette().dark()); for (int childW = 0; childW< children().size(); childW++) { if (children().at(childW)->isWidgetType()) { QWidget *w = dynamic_cast(children().at(childW)); if (w->isVisible()) { QRect wRect = QRectF(w->geometry().x()*scale , w->geometry().y()*scale , w->geometry().width()*scale , w->geometry().height()*scale ).toRect(); wRect = wRect.intersected(layoutThumbnail.rect().adjusted(-1, -1, -1, -1)); gc.setBrush(this->palette().window()); if (w == d->widgetStack) { gc.setBrush(d->mdiArea->background()); } gc.setPen(this->palette().windowText().color()); gc.drawRect(wRect); } } } gc.end(); return layoutThumbnail; } 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; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty() && !d->lastExportLocation.contains(QDir::tempPath())) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).completeBaseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.completeBaseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).completeBaseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).completeBaseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (hackIsSaving()) { e->setAccepted(false); return; } if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); d->canvasWindow->close(); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; item.title = i18n("Custom Document"); startupWidget->addCustomDocumentWidget(item.widget, item.title, "Custom Document", item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, "Create from ClipBoard", item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { #ifndef Q_OS_ANDROID QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } #else Q_UNUSED(isImporting) d->fileManager->openImportFile(); connect(d->fileManager, SIGNAL(sigFileSelected(QString)), this, SLOT(slotFileSelected(QString))); connect(d->fileManager, SIGNAL(sigEmptyFilePath()), this, SLOT(slotEmptyFilePath())); #endif } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(int workspaceId) { KisWorkspaceResourceSP workspace = KisResourceModelProvider::resourceModel(ResourceType::Workspaces) ->resourceForId(workspaceId).dynamicCast(); bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { // Do not close while KisMainWindow has the savingEntryMutex locked, bug409395. // After the background saving job is initiated, KisDocument blocks closing // while it saves itself. if (hackIsSaving()) { return; } KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); KisImportExportErrorCode status = importer.import(files, firstFrame, step); if (!status.isOk() && !status.isInternalError()) { QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList 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::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); KisResourceIterator resourceIterator(KisResourceModelProvider::resourceModel(ResourceType::Workspaces)); KisMainWindow *m_this = this; while (resourceIterator.hasNext()) { KisResourceItemSP resource = resourceIterator.next(); QAction *action = workspaceMenu->addAction(resource->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(resource->id()); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Workspaces); 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; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace(new KisWorkspaceResource("")); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); bool fileOverWriteAccepted = false; while(!fileOverWriteAccepted) { name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + workspace->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (res == QMessageBox::Yes) fileOverWriteAccepted = true; } else { fileOverWriteAccepted = true; } } } workspace->setFilename(fileInfo.fileName()); 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, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } 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(true); 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 continue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document, QMdiSubWindow *subWindow) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc, subWindow); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resourceCount() == 0) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::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_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.completeBaseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); d->toggleDetachCanvas = actionManager->createAction("view_detached_canvas"); d->toggleDetachCanvas->setChecked(false); connect(d->toggleDetachCanvas, SIGNAL(toggled(bool)), SLOT(setCanvasDetached(bool))); setCanvasDetached(false); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QGuiApplication::screens().at(scnum)->availableVirtualGeometry(); quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } void KisMainWindow::slotXmlGuiMakingChanges(bool finished) { if (finished) { subWindowActivated(); } } #include diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp index 9a830700ab..f1b547f0cc 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.cpp @@ -1,395 +1,383 @@ /* * Copyright (c) 2016 Kapustin Alexey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_dlg_stroke_selection_properties.h" #include #include #include #include #include #include #include #include #include #include #include #include "KoColorProfile.h" #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorConversionTransformation.h" #include "KoColorPopupAction.h" #include "kis_icon_utils.h" #include "KoID.h" #include "kis_image.h" #include "kis_annotation.h" #include "kis_config.h" #include "kis_signal_compressor.h" #include "widgets/kis_cmb_idlist.h" #include #include "kis_layer_utils.h" #include #include "kis_canvas_resource_provider.h" #include "KoUnit.h" #include "kis_display_color_converter.h" KisDlgStrokeSelection::KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer) : KoDialog(view->mainWindow()) { m_resourceManager = view->mainWindow()->resourceManager(); + KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection"); converter = view->canvasBase()->displayColorConverter(); setButtons(Ok | Cancel); setDefaultButton(Ok); setCaption(i18nc("@title:window", "Stroke Selection Properties")); m_page = new WdgStrokeSelection(this); + m_page->m_isVectorLayer = isVectorLayer; + m_page->m_cfg = cfg; m_image = image; setMainWidget(m_page); - resize(m_page->sizeHint()); - - KisPropertiesConfigurationSP cfg = KisConfig(true).exportConfiguration("StrokeSelection"); - auto &m_options = m_page->m_options; + StrokeSelectionOptions &m_options = m_page->m_options; m_options.color = cfg->getColor("color"); m_options.lineColorSource = cfg->getInt("lineColorSource"); m_page->lineColorBox->setCurrentIndex(m_options.lineColorSource); m_page->colorSelector->setColor(getSelectedColor().toQColor()); m_options.brushSelected = cfg->getBool("useBrush", 0); m_page->typeBox->setCurrentIndex(m_options.brushSelected? 0 : 1); m_options._colorFillSource = cfg->getInt("colorFillSource", 0); m_page->fillBox->setCurrentIndex(m_options._colorFillSource); m_options.customColor = cfg->getColor("customColor"); + if (m_options._colorFillSource == static_cast(colorFillSource::CustomColor)) { m_page->colorFillSelector->setColor(m_options.customColor.toQColor()); } else { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } - m_options.fillColor = cfg->getColor("fillColor"); - if (m_options._colorFillSource == static_cast(colorFillSource::None)) { - m_page->colorFillSelector->setDisabled(true); - } - else { - m_page->colorFillSelector->setDisabled(false); } - m_options.lineSize = cfg->getInt("lineSize", 1); m_page->lineSize->setValue(m_options.lineSize); - if (m_options.brushSelected) { - m_page->lineSize->setDisabled(true); - m_page->fillBox->setDisabled(true); - m_page->colorFillSelector->setDisabled(true); - m_page->sizeBox->setDisabled(true); - } m_options.lineDimension = cfg->getInt("lineDimension", 0); m_page->sizeBox->setCurrentIndex(m_options.lineDimension); connect(m_page, SIGNAL(colorSelectorChanged()), SLOT(setColorButton())); connect(m_page, SIGNAL(colorFillSelectorChanged()), SLOT(setColorFillButton())); connect(m_page->colorFillSelector, SIGNAL(changed(QColor)), SLOT(colorFillChanged(QColor))); connect(m_page->colorSelector, SIGNAL(changed(QColor)), SLOT(colorChanged(QColor))); - if (isVectorLayer) { - lockVectorLayerFunctions(); - } + m_page->enableControls(); + } KisDlgStrokeSelection::~KisDlgStrokeSelection() { - auto &m_options = m_page->m_options; + StrokeSelectionOptions &m_options = m_page->m_options; m_options.lineSize = m_page->lineSize->value(); m_options.lineDimension = m_page->sizeBox->currentIndex(); m_options.lineColorSource = m_page->lineColorBox->currentIndex(); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("lineSize", m_options.lineSize); cfg->setProperty("colorFillSource", m_options._colorFillSource); cfg->setProperty("useBrush", m_options.brushSelected); cfg->setProperty("lineDimension", m_options.lineDimension); cfg->setProperty("lineColorSource", m_options.lineColorSource); QVariant colorVariant("KoColor"); colorVariant.setValue(m_options.customColor); cfg->setProperty("customColor", colorVariant); QVariant _colorVariant("KoColor"); _colorVariant.setValue(m_options.color); cfg->setProperty("color", _colorVariant); QVariant _cVariant("KoColor"); _cVariant.setValue(m_options.fillColor); cfg->setProperty("fillColor", _cVariant); KisConfig(false).setExportConfiguration("StrokeSelection", cfg); delete m_page; } KoColor KisDlgStrokeSelection::getSelectedColor() const { KoColor color; QString currentSource = m_page->lineColorBox->currentText(); if (currentSource == "Foreground color") { color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } else if (currentSource == "Background color") { - color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); - } - else { - color = m_page->m_options.color; - } + color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); + } + else { + color = m_page->m_options.color; + } return color; } KoColor KisDlgStrokeSelection::getFillSelectedColor() const { KoColor color; colorFillSource currentSource = static_cast(m_page->fillBox->currentIndex()); if (currentSource == colorFillSource::FGColor) { color = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value(); } else if (currentSource == colorFillSource::BGColor) { - color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); - } - else if (currentSource == colorFillSource::PaintColor) { - color = converter->approximateFromRenderedQColor(m_page->colorSelector->color()); - } - else { - color = m_page->m_options.customColor; - } + color = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value(); + } + else if (currentSource == colorFillSource::PaintColor) { + color = converter->approximateFromRenderedQColor(m_page->colorSelector->color()); + } + else { + color = m_page->m_options.customColor; + } return color; } bool KisDlgStrokeSelection::isBrushSelected() const { - int index = m_page->typeBox->currentIndex(); - drawType type = static_cast(index); - - if (type == drawType::brushDraw){ - return true; - } - else { - return false; - } + if (static_cast(m_page->typeBox->currentIndex()) == drawType::brushDraw){ + return true; + } + else { + return false; + } } StrokeSelectionOptions KisDlgStrokeSelection::getParams() const - { - StrokeSelectionOptions params; - - params.lineSize = getLineSize(); - params.color = getSelectedColor(); - params.brushSelected = isBrushSelected(); - params.fillColor = getFillSelectedColor(); - params._colorFillSource = m_page->m_options._colorFillSource; - return params; +{ + StrokeSelectionOptions params; -} + params.lineSize = getLineSize(); + params.color = getSelectedColor(); + params.brushSelected = isBrushSelected(); + params.fillColor = getFillSelectedColor(); + params._colorFillSource = m_page->m_options._colorFillSource; + return params; -void KisDlgStrokeSelection::lockVectorLayerFunctions() -{ - m_page->colorFillSelector->setEnabled(false); - m_page->lineSize->setEnabled(false); - m_page->sizeBox->setEnabled(false); - m_page->fillBox->setEnabled(false); - m_page->typeBox->setEnabled(false); } -void KisDlgStrokeSelection::unlockVectorLayerFunctions() -{ - m_page->colorFillSelector->setEnabled(true); - m_page->lineSize->setEnabled(true); - m_page->sizeBox->setEnabled(true); - m_page->fillBox->setEnabled(true); - m_page->typeBox->setEnabled(true); -} void KisDlgStrokeSelection::setColorFillButton() { m_page->colorFillSelector->setColor(getFillSelectedColor().toQColor()); } void KisDlgStrokeSelection::setColorButton() { m_page->colorSelector->setColor(getSelectedColor().toQColor()); } int KisDlgStrokeSelection::getLineSize() const { int value = m_page->lineSize->value(); if (m_page->sizeBox->currentText() == i18n("px")) { return value; } else if (m_page->sizeBox->currentText() == i18n("mm")) { - int pixels = static_cast(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel))); - return pixels; + int pixels = static_cast(KoUnit::convertFromUnitToUnit(value,KoUnit(KoUnit::Millimeter), KoUnit(KoUnit::Pixel))); + return pixels; } - else { - int pixels = static_cast(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel))); - return pixels; + else { + int pixels = static_cast(KoUnit::convertFromUnitToUnit(value, KoUnit(KoUnit::Inch), KoUnit(KoUnit::Pixel))); + return pixels; } } linePosition KisDlgStrokeSelection::getLinePosition() const {/* TODO int index = m_page->linePosition->currentIndex(); switch(index) { case(0): return linePosition::OUTSIDE; case(1): return linePosition::INSIDE; case(2): return linePosition::CENTER; default: return linePosition::CENTER; }*/ return linePosition::CENTER; } void KisDlgStrokeSelection::colorChanged(const QColor &newColor) { if (m_page->fillBox->currentText() == "Paint color") { m_page->colorFillSelector->setColor(newColor); } QColor BGColor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); - if (!(newColor == BGColor) && !(newColor == FGColor)) { + if (!(newColor == BGColor) && !(newColor == FGColor)) { m_page->m_options.color = tempColor; m_page->lineColorBox->setCurrentIndex(2); //custom color - } + } } void KisDlgStrokeSelection::colorFillChanged(const QColor &newColor) { QColor PaintColor = m_page->colorSelector->color(); QColor BGcolor = m_resourceManager->resource(KoCanvasResourceProvider::BackgroundColor).value().toQColor(); QColor FGColor = m_resourceManager->resource(KoCanvasResourceProvider::ForegroundColor).value().toQColor(); KoColor tempColor= converter->approximateFromRenderedQColor(newColor); if (!(newColor == FGColor) && !(newColor == BGcolor) && !(newColor == PaintColor)) { m_page->m_options.customColor = tempColor; m_page->fillBox->setCurrentIndex(static_cast(colorFillSource::CustomColor)); } m_page->m_options.fillColor = tempColor; } WdgStrokeSelection::WdgStrokeSelection(QWidget *parent) : QWidget(parent) { setupUi(this); } +void WdgStrokeSelection::enableControls() +{ + m_options.fillColor = m_cfg->getColor("fillColor"); + if (m_options._colorFillSource == static_cast(colorFillSource::None)) { + colorFillSelector->setEnabled(false); + } + else { + colorFillSelector->setEnabled(true); + } + + if (m_isVectorLayer) { + typeBox->setCurrentIndex(1); + typeBox->setEnabled(false); + } + else { + typeBox->setEnabled(true); + } + + on_typeBox_currentIndexChanged(typeBox->currentIndex()); +} + void WdgStrokeSelection::on_fillBox_currentIndexChanged(int index) { if (index == static_cast(colorFillSource::None)) { colorFillSelector->setDisabled(true); } else { colorFillSelector->setDisabled(false); emit colorFillSelectorChanged(); } m_options._colorFillSource = index; } -void WdgStrokeSelection::on_typeBox_currentIndexChanged(const QString &arg1) +void WdgStrokeSelection::on_typeBox_currentIndexChanged(int arg1) { - if (arg1 == "Current Brush") { - m_options.brushSelected = true; - lineSize->setDisabled(true); - fillBox->setDisabled(true); - colorFillSelector->setDisabled(true); - sizeBox->setDisabled(true); - } - else { - m_options.brushSelected = false; - lineSize->setDisabled(false); - fillBox->setDisabled(false); - colorFillSelector->setDisabled(false); - sizeBox->setDisabled(false); - } + if (arg1 == 0) { + m_options.brushSelected = true; + lineSize->setEnabled(false); + fillBox->setEnabled(false); + colorFillSelector->setEnabled(false); + sizeBox->setEnabled(false); + } + else { + m_options.brushSelected = false; + lineSize->setEnabled(true); + fillBox->setEnabled(true); + colorFillSelector->setEnabled(true); + sizeBox->setEnabled(true); + } } -void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(const QString &/*arg1*/) +void WdgStrokeSelection::on_lineColorBox_currentIndexChanged(int/*arg1*/) { emit colorSelectorChanged(); } StrokeSelectionOptions ::StrokeSelectionOptions(): lineSize(1), brushSelected(false), _colorFillSource(0), lineColorSource(0), lineDimension(0) { color.fromQColor(Qt::black); fillColor.fromQColor(Qt::black); customColor.fromQColor(Qt::black); } KisToolShapeUtils::FillStyle StrokeSelectionOptions::fillStyle() const { using namespace KisToolShapeUtils; colorFillSource tempColor = static_cast(_colorFillSource); FillStyle style = FillStyleNone; switch (tempColor) { case colorFillSource::PaintColor: style = FillStyleForegroundColor; break; case colorFillSource::BGColor: style = FillStyleBackgroundColor; break; case colorFillSource::CustomColor: style = FillStyleBackgroundColor; break; case colorFillSource::None: style = FillStyleNone; break; case colorFillSource::FGColor: style = FillStyleBackgroundColor; break; } return style; } diff --git a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h index b2f8d6ae02..a866957a8d 100644 --- a/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h +++ b/libs/ui/dialogs/kis_dlg_stroke_selection_properties.h @@ -1,113 +1,117 @@ /* * Copyright (c) 2016 Alexey Kapustin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_DLG_STROKE_SELECTION_PROPERTIES_H_ #define KIS_DLG_STROKE_SELECTION_PROPERTIES_H_ #include #include "KisProofingConfiguration.h" #include #include "KisViewManager.h" #include "KoStrokeConfigWidget.h" #include "ui_wdgstrokeselectionproperties.h" #include #include class KoColorSpace; class KoColorPopupAction; enum class linePosition { OUTSIDE, INSIDE, CENTER }; enum class drawType{ brushDraw, lineDraw }; enum class colorFillSource { None, PaintColor, BGColor, CustomColor, FGColor }; struct StrokeSelectionOptions { StrokeSelectionOptions (); int lineSize; bool brushSelected; int _colorFillSource; int lineColorSource; int lineDimension; KoColor color; KoColor fillColor; KoColor customColor; KisToolShapeUtils::FillStyle fillStyle() const; void lock(); }; class WdgStrokeSelection : public QWidget, public Ui::WdgStrokeSelection { Q_OBJECT public: WdgStrokeSelection(QWidget *parent) ; StrokeSelectionOptions m_options; + bool m_isVectorLayer; + KisPropertiesConfigurationSP m_cfg; + + void enableControls(); + Q_SIGNALS: void colorFillSelectorChanged(); void colorSelectorChanged(); private Q_SLOTS: void on_fillBox_currentIndexChanged(int index); - void on_typeBox_currentIndexChanged(const QString &arg1); - void on_lineColorBox_currentIndexChanged(const QString &arg1); + void on_typeBox_currentIndexChanged(int index); + void on_lineColorBox_currentIndexChanged(int index); }; class KisDlgStrokeSelection : public KoDialog { Q_OBJECT - public: KisDlgStrokeSelection(KisImageWSP image, KisViewManager *view, bool isVectorLayer); ~KisDlgStrokeSelection() override; + int getLineSize() const; linePosition getLinePosition() const; KoColor getSelectedColor() const; bool isBrushSelected() const; KoColor getFillSelectedColor() const; StrokeSelectionOptions getParams() const; - void lockVectorLayerFunctions(); - void unlockVectorLayerFunctions(); private: - WdgStrokeSelection * m_page; + WdgStrokeSelection *m_page {0}; KisImageWSP m_image; - KoCanvasResourceProvider *m_resourceManager; - KisDisplayColorConverter *converter; + KoCanvasResourceProvider *m_resourceManager {0}; + KisDisplayColorConverter *converter {0}; + bool m_isVectorLayer {false}; private Q_SLOTS: void setColorFillButton(); void setColorButton(); void colorChanged(const QColor &newColor); void colorFillChanged(const QColor &newColor); }; #endif // KIS_DLG_STROKE_SEL_PROPERTIES_H_ diff --git a/libs/ui/forms/wdgstrokeselectionproperties.ui b/libs/ui/forms/wdgstrokeselectionproperties.ui index 1f258c5e65..6641d1ecd1 100644 --- a/libs/ui/forms/wdgstrokeselectionproperties.ui +++ b/libs/ui/forms/wdgstrokeselectionproperties.ui @@ -1,210 +1,248 @@ WdgStrokeSelection 0 0 - 304 + 334 208 0 0 New Image - + 0 Stroke - - - - - - - 0 - - - - Current Brush - - - - - Line selection - - - + + + + + Type: + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + 0 + + + + Current Brush + + + + + Line selection + + + + + + + + Line: + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + + Foreground color + - - - - Fill: - - + + + Background color + - - - - Type: - - + + + Custom color + - - - - - - - 1 - - - 1000000 - - - 1 - - + + + + + + Color + + + false + + + false + + + false + + + false + + + + + + + Width: + + + lineSize + + + + + + + + 175 + 0 + + + + + + + 1 + + + 1000000 + + + 1 + + + + + + + + px + - - - - Width: - - - lineSize - - + + + mm + - - - - - px - - - - - mm - - - - - inch - - - + + + inch + - - - - - None - - - - - Paint color - - - - - Background color - - - - - Custom color - - - - - Foreground color - - - + + + + + + Fill: + + + + + + + + 0 + 0 + + + + + 175 + 0 + + + + + None + - - - - Line: - - + + + Paint color + - - - - Color - - - false - - - false - - - false - - - false - - + + + Background color + - - - - - Foreground color - - - - - Background color - - - - - Custom color - - - + + + Custom color + - - - - Color - - + + + Foreground color + - + + + + + + Color + + KColorButton QPushButton
kcolorbutton.h
1
diff --git a/libs/ui/kis_config.cc b/libs/ui/kis_config.cc index 4c7711b248..dc2d10931a 100644 --- a/libs/ui/kis_config.cc +++ b/libs/ui/kis_config.cc @@ -1,2209 +1,2215 @@ /* * Copyright (c) 2002 Patrick Julien * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_canvas_resource_provider.h" #include "kis_config_notifier.h" #include "kis_snap_config.h" #include #include #include #include #include #ifdef Q_OS_WIN #include "config_use_qt_tablet_windows.h" #endif KisConfig::KisConfig(bool readOnly) : m_cfg( KSharedConfig::openConfig()->group("")) , m_readOnly(readOnly) { if (!readOnly) { KIS_SAFE_ASSERT_RECOVER_RETURN(qApp && qApp->thread() == QThread::currentThread()); } } KisConfig::~KisConfig() { if (m_readOnly) return; if (qApp && qApp->thread() != QThread::currentThread()) { dbgKrita << "WARNING: KisConfig: requested config synchronization from nonGUI thread! Called from:" << kisBacktrace(); return; } m_cfg.sync(); } void KisConfig::logImportantSettings() const { - KisUsageLogger::write("Current Settings\n"); - KisUsageLogger::write(QString("\tCurrent Swap Location: %1").arg(KisImageConfig(true).swapDir())); - KisUsageLogger::write(QString("\tUndo Enabled: %1").arg(undoEnabled())); - KisUsageLogger::write(QString("\tUndo Stack Limit: %1").arg(undoStackLimit())); - KisUsageLogger::write(QString("\tUse OpenGL: %1").arg(useOpenGL())); - KisUsageLogger::write(QString("\tUse OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer())); - KisUsageLogger::write(QString("\tUse AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround())); - KisUsageLogger::write(QString("\tCanvas State: %1").arg(canvasState())); - KisUsageLogger::write(QString("\tAutosave Interval: %1").arg(autoSaveInterval())); - KisUsageLogger::write(QString("\tUse Backup Files: %1").arg(backupFile())); - KisUsageLogger::write(QString("\tNumber of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1))); - KisUsageLogger::write(QString("\tBackup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~"))); + KisUsageLogger::writeSysInfo("Current Settings\n"); + KisUsageLogger::writeSysInfo(QString(" Current Swap Location: %1").arg(KisImageConfig(true).swapDir())); + KisUsageLogger::writeSysInfo(QString(" Undo Enabled: %1").arg(undoEnabled())); + KisUsageLogger::writeSysInfo(QString(" Undo Stack Limit: %1").arg(undoStackLimit())); + KisUsageLogger::writeSysInfo(QString(" Use OpenGL: %1").arg(useOpenGL())); + KisUsageLogger::writeSysInfo(QString(" Use OpenGL Texture Buffer: %1").arg(useOpenGLTextureBuffer())); + KisUsageLogger::writeSysInfo(QString(" Use AMD Vectorization Workaround: %1").arg(enableAmdVectorizationWorkaround())); + KisUsageLogger::writeSysInfo(QString(" Canvas State: %1").arg(canvasState())); + KisUsageLogger::writeSysInfo(QString(" Autosave Interval: %1").arg(autoSaveInterval())); + KisUsageLogger::writeSysInfo(QString(" Use Backup Files: %1").arg(backupFile())); + KisUsageLogger::writeSysInfo(QString(" Number of Backups Kept: %1").arg(m_cfg.readEntry("numberofbackupfiles", 1))); + KisUsageLogger::writeSysInfo(QString(" Backup File Suffix: %1").arg(m_cfg.readEntry("backupfilesuffix", "~"))); QString backupDir; switch(m_cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location backupDir = "Same Folder as the File"; } - KisUsageLogger::write(QString("\tBackup Location: %1").arg(backupDir)); + KisUsageLogger::writeSysInfo(QString(" Backup Location: %1").arg(backupDir)); - KisUsageLogger::write(QString("\tUse Win8 Pointer Input: %1").arg(useWin8PointerInput())); - KisUsageLogger::write(QString("\tUse RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround())); - KisUsageLogger::write(QString("\tLevels of Detail Enabled: %1").arg(levelOfDetailEnabled())); - KisUsageLogger::write(QString("\tUse Zip64: %1").arg(useZip64())); + KisUsageLogger::writeSysInfo(QString(" Use Win8 Pointer Input: %1").arg(useWin8PointerInput())); + KisUsageLogger::writeSysInfo(QString(" Use RightMiddleTabletButton Workaround: %1").arg(useRightMiddleTabletButtonWorkaround())); + KisUsageLogger::writeSysInfo(QString(" Levels of Detail Enabled: %1").arg(levelOfDetailEnabled())); + KisUsageLogger::writeSysInfo(QString(" Use Zip64: %1").arg(useZip64())); - KisUsageLogger::write("\n"); + KisUsageLogger::writeSysInfo("\n"); } bool KisConfig::disableTouchOnCanvas(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchOnCanvas", false)); } void KisConfig::setDisableTouchOnCanvas(bool value) const { m_cfg.writeEntry("disableTouchOnCanvas", value); } bool KisConfig::disableTouchRotation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableTouchRotation", false)); } void KisConfig::setDisableTouchRotation(bool value) const { m_cfg.writeEntry("disableTouchRotation", value); } bool KisConfig::useProjections(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useProjections", true)); } void KisConfig::setUseProjections(bool useProj) const { m_cfg.writeEntry("useProjections", useProj); } bool KisConfig::undoEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("undoEnabled", true)); } void KisConfig::setUndoEnabled(bool undo) const { m_cfg.writeEntry("undoEnabled", undo); } int KisConfig::undoStackLimit(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("undoStackLimit", 30)); } void KisConfig::setUndoStackLimit(int limit) const { m_cfg.writeEntry("undoStackLimit", limit); } bool KisConfig::useCumulativeUndoRedo(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useCumulativeUndoRedo",false)); } void KisConfig::setCumulativeUndoRedo(bool value) { m_cfg.writeEntry("useCumulativeUndoRedo", value); } qreal KisConfig::stackT1(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackT1",5)); } void KisConfig::setStackT1(int T1) { m_cfg.writeEntry("stackT1", T1); } qreal KisConfig::stackT2(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("stackT2",1)); } void KisConfig::setStackT2(int T2) { m_cfg.writeEntry("stackT2", T2); } int KisConfig::stackN(bool defaultValue) const { return (defaultValue ? 5 : m_cfg.readEntry("stackN",5)); } void KisConfig::setStackN(int N) { m_cfg.writeEntry("stackN", N); } qint32 KisConfig::defImageWidth(bool defaultValue) const { return (defaultValue ? 1600 : m_cfg.readEntry("imageWidthDef", 1600)); } qint32 KisConfig::defImageHeight(bool defaultValue) const { return (defaultValue ? 1200 : m_cfg.readEntry("imageHeightDef", 1200)); } qreal KisConfig::defImageResolution(bool defaultValue) const { return (defaultValue ? 100.0 : m_cfg.readEntry("imageResolutionDef", 100.0)) / 72.0; } QString KisConfig::defColorModel(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id() : m_cfg.readEntry("colorModelDef", KoColorSpaceRegistry::instance()->rgb8()->colorModelId().id())); } void KisConfig::defColorModel(const QString & model) const { m_cfg.writeEntry("colorModelDef", model); } QString KisConfig::defaultColorDepth(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id() : m_cfg.readEntry("colorDepthDef", KoColorSpaceRegistry::instance()->rgb8()->colorDepthId().id())); } void KisConfig::setDefaultColorDepth(const QString & depth) const { m_cfg.writeEntry("colorDepthDef", depth); } QString KisConfig::defColorProfile(bool defaultValue) const { return (defaultValue ? KoColorSpaceRegistry::instance()->rgb8()->profile()->name() : m_cfg.readEntry("colorProfileDef", KoColorSpaceRegistry::instance()->rgb8()->profile()->name())); } void KisConfig::defColorProfile(const QString & profile) const { m_cfg.writeEntry("colorProfileDef", profile); } void KisConfig::defImageWidth(qint32 width) const { m_cfg.writeEntry("imageWidthDef", width); } void KisConfig::defImageHeight(qint32 height) const { m_cfg.writeEntry("imageHeightDef", height); } void KisConfig::defImageResolution(qreal res) const { m_cfg.writeEntry("imageResolutionDef", res*72.0); } int KisConfig::preferredVectorImportResolutionPPI(bool defaultValue) const { return defaultValue ? 100.0 : m_cfg.readEntry("preferredVectorImportResolution", 100.0); } void KisConfig::setPreferredVectorImportResolutionPPI(int value) const { m_cfg.writeEntry("preferredVectorImportResolution", value); } void cleanOldCursorStyleKeys(KConfigGroup &cfg) { if (cfg.hasKey("newCursorStyle") && cfg.hasKey("newOutlineStyle")) { cfg.deleteEntry("cursorStyleDef"); } } CursorStyle KisConfig::newCursorStyle(bool defaultValue) const { if (defaultValue) { return CURSOR_STYLE_NO_CURSOR; } int style = m_cfg.readEntry("newCursorStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: style = CURSOR_STYLE_TOOLICON; break; case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: style = CURSOR_STYLE_CROSSHAIR; break; case OLD_CURSOR_STYLE_POINTER: style = CURSOR_STYLE_POINTER; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_NO_CURSOR: style = CURSOR_STYLE_NO_CURSOR; break; case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: style = CURSOR_STYLE_SMALL_ROUND; break; case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: style = CURSOR_STYLE_TRIANGLE_RIGHTHANDED; break; case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = CURSOR_STYLE_TRIANGLE_LEFTHANDED; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_CURSOR_STYLE_SIZE) { style = CURSOR_STYLE_NO_CURSOR; } return (CursorStyle) style; } void KisConfig::setNewCursorStyle(CursorStyle style) { m_cfg.writeEntry("newCursorStyle", (int)style); } QColor KisConfig::getCursorMainColor(bool defaultValue) const { QColor col; col.setRgbF(0.501961, 1.0, 0.501961); return (defaultValue ? col : m_cfg.readEntry("cursorMaincColor", col)); } void KisConfig::setCursorMainColor(const QColor &v) const { m_cfg.writeEntry("cursorMaincColor", v); } OutlineStyle KisConfig::newOutlineStyle(bool defaultValue) const { if (defaultValue) { return OUTLINE_FULL; } int style = m_cfg.readEntry("newOutlineStyle", int(-1)); if (style < 0) { // old style format style = m_cfg.readEntry("cursorStyleDef", int(OLD_CURSOR_STYLE_OUTLINE)); switch (style) { case OLD_CURSOR_STYLE_TOOLICON: case OLD_CURSOR_STYLE_CROSSHAIR: case OLD_CURSOR_STYLE_POINTER: case OLD_CURSOR_STYLE_NO_CURSOR: case OLD_CURSOR_STYLE_SMALL_ROUND: case OLD_CURSOR_STYLE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_TRIANGLE_LEFTHANDED: style = OUTLINE_NONE; break; case OLD_CURSOR_STYLE_OUTLINE: case OLD_CURSOR_STYLE_OUTLINE_CENTER_DOT: case OLD_CURSOR_STYLE_OUTLINE_CENTER_CROSS: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_RIGHTHANDED: case OLD_CURSOR_STYLE_OUTLINE_TRIANGLE_LEFTHANDED: style = OUTLINE_FULL; break; default: style = -1; } } cleanOldCursorStyleKeys(m_cfg); // compatibility with future versions if (style < 0 || style >= N_OUTLINE_STYLE_SIZE) { style = OUTLINE_FULL; } return (OutlineStyle) style; } void KisConfig::setNewOutlineStyle(OutlineStyle style) { m_cfg.writeEntry("newOutlineStyle", (int)style); } QRect KisConfig::colorPreviewRect() const { return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect(); } void KisConfig::setColorPreviewRect(const QRect &rect) { m_cfg.writeEntry("colorPreviewRect", QVariant(rect)); } bool KisConfig::useDirtyPresets(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useDirtyPresets", true)); } void KisConfig::setUseDirtyPresets(bool value) { m_cfg.writeEntry("useDirtyPresets",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushSize(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushSize", false)); } void KisConfig::setUseEraserBrushSize(bool value) { m_cfg.writeEntry("useEraserBrushSize",value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::useEraserBrushOpacity(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useEraserBrushOpacity",false)); } void KisConfig::setUseEraserBrushOpacity(bool value) { m_cfg.writeEntry("useEraserBrushOpacity",value); KisConfigNotifier::instance()->notifyConfigChanged(); } QString KisConfig::getMDIBackgroundColor(bool defaultValue) const { QColor col(77, 77, 77); KoColor kol(KoColorSpaceRegistry::instance()->rgb8()); kol.fromQColor(col); QString xml = kol.toXML(); return (defaultValue ? xml : m_cfg.readEntry("mdiBackgroundColorXML", xml)); } void KisConfig::setMDIBackgroundColor(const QString &v) const { m_cfg.writeEntry("mdiBackgroundColorXML", v); } QString KisConfig::getMDIBackgroundImage(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("mdiBackgroundImage", "")); } void KisConfig::setMDIBackgroundImage(const QString &filename) const { m_cfg.writeEntry("mdiBackgroundImage", filename); } QString KisConfig::monitorProfile(int screen) const { // Note: keep this in sync with the default profile for the RGB colorspaces! QString profile = m_cfg.readEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), "sRGB-elle-V2-srgbtrc.icc"); //dbgKrita << "KisConfig::monitorProfile()" << profile; return profile; } QString KisConfig::monitorForScreen(int screen, const QString &defaultMonitor, bool defaultValue) const { return (defaultValue ? defaultMonitor : m_cfg.readEntry(QString("monitor_for_screen_%1").arg(screen), defaultMonitor)); } void KisConfig::setMonitorForScreen(int screen, const QString& monitor) { m_cfg.writeEntry(QString("monitor_for_screen_%1").arg(screen), monitor); } void KisConfig::setMonitorProfile(int screen, const QString & monitorProfile, bool override) const { m_cfg.writeEntry("monitorProfile/OverrideX11", override); m_cfg.writeEntry("monitorProfile" + QString(screen == 0 ? "": QString("_%1").arg(screen)), monitorProfile); } const KoColorProfile *KisConfig::getScreenProfile(int screen) { if (screen < 0) return 0; KisConfig cfg(true); QString monitorId; if (KisColorManager::instance()->devices().size() > screen) { monitorId = cfg.monitorForScreen(screen, KisColorManager::instance()->devices()[screen]); } //dbgKrita << "getScreenProfile(). Screen" << screen << "monitor id" << monitorId; if (monitorId.isEmpty()) { return 0; } QByteArray bytes = KisColorManager::instance()->displayProfile(monitorId); //dbgKrita << "\tgetScreenProfile()" << bytes.size(); const KoColorProfile * profile = 0; if (bytes.length() > 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), bytes); //dbgKrita << "\tKisConfig::getScreenProfile for screen" << screen << profile->name(); } return profile; } const KoColorProfile *KisConfig::displayProfile(int screen) const { if (screen < 0) return 0; // if the user plays with the settings, they can override the display profile, in which case // we don't want the system setting. bool override = useSystemMonitorProfile(); //dbgKrita << "KisConfig::displayProfile(). Override X11:" << override; const KoColorProfile *profile = 0; if (override) { //dbgKrita << "\tGoing to get the screen profile"; profile = KisConfig::getScreenProfile(screen); } // if it fails. check the configuration if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tGoing to get the monitor profile"; QString monitorProfileName = monitorProfile(screen); //dbgKrita << "\t\tmonitorProfileName:" << monitorProfileName; if (!monitorProfileName.isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(monitorProfileName); } if (profile) { //dbgKrita << "\t\tsuitable for display" << profile->isSuitableForDisplay(); } else { //dbgKrita << "\t\tstill no profile"; } } // if we still don't have a profile, or the profile isn't suitable for display, // we need to get a last-resort profile. the built-in sRGB is a good choice then. if (!profile || !profile->isSuitableForDisplay()) { //dbgKrita << "\tnothing worked, going to get sRGB built-in"; profile = KoColorSpaceRegistry::instance()->profileByName("sRGB Built-in"); } if (profile) { //dbgKrita << "\tKisConfig::displayProfile for screen" << screen << "is" << profile->name(); } else { //dbgKrita << "\tCouldn't get a display profile at all"; } return profile; } QString KisConfig::workingColorSpace(bool defaultValue) const { return (defaultValue ? "RGBA" : m_cfg.readEntry("workingColorSpace", "RGBA")); } void KisConfig::setWorkingColorSpace(const QString & workingColorSpace) const { m_cfg.writeEntry("workingColorSpace", workingColorSpace); } QString KisConfig::printerColorSpace(bool /*defaultValue*/) const { //TODO currently only rgb8 is supported //return (defaultValue ? "RGBA" : m_cfg.readEntry("printerColorSpace", "RGBA")); return QString("RGBA"); } void KisConfig::setPrinterColorSpace(const QString & printerColorSpace) const { m_cfg.writeEntry("printerColorSpace", printerColorSpace); } QString KisConfig::printerProfile(bool defaultValue) const { return (defaultValue ? "" : m_cfg.readEntry("printerProfile", "")); } void KisConfig::setPrinterProfile(const QString & printerProfile) const { m_cfg.writeEntry("printerProfile", printerProfile); } bool KisConfig::useBlackPointCompensation(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useBlackPointCompensation", true)); } void KisConfig::setUseBlackPointCompensation(bool useBlackPointCompensation) const { m_cfg.writeEntry("useBlackPointCompensation", useBlackPointCompensation); } bool KisConfig::allowLCMSOptimization(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("allowLCMSOptimization", true)); } void KisConfig::setAllowLCMSOptimization(bool allowLCMSOptimization) { m_cfg.writeEntry("allowLCMSOptimization", allowLCMSOptimization); } bool KisConfig::forcePaletteColors(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("colorsettings/forcepalettecolors", false)); } void KisConfig::setForcePaletteColors(bool forcePaletteColors) { m_cfg.writeEntry("colorsettings/forcepalettecolors", forcePaletteColors); } bool KisConfig::showRulers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showrulers", false)); } void KisConfig::setShowRulers(bool rulers) const { m_cfg.writeEntry("showrulers", rulers); } bool KisConfig::forceShowSaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowSaveMessages", false)); } void KisConfig::setForceShowSaveMessages(bool value) const { m_cfg.writeEntry("forceShowSaveMessages", value); } bool KisConfig::forceShowAutosaveMessages(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceShowAutosaveMessages", false)); } void KisConfig::setForceShowAutosaveMessages(bool value) const { m_cfg.writeEntry("forceShowAutosaveMessages", value); } bool KisConfig::rulersTrackMouse(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("rulersTrackMouse", true)); } void KisConfig::setRulersTrackMouse(bool value) const { m_cfg.writeEntry("rulersTrackMouse", value); } qint32 KisConfig::pasteBehaviour(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("pasteBehaviour", 2)); } void KisConfig::setPasteBehaviour(qint32 renderIntent) const { m_cfg.writeEntry("pasteBehaviour", renderIntent); } qint32 KisConfig::monitorRenderIntent(bool defaultValue) const { qint32 intent = m_cfg.readEntry("renderIntent", INTENT_PERCEPTUAL); if (intent > 3) intent = 3; if (intent < 0) intent = 0; return (defaultValue ? INTENT_PERCEPTUAL : intent); } void KisConfig::setRenderIntent(qint32 renderIntent) const { if (renderIntent > 3) renderIntent = 3; if (renderIntent < 0) renderIntent = 0; m_cfg.writeEntry("renderIntent", renderIntent); } bool KisConfig::useOpenGL(bool defaultValue) const { if (defaultValue) { return true; } const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return kritarc.value("OpenGLRenderer", "auto").toString() != "none"; } void KisConfig::disableOpenGL() const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", "none"); } int KisConfig::openGLFilteringMode(bool defaultValue) const { return (defaultValue ? 3 : m_cfg.readEntry("OpenGLFilterMode", 3)); } void KisConfig::setOpenGLFilteringMode(int filteringMode) { m_cfg.writeEntry("OpenGLFilterMode", filteringMode); } bool KisConfig::useOpenGLTextureBuffer(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("useOpenGLTextureBuffer", true)); } void KisConfig::setUseOpenGLTextureBuffer(bool useBuffer) { m_cfg.writeEntry("useOpenGLTextureBuffer", useBuffer); } int KisConfig::openGLTextureSize(bool defaultValue) const { return (defaultValue ? 256 : m_cfg.readEntry("textureSize", 256)); } bool KisConfig::disableVSync(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("disableVSync", true)); } void KisConfig::setDisableVSync(bool disableVSync) { m_cfg.writeEntry("disableVSync", disableVSync); } bool KisConfig::showAdvancedOpenGLSettings(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showAdvancedOpenGLSettings", false)); } bool KisConfig::forceOpenGLFenceWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceOpenGLFenceWorkaround", false)); } int KisConfig::numMipmapLevels(bool defaultValue) const { return (defaultValue ? 4 : m_cfg.readEntry("numMipmapLevels", 4)); } int KisConfig::textureOverlapBorder() const { return 1 << qMax(0, numMipmapLevels()); } quint32 KisConfig::getGridMainStyle(bool defaultValue) const { int v = m_cfg.readEntry("gridmainstyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGridMainStyle(quint32 v) const { m_cfg.writeEntry("gridmainstyle", v); } quint32 KisConfig::getGridSubdivisionStyle(bool defaultValue) const { quint32 v = m_cfg.readEntry("gridsubdivisionstyle", 1); if (v > 2) v = 2; return (defaultValue ? 1 : v); } void KisConfig::setGridSubdivisionStyle(quint32 v) const { m_cfg.writeEntry("gridsubdivisionstyle", v); } QColor KisConfig::getGridMainColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("gridmaincolor", col)); } void KisConfig::setGridMainColor(const QColor & v) const { m_cfg.writeEntry("gridmaincolor", v); } QColor KisConfig::getGridSubdivisionColor(bool defaultValue) const { QColor col(150, 150, 150); return (defaultValue ? col : m_cfg.readEntry("gridsubdivisioncolor", col)); } void KisConfig::setGridSubdivisionColor(const QColor & v) const { m_cfg.writeEntry("gridsubdivisioncolor", v); } QColor KisConfig::getPixelGridColor(bool defaultValue) const { QColor col(255, 255, 255); return (defaultValue ? col : m_cfg.readEntry("pixelGridColor", col)); } void KisConfig::setPixelGridColor(const QColor & v) const { m_cfg.writeEntry("pixelGridColor", v); } qreal KisConfig::getPixelGridDrawingThreshold(bool defaultValue) const { qreal border = 24.0f; return (defaultValue ? border : m_cfg.readEntry("pixelGridDrawingThreshold", border)); } void KisConfig::setPixelGridDrawingThreshold(qreal v) const { m_cfg.writeEntry("pixelGridDrawingThreshold", v); } bool KisConfig::pixelGridEnabled(bool defaultValue) const { bool enabled = true; return (defaultValue ? enabled : m_cfg.readEntry("pixelGridEnabled", enabled)); } void KisConfig::enablePixelGrid(bool v) const { m_cfg.writeEntry("pixelGridEnabled", v); } quint32 KisConfig::guidesLineStyle(bool defaultValue) const { int v = m_cfg.readEntry("guidesLineStyle", 0); v = qBound(0, v, 2); return (defaultValue ? 0 : v); } void KisConfig::setGuidesLineStyle(quint32 v) const { m_cfg.writeEntry("guidesLineStyle", v); } QColor KisConfig::guidesColor(bool defaultValue) const { QColor col(99, 99, 99); return (defaultValue ? col : m_cfg.readEntry("guidesColor", col)); } void KisConfig::setGuidesColor(const QColor & v) const { m_cfg.writeEntry("guidesColor", v); } void KisConfig::loadSnapConfig(KisSnapConfig *config, bool defaultValue) const { KisSnapConfig defaultConfig(false); if (defaultValue) { *config = defaultConfig; return; } config->setOrthogonal(m_cfg.readEntry("globalSnapOrthogonal", defaultConfig.orthogonal())); config->setNode(m_cfg.readEntry("globalSnapNode", defaultConfig.node())); config->setExtension(m_cfg.readEntry("globalSnapExtension", defaultConfig.extension())); config->setIntersection(m_cfg.readEntry("globalSnapIntersection", defaultConfig.intersection())); config->setBoundingBox(m_cfg.readEntry("globalSnapBoundingBox", defaultConfig.boundingBox())); config->setImageBounds(m_cfg.readEntry("globalSnapImageBounds", defaultConfig.imageBounds())); config->setImageCenter(m_cfg.readEntry("globalSnapImageCenter", defaultConfig.imageCenter())); config->setToPixel(m_cfg.readEntry("globalSnapToPixel", defaultConfig.toPixel())); } void KisConfig::saveSnapConfig(const KisSnapConfig &config) { m_cfg.writeEntry("globalSnapOrthogonal", config.orthogonal()); m_cfg.writeEntry("globalSnapNode", config.node()); m_cfg.writeEntry("globalSnapExtension", config.extension()); m_cfg.writeEntry("globalSnapIntersection", config.intersection()); m_cfg.writeEntry("globalSnapBoundingBox", config.boundingBox()); m_cfg.writeEntry("globalSnapImageBounds", config.imageBounds()); m_cfg.writeEntry("globalSnapImageCenter", config.imageCenter()); m_cfg.writeEntry("globalSnapToPixel", config.toPixel()); } qint32 KisConfig::checkSize(bool defaultValue) const { qint32 size = (defaultValue ? 32 : m_cfg.readEntry("checksize", 32)); if (size == 0) size = 32; return size; } void KisConfig::setCheckSize(qint32 checksize) const { if (checksize == 0) { checksize = 32; } m_cfg.writeEntry("checksize", checksize); } bool KisConfig::scrollCheckers(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("scrollingcheckers", false)); } void KisConfig::setScrollingCheckers(bool sc) const { m_cfg.writeEntry("scrollingcheckers", sc); } QColor KisConfig::canvasBorderColor(bool defaultValue) const { QColor color(QColor(128,128,128)); return (defaultValue ? color : m_cfg.readEntry("canvasBorderColor", color)); } void KisConfig::setCanvasBorderColor(const QColor& color) const { m_cfg.writeEntry("canvasBorderColor", color); } bool KisConfig::hideScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hideScrollbars", false)); } void KisConfig::setHideScrollbars(bool value) const { m_cfg.writeEntry("hideScrollbars", value); } QColor KisConfig::checkersColor1(bool defaultValue) const { QColor col(220, 220, 220); return (defaultValue ? col : m_cfg.readEntry("checkerscolor", col)); } void KisConfig::setCheckersColor1(const QColor & v) const { m_cfg.writeEntry("checkerscolor", v); } QColor KisConfig::checkersColor2(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("checkerscolor2", QColor(Qt::white))); } void KisConfig::setCheckersColor2(const QColor & v) const { m_cfg.writeEntry("checkerscolor2", v); } bool KisConfig::antialiasCurves(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("antialiascurves", true)); } void KisConfig::setAntialiasCurves(bool v) const { m_cfg.writeEntry("antialiascurves", v); } bool KisConfig::antialiasSelectionOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("AntialiasSelectionOutline", false)); } void KisConfig::setAntialiasSelectionOutline(bool v) const { m_cfg.writeEntry("AntialiasSelectionOutline", v); } bool KisConfig::showRootLayer(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowRootLayer", false)); } void KisConfig::setShowRootLayer(bool showRootLayer) const { m_cfg.writeEntry("ShowRootLayer", showRootLayer); } bool KisConfig::showGlobalSelection(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ShowGlobalSelection", false)); } void KisConfig::setShowGlobalSelection(bool showGlobalSelection) const { m_cfg.writeEntry("ShowGlobalSelection", showGlobalSelection); } bool KisConfig::showOutlineWhilePainting(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ShowOutlineWhilePainting", true)); } void KisConfig::setShowOutlineWhilePainting(bool showOutlineWhilePainting) const { m_cfg.writeEntry("ShowOutlineWhilePainting", showOutlineWhilePainting); } bool KisConfig::forceAlwaysFullSizedOutline(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("forceAlwaysFullSizedOutline", false)); } void KisConfig::setForceAlwaysFullSizedOutline(bool value) const { m_cfg.writeEntry("forceAlwaysFullSizedOutline", value); } KisConfig::SessionOnStartup KisConfig::sessionOnStartup(bool defaultValue) const { int value = defaultValue ? SOS_BlankSession : m_cfg.readEntry("sessionOnStartup", (int)SOS_BlankSession); return (KisConfig::SessionOnStartup)value; } void KisConfig::setSessionOnStartup(SessionOnStartup value) { m_cfg.writeEntry("sessionOnStartup", (int)value); } bool KisConfig::saveSessionOnQuit(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("saveSessionOnQuit", false); } void KisConfig::setSaveSessionOnQuit(bool value) { m_cfg.writeEntry("saveSessionOnQuit", value); } qreal KisConfig::outlineSizeMinimum(bool defaultValue) const { return (defaultValue ? 1.0 : m_cfg.readEntry("OutlineSizeMinimum", 1.0)); } void KisConfig::setOutlineSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("OutlineSizeMinimum", outlineSizeMinimum); } qreal KisConfig::selectionViewSizeMinimum(bool defaultValue) const { return (defaultValue ? 5.0 : m_cfg.readEntry("SelectionViewSizeMinimum", 5.0)); } void KisConfig::setSelectionViewSizeMinimum(qreal outlineSizeMinimum) const { m_cfg.writeEntry("SelectionViewSizeMinimum", outlineSizeMinimum); } int KisConfig::autoSaveInterval(bool defaultValue) const { return (defaultValue ? 15 * 60 : m_cfg.readEntry("AutoSaveInterval", 15 * 60)); } void KisConfig::setAutoSaveInterval(int seconds) const { return m_cfg.writeEntry("AutoSaveInterval", seconds); } bool KisConfig::backupFile(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("CreateBackupFile", true)); } void KisConfig::setBackupFile(bool backupFile) const { m_cfg.writeEntry("CreateBackupFile", backupFile); } bool KisConfig::showFilterGallery(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showFilterGallery", false)); } void KisConfig::setShowFilterGallery(bool showFilterGallery) const { m_cfg.writeEntry("showFilterGallery", showFilterGallery); } bool KisConfig::showFilterGalleryLayerMaskDialog(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showFilterGalleryLayerMaskDialog", true)); } void KisConfig::setShowFilterGalleryLayerMaskDialog(bool showFilterGallery) const { m_cfg.writeEntry("setShowFilterGalleryLayerMaskDialog", showFilterGallery); } QString KisConfig::canvasState(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return (defaultValue ? "OPENGL_NOT_TRIED" : kritarc.value("canvasState", "OPENGL_NOT_TRIED").toString()); } void KisConfig::setCanvasState(const QString& state) const { static QStringList acceptableStates; if (acceptableStates.isEmpty()) { acceptableStates << "OPENGL_SUCCESS" << "TRY_OPENGL" << "OPENGL_NOT_TRIED" << "OPENGL_FAILED"; } if (acceptableStates.contains(state)) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("canvasState", state); } } bool KisConfig::toolOptionsPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ToolOptionsPopupDetached", false)); } void KisConfig::setToolOptionsPopupDetached(bool detached) const { m_cfg.writeEntry("ToolOptionsPopupDetached", detached); } bool KisConfig::paintopPopupDetached(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("PaintopPopupDetached", false)); } void KisConfig::setPaintopPopupDetached(bool detached) const { m_cfg.writeEntry("PaintopPopupDetached", detached); } QString KisConfig::pressureTabletCurve(bool defaultValue) const { return (defaultValue ? "0,0;1,1" : m_cfg.readEntry("tabletPressureCurve","0,0;1,1;")); } void KisConfig::setPressureTabletCurve(const QString& curveString) const { m_cfg.writeEntry("tabletPressureCurve", curveString); } bool KisConfig::useWin8PointerInput(bool defaultValue) const { #ifdef Q_OS_WIN #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return useWin8PointerInputNoApp(&kritarc, defaultValue); #else return (defaultValue ? false : m_cfg.readEntry("useWin8PointerInput", false)); #endif #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseWin8PointerInput(bool value) { #ifdef Q_OS_WIN // Special handling: Only set value if changed // I don't want it to be set if the user hasn't touched it if (useWin8PointerInput() != value) { #ifdef USE_QT_TABLET_WINDOWS const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setUseWin8PointerInputNoApp(&kritarc, value); #else m_cfg.writeEntry("useWin8PointerInput", value); #endif } #else Q_UNUSED(value) #endif } bool KisConfig::useWin8PointerInputNoApp(QSettings *settings, bool defaultValue) { return defaultValue ? false : settings->value("useWin8PointerInput", false).toBool(); } void KisConfig::setUseWin8PointerInputNoApp(QSettings *settings, bool value) { settings->setValue("useWin8PointerInput", value); } bool KisConfig::useRightMiddleTabletButtonWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("useRightMiddleTabletButtonWorkaround", false)); } void KisConfig::setUseRightMiddleTabletButtonWorkaround(bool value) { m_cfg.writeEntry("useRightMiddleTabletButtonWorkaround", value); } qreal KisConfig::vastScrolling(bool defaultValue) const { return (defaultValue ? 0.9 : m_cfg.readEntry("vastScrolling", 0.9)); } void KisConfig::setVastScrolling(const qreal factor) const { m_cfg.writeEntry("vastScrolling", factor); } int KisConfig::presetChooserViewMode(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("presetChooserViewMode", 0)); } void KisConfig::setPresetChooserViewMode(const int mode) const { m_cfg.writeEntry("presetChooserViewMode", mode); } int KisConfig::presetIconSize(bool defaultValue) const { return (defaultValue ? 60 : m_cfg.readEntry("presetIconSize", 60)); } void KisConfig::setPresetIconSize(const int value) const { m_cfg.writeEntry("presetIconSize", value); } bool KisConfig::firstRun(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("firstRun", true)); } void KisConfig::setFirstRun(const bool first) const { m_cfg.writeEntry("firstRun", first); } int KisConfig::horizontalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("horizontalSplitLines", 1)); } void KisConfig::setHorizontalSplitLines(const int numberLines) const { m_cfg.writeEntry("horizontalSplitLines", numberLines); } int KisConfig::verticalSplitLines(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("verticalSplitLines", 1)); } void KisConfig::setVerticalSplitLines(const int numberLines) const { m_cfg.writeEntry("verticalSplitLines", numberLines); } bool KisConfig::clicklessSpacePan(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("clicklessSpacePan", true)); } void KisConfig::setClicklessSpacePan(const bool toggle) const { m_cfg.writeEntry("clicklessSpacePan", toggle); } bool KisConfig::hideDockersFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideDockersFullScreen", true)); } void KisConfig::setHideDockersFullscreen(const bool value) const { m_cfg.writeEntry("hideDockersFullScreen", value); } bool KisConfig::showDockers(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showDockers", true)); } void KisConfig::setShowDockers(const bool value) const { m_cfg.writeEntry("showDockers", value); } bool KisConfig::showStatusBar(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showStatusBar", true)); } void KisConfig::setShowStatusBar(const bool value) const { m_cfg.writeEntry("showStatusBar", value); } bool KisConfig::hideMenuFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideMenuFullScreen", true)); } void KisConfig::setHideMenuFullscreen(const bool value) const { m_cfg.writeEntry("hideMenuFullScreen", value); } bool KisConfig::hideScrollbarsFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideScrollbarsFullScreen", true)); } void KisConfig::setHideScrollbarsFullscreen(const bool value) const { m_cfg.writeEntry("hideScrollbarsFullScreen", value); } bool KisConfig::hideStatusbarFullscreen(bool defaultValue) const { return (defaultValue ? true: m_cfg.readEntry("hideStatusbarFullScreen", true)); } void KisConfig::setHideStatusbarFullscreen(const bool value) const { m_cfg.writeEntry("hideStatusbarFullScreen", value); } bool KisConfig::hideTitlebarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideTitleBarFullscreen", true)); } void KisConfig::setHideTitlebarFullscreen(const bool value) const { m_cfg.writeEntry("hideTitleBarFullscreen", value); } bool KisConfig::hideToolbarFullscreen(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("hideToolbarFullscreen", true)); } void KisConfig::setHideToolbarFullscreen(const bool value) const { m_cfg.writeEntry("hideToolbarFullscreen", value); } bool KisConfig::fullscreenMode(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("fullscreenMode", true)); } void KisConfig::setFullscreenMode(const bool value) const { m_cfg.writeEntry("fullscreenMode", value); } QStringList KisConfig::favoriteCompositeOps(bool defaultValue) const { return (defaultValue ? QStringList() : m_cfg.readEntry("favoriteCompositeOps", QString("normal,erase,multiply,burn,darken,add,dodge,screen,overlay,soft_light_svg,luminize,lighten,saturation,color,divide").split(','))); } void KisConfig::setFavoriteCompositeOps(const QStringList& compositeOps) const { m_cfg.writeEntry("favoriteCompositeOps", compositeOps); } QString KisConfig::exportConfigurationXML(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ExportConfiguration-" + filterId, QString())); } KisPropertiesConfigurationSP KisConfig::exportConfiguration(const QString &filterId, bool defaultValue) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); const QString xmlData = exportConfigurationXML(filterId, defaultValue); cfg->fromXML(xmlData); return cfg; } void KisConfig::setExportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString exportConfig = properties->toXML(); m_cfg.writeEntry("ExportConfiguration-" + filterId, exportConfig); } QString KisConfig::importConfiguration(const QString &filterId, bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("ImportConfiguration-" + filterId, QString())); } void KisConfig::setImportConfiguration(const QString &filterId, KisPropertiesConfigurationSP properties) const { QString importConfig = properties->toXML(); m_cfg.writeEntry("ImportConfiguration-" + filterId, importConfig); } bool KisConfig::useOcio(bool defaultValue) const { #ifdef HAVE_OCIO return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/UseOcio", false)); #else Q_UNUSED(defaultValue); return false; #endif } void KisConfig::setUseOcio(bool useOCIO) const { m_cfg.writeEntry("Krita/Ocio/UseOcio", useOCIO); } int KisConfig::favoritePresets(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("numFavoritePresets", 10)); } void KisConfig::setFavoritePresets(const int value) { m_cfg.writeEntry("numFavoritePresets", value); } bool KisConfig::levelOfDetailEnabled(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("levelOfDetailEnabled", false)); } void KisConfig::setLevelOfDetailEnabled(bool value) { m_cfg.writeEntry("levelOfDetailEnabled", value); } KisOcioConfiguration KisConfig::ocioConfiguration(bool defaultValue) const { KisOcioConfiguration cfg; if (!defaultValue) { cfg.mode = (KisOcioConfiguration::Mode)m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", 0); cfg.configurationPath = m_cfg.readEntry("Krita/Ocio/OcioConfigPath", QString()); cfg.lutPath = m_cfg.readEntry("Krita/Ocio/OcioLutPath", QString()); cfg.inputColorSpace = m_cfg.readEntry("Krita/Ocio/InputColorSpace", QString()); cfg.displayDevice = m_cfg.readEntry("Krita/Ocio/DisplayDevice", QString()); cfg.displayView = m_cfg.readEntry("Krita/Ocio/DisplayView", QString()); cfg.look = m_cfg.readEntry("Krita/Ocio/DisplayLook", QString()); } return cfg; } void KisConfig::setOcioConfiguration(const KisOcioConfiguration &cfg) { m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) cfg.mode); m_cfg.writeEntry("Krita/Ocio/OcioConfigPath", cfg.configurationPath); m_cfg.writeEntry("Krita/Ocio/OcioLutPath", cfg.lutPath); m_cfg.writeEntry("Krita/Ocio/InputColorSpace", cfg.inputColorSpace); m_cfg.writeEntry("Krita/Ocio/DisplayDevice", cfg.displayDevice); m_cfg.writeEntry("Krita/Ocio/DisplayView", cfg.displayView); m_cfg.writeEntry("Krita/Ocio/DisplayLook", cfg.look); } KisConfig::OcioColorManagementMode KisConfig::ocioColorManagementMode(bool defaultValue) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it return (OcioColorManagementMode)(defaultValue ? INTERNAL : m_cfg.readEntry("Krita/Ocio/OcioColorManagementMode", (int) INTERNAL)); } void KisConfig::setOcioColorManagementMode(OcioColorManagementMode mode) const { // FIXME: this option duplicates ocioConfiguration(), please deprecate it m_cfg.writeEntry("Krita/Ocio/OcioColorManagementMode", (int) mode); } int KisConfig::ocioLutEdgeSize(bool defaultValue) const { return (defaultValue ? 64 : m_cfg.readEntry("Krita/Ocio/LutEdgeSize", 64)); } void KisConfig::setOcioLutEdgeSize(int value) { m_cfg.writeEntry("Krita/Ocio/LutEdgeSize", value); } bool KisConfig::ocioLockColorVisualRepresentation(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("Krita/Ocio/OcioLockColorVisualRepresentation", false)); } void KisConfig::setOcioLockColorVisualRepresentation(bool value) { m_cfg.writeEntry("Krita/Ocio/OcioLockColorVisualRepresentation", value); } QString KisConfig::defaultPalette(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("defaultPalette", "Default")); } void KisConfig::setDefaultPalette(const QString& name) const { m_cfg.writeEntry("defaultPalette", name); } QString KisConfig::toolbarSlider(int sliderNumber, bool defaultValue) const { QString def = "flow"; if (sliderNumber == 1) { def = "opacity"; } if (sliderNumber == 2) { def = "size"; } return (defaultValue ? def : m_cfg.readEntry(QString("toolbarslider_%1").arg(sliderNumber), def)); } void KisConfig::setToolbarSlider(int sliderNumber, const QString &slider) { m_cfg.writeEntry(QString("toolbarslider_%1").arg(sliderNumber), slider); } int KisConfig::layerThumbnailSize(bool defaultValue) const { return (defaultValue ? 20 : m_cfg.readEntry("layerThumbnailSize", 20)); } void KisConfig::setLayerThumbnailSize(int size) { m_cfg.writeEntry("layerThumbnailSize", size); } bool KisConfig::sliderLabels(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("sliderLabels", true)); } void KisConfig::setSliderLabels(bool enabled) { m_cfg.writeEntry("sliderLabels", enabled); } QString KisConfig::currentInputProfile(bool defaultValue) const { return (defaultValue ? QString() : m_cfg.readEntry("currentInputProfile", QString())); } void KisConfig::setCurrentInputProfile(const QString& name) { m_cfg.writeEntry("currentInputProfile", name); } bool KisConfig::useSystemMonitorProfile(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("ColorManagement/UseSystemMonitorProfile", false)); } void KisConfig::setUseSystemMonitorProfile(bool _useSystemMonitorProfile) const { m_cfg.writeEntry("ColorManagement/UseSystemMonitorProfile", _useSystemMonitorProfile); } bool KisConfig::presetStripVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("presetStripVisible", true)); } void KisConfig::setPresetStripVisible(bool visible) { m_cfg.writeEntry("presetStripVisible", visible); } bool KisConfig::scratchpadVisible(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("scratchpadVisible", true)); } void KisConfig::setScratchpadVisible(bool visible) { m_cfg.writeEntry("scratchpadVisible", visible); } bool KisConfig::showSingleChannelAsColor(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("showSingleChannelAsColor", false)); } void KisConfig::setShowSingleChannelAsColor(bool asColor) { m_cfg.writeEntry("showSingleChannelAsColor", asColor); } bool KisConfig::hidePopups(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("hidePopups", false)); } void KisConfig::setHidePopups(bool hidepopups) { m_cfg.writeEntry("hidePopups", hidepopups); } int KisConfig::numDefaultLayers(bool defaultValue) const { return (defaultValue ? 2 : m_cfg.readEntry("NumberOfLayersForNewImage", 2)); } void KisConfig::setNumDefaultLayers(int num) { m_cfg.writeEntry("NumberOfLayersForNewImage", num); } quint8 KisConfig::defaultBackgroundOpacity(bool defaultValue) const { return (defaultValue ? (int)OPACITY_OPAQUE_U8 : m_cfg.readEntry("BackgroundOpacityForNewImage", (int)OPACITY_OPAQUE_U8)); } void KisConfig::setDefaultBackgroundOpacity(quint8 value) { m_cfg.writeEntry("BackgroundOpacityForNewImage", (int)value); } QColor KisConfig::defaultBackgroundColor(bool defaultValue) const { return (defaultValue ? QColor(Qt::white) : m_cfg.readEntry("BackgroundColorForNewImage", QColor(Qt::white))); } void KisConfig::setDefaultBackgroundColor(const QColor &value) { m_cfg.writeEntry("BackgroundColorForNewImage", value); } KisConfig::BackgroundStyle KisConfig::defaultBackgroundStyle(bool defaultValue) const { return (KisConfig::BackgroundStyle)(defaultValue ? RASTER_LAYER : m_cfg.readEntry("BackgroundStyleForNewImage", (int)RASTER_LAYER)); } void KisConfig::setDefaultBackgroundStyle(KisConfig::BackgroundStyle value) { m_cfg.writeEntry("BackgroundStyleForNewImage", (int)value); } int KisConfig::lineSmoothingType(bool defaultValue) const { return (defaultValue ? 1 : m_cfg.readEntry("LineSmoothingType", 1)); } void KisConfig::setLineSmoothingType(int value) { m_cfg.writeEntry("LineSmoothingType", value); } qreal KisConfig::lineSmoothingDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDistance", 50.0)); } void KisConfig::setLineSmoothingDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDistance", value); } qreal KisConfig::lineSmoothingTailAggressiveness(bool defaultValue) const { return (defaultValue ? 0.15 : m_cfg.readEntry("LineSmoothingTailAggressiveness", 0.15)); } void KisConfig::setLineSmoothingTailAggressiveness(qreal value) { m_cfg.writeEntry("LineSmoothingTailAggressiveness", value); } bool KisConfig::lineSmoothingSmoothPressure(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("LineSmoothingSmoothPressure", false)); } void KisConfig::setLineSmoothingSmoothPressure(bool value) { m_cfg.writeEntry("LineSmoothingSmoothPressure", value); } bool KisConfig::lineSmoothingScalableDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingScalableDistance", true)); } void KisConfig::setLineSmoothingScalableDistance(bool value) { m_cfg.writeEntry("LineSmoothingScalableDistance", value); } qreal KisConfig::lineSmoothingDelayDistance(bool defaultValue) const { return (defaultValue ? 50.0 : m_cfg.readEntry("LineSmoothingDelayDistance", 50.0)); } void KisConfig::setLineSmoothingDelayDistance(qreal value) { m_cfg.writeEntry("LineSmoothingDelayDistance", value); } bool KisConfig::lineSmoothingUseDelayDistance(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingUseDelayDistance", true)); } void KisConfig::setLineSmoothingUseDelayDistance(bool value) { m_cfg.writeEntry("LineSmoothingUseDelayDistance", value); } bool KisConfig::lineSmoothingFinishStabilizedCurve(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingFinishStabilizedCurve", true)); } void KisConfig::setLineSmoothingFinishStabilizedCurve(bool value) { m_cfg.writeEntry("LineSmoothingFinishStabilizedCurve", value); } bool KisConfig::lineSmoothingStabilizeSensors(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("LineSmoothingStabilizeSensors", true)); } void KisConfig::setLineSmoothingStabilizeSensors(bool value) { m_cfg.writeEntry("LineSmoothingStabilizeSensors", value); } int KisConfig::tabletEventsDelay(bool defaultValue) const { return (defaultValue ? 10 : m_cfg.readEntry("tabletEventsDelay", 10)); } void KisConfig::setTabletEventsDelay(int value) { m_cfg.writeEntry("tabletEventsDelay", value); } bool KisConfig::trackTabletEventLatency(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("trackTabletEventLatency", false)); } void KisConfig::setTrackTabletEventLatency(bool value) { m_cfg.writeEntry("trackTabletEventLatency", value); } bool KisConfig::testingAcceptCompressedTabletEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingAcceptCompressedTabletEvents", false)); } void KisConfig::setTestingAcceptCompressedTabletEvents(bool value) { m_cfg.writeEntry("testingAcceptCompressedTabletEvents", value); } bool KisConfig::shouldEatDriverShortcuts(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("shouldEatDriverShortcuts", false)); } bool KisConfig::testingCompressBrushEvents(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("testingCompressBrushEvents", false)); } void KisConfig::setTestingCompressBrushEvents(bool value) { m_cfg.writeEntry("testingCompressBrushEvents", value); } int KisConfig::workaroundX11SmoothPressureSteps(bool defaultValue) const { return (defaultValue ? 0 : m_cfg.readEntry("workaroundX11SmoothPressureSteps", 0)); } bool KisConfig::showCanvasMessages(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("showOnCanvasMessages", true)); } void KisConfig::setShowCanvasMessages(bool show) { m_cfg.writeEntry("showOnCanvasMessages", show); } bool KisConfig::compressKra(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("compressLayersInKra", false)); } void KisConfig::setCompressKra(bool compress) { m_cfg.writeEntry("compressLayersInKra", compress); } bool KisConfig::toolOptionsInDocker(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("ToolOptionsInDocker", true)); } void KisConfig::setToolOptionsInDocker(bool inDocker) { m_cfg.writeEntry("ToolOptionsInDocker", inDocker); } bool KisConfig::kineticScrollingEnabled(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("KineticScrollingEnabled", true)); } void KisConfig::setKineticScrollingEnabled(bool value) { m_cfg.writeEntry("KineticScrollingEnabled", value); } int KisConfig::kineticScrollingGesture(bool defaultValue) const { - return (defaultValue ? 2 : m_cfg.readEntry("KineticScrollingGesture", 2)); +#ifdef Q_OS_ANDROID + int defaultGesture = 0; // TouchGesture +#else + int defaultGesture = 2; // MiddleMouseButtonGesture +#endif + + return (defaultValue ? defaultGesture : m_cfg.readEntry("KineticScrollingGesture", defaultGesture)); } void KisConfig::setKineticScrollingGesture(int gesture) { m_cfg.writeEntry("KineticScrollingGesture", gesture); } int KisConfig::kineticScrollingSensitivity(bool defaultValue) const { return (defaultValue ? 75 : m_cfg.readEntry("KineticScrollingSensitivity", 75)); } void KisConfig::setKineticScrollingSensitivity(int sensitivity) { m_cfg.writeEntry("KineticScrollingSensitivity", sensitivity); } bool KisConfig::kineticScrollingHiddenScrollbars(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("KineticScrollingHideScrollbar", false)); } void KisConfig::setKineticScrollingHideScrollbars(bool scrollbar) { m_cfg.writeEntry("KineticScrollingHideScrollbar", scrollbar); } const KoColorSpace* KisConfig::customColorSelectorColorSpace(bool defaultValue) const { const KoColorSpace *cs = 0; KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); if (defaultValue || cfg.readEntry("useCustomColorSpace", true)) { KoColorSpaceRegistry* csr = KoColorSpaceRegistry::instance(); QString modelID = cfg.readEntry("customColorSpaceModel", "RGBA"); QString depthID = cfg.readEntry("customColorSpaceDepthID", "U8"); QString profile = cfg.readEntry("customColorSpaceProfile", "sRGB built-in - (lcms internal)"); if (profile == "default") { // qDebug() << "Falling back to default color profile."; profile = "sRGB built-in - (lcms internal)"; } cs = csr->colorSpace(modelID, depthID, profile); } return cs; } void KisConfig::setCustomColorSelectorColorSpace(const KoColorSpace *cs) { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); cfg.writeEntry("useCustomColorSpace", bool(cs)); if(cs) { cfg.writeEntry("customColorSpaceModel", cs->colorModelId().id()); cfg.writeEntry("customColorSpaceDepthID", cs->colorDepthId().id()); cfg.writeEntry("customColorSpaceProfile", cs->profile()->name()); } KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::enableOpenGLFramerateLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableOpenGLFramerateLogging", false)); } void KisConfig::setEnableOpenGLFramerateLogging(bool value) const { m_cfg.writeEntry("enableOpenGLFramerateLogging", value); } bool KisConfig::enableBrushSpeedLogging(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("enableBrushSpeedLogging", false)); } void KisConfig::setEnableBrushSpeedLogging(bool value) const { m_cfg.writeEntry("enableBrushSpeedLogging", value); } void KisConfig::setEnableAmdVectorizationWorkaround(bool value) { m_cfg.writeEntry("amdDisableVectorWorkaround", value); } bool KisConfig::enableAmdVectorizationWorkaround(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("amdDisableVectorWorkaround", false)); } void KisConfig::setDisableAVXOptimizations(bool value) { m_cfg.writeEntry("disableAVXOptimizations", value); } bool KisConfig::disableAVXOptimizations(bool defaultValue) const { return (defaultValue ? false : m_cfg.readEntry("disableAVXOptimizations", false)); } void KisConfig::setAnimationDropFrames(bool value) { bool oldValue = animationDropFrames(); if (value == oldValue) return; m_cfg.writeEntry("animationDropFrames", value); KisConfigNotifier::instance()->notifyDropFramesModeChanged(); } bool KisConfig::animationDropFrames(bool defaultValue) const { return (defaultValue ? true : m_cfg.readEntry("animationDropFrames", true)); } int KisConfig::scrubbingUpdatesDelay(bool defaultValue) const { return (defaultValue ? 30 : m_cfg.readEntry("scrubbingUpdatesDelay", 30)); } void KisConfig::setScrubbingUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingUpdatesDelay", value); } int KisConfig::scrubbingAudioUpdatesDelay(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("scrubbingAudioUpdatesDelay", -1)); } void KisConfig::setScrubbingAudioUpdatesDelay(int value) { m_cfg.writeEntry("scrubbingAudioUpdatesDelay", value); } int KisConfig::audioOffsetTolerance(bool defaultValue) const { return (defaultValue ? -1 : m_cfg.readEntry("audioOffsetTolerance", -1)); } void KisConfig::setAudioOffsetTolerance(int value) { m_cfg.writeEntry("audioOffsetTolerance", value); } bool KisConfig::switchSelectionCtrlAlt(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("switchSelectionCtrlAlt", false); } void KisConfig::setSwitchSelectionCtrlAlt(bool value) { m_cfg.writeEntry("switchSelectionCtrlAlt", value); KisConfigNotifier::instance()->notifyConfigChanged(); } bool KisConfig::convertToImageColorspaceOnImport(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("ConvertToImageColorSpaceOnImport", false); } void KisConfig::setConvertToImageColorspaceOnImport(bool value) { m_cfg.writeEntry("ConvertToImageColorSpaceOnImport", value); } int KisConfig::stabilizerSampleSize(bool defaultValue) const { #ifdef Q_OS_WIN const int defaultSampleSize = 50; #else const int defaultSampleSize = 15; #endif return defaultValue ? defaultSampleSize : m_cfg.readEntry("stabilizerSampleSize", defaultSampleSize); } void KisConfig::setStabilizerSampleSize(int value) { m_cfg.writeEntry("stabilizerSampleSize", value); } bool KisConfig::stabilizerDelayedPaint(bool defaultValue) const { const bool defaultEnabled = true; return defaultValue ? defaultEnabled : m_cfg.readEntry("stabilizerDelayedPaint", defaultEnabled); } void KisConfig::setStabilizerDelayedPaint(bool value) { m_cfg.writeEntry("stabilizerDelayedPaint", value); } bool KisConfig::showBrushHud(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("showBrushHud", false); } void KisConfig::setShowBrushHud(bool value) { m_cfg.writeEntry("showBrushHud", value); } QString KisConfig::brushHudSetting(bool defaultValue) const { QString defaultDoc = "\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; return defaultValue ? defaultDoc : m_cfg.readEntry("brushHudSettings", defaultDoc); } void KisConfig::setBrushHudSetting(const QString &value) const { m_cfg.writeEntry("brushHudSettings", value); } bool KisConfig::calculateAnimationCacheInBackground(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("calculateAnimationCacheInBackground", true); } void KisConfig::setCalculateAnimationCacheInBackground(bool value) { m_cfg.writeEntry("calculateAnimationCacheInBackground", value); } QColor KisConfig::defaultAssistantsColor(bool defaultValue) const { static const QColor defaultColor = QColor(176, 176, 176, 255); return defaultValue ? defaultColor : m_cfg.readEntry("defaultAssistantsColor", defaultColor); } void KisConfig::setDefaultAssistantsColor(const QColor &color) const { m_cfg.writeEntry("defaultAssistantsColor", color); } bool KisConfig::autoSmoothBezierCurves(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("autoSmoothBezierCurves", false); } void KisConfig::setAutoSmoothBezierCurves(bool value) { m_cfg.writeEntry("autoSmoothBezierCurves", value); } bool KisConfig::activateTransformToolAfterPaste(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("activateTransformToolAfterPaste", false); } void KisConfig::setActivateTransformToolAfterPaste(bool value) { m_cfg.writeEntry("activateTransformToolAfterPaste", value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(bool defaultValue) const { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return rootSurfaceFormat(&kritarc, defaultValue); } void KisConfig::setRootSurfaceFormat(KisConfig::RootSurfaceFormat value) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); setRootSurfaceFormat(&kritarc, value); } KisConfig::RootSurfaceFormat KisConfig::rootSurfaceFormat(QSettings *displayrc, bool defaultValue) { QString textValue = "bt709-g22"; if (!defaultValue) { textValue = displayrc->value("rootSurfaceFormat", textValue).toString(); } return textValue == "bt709-g10" ? BT709_G10 : textValue == "bt2020-pq" ? BT2020_PQ : BT709_G22; } void KisConfig::setRootSurfaceFormat(QSettings *displayrc, KisConfig::RootSurfaceFormat value) { const QString textValue = value == BT709_G10 ? "bt709-g10" : value == BT2020_PQ ? "bt2020-pq" : "bt709-g22"; displayrc->setValue("rootSurfaceFormat", textValue); } bool KisConfig::useZip64(bool defaultValue) const { return defaultValue ? false : m_cfg.readEntry("UseZip64", false); } void KisConfig::setUseZip64(bool value) { m_cfg.writeEntry("UseZip64", value); } bool KisConfig::convertLayerColorSpaceInProperties(bool defaultValue) const { return defaultValue ? true : m_cfg.readEntry("convertLayerColorSpaceInProperties", true); } void KisConfig::setConvertLayerColorSpaceInProperties(bool value) { m_cfg.writeEntry("convertLayerColorSpaceInProperties", value); } #include #include void KisConfig::writeKoColor(const QString& name, const KoColor& color) const { QDomDocument doc = QDomDocument(name); QDomElement el = doc.createElement(name); doc.appendChild(el); color.toXML(doc, el); m_cfg.writeEntry(name, doc.toString()); } //ported from kispropertiesconfig. KoColor KisConfig::readKoColor(const QString& name, const KoColor& _color) const { QDomDocument doc; KoColor color = _color; if (!m_cfg.readEntry(name).isNull()) { doc.setContent(m_cfg.readEntry(name)); QDomElement e = doc.documentElement().firstChild().toElement(); color = KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } else { QString blackColor = "\n\n \n\n"; doc.setContent(blackColor); QDomElement e = doc.documentElement().firstChild().toElement(); color = KoColor::fromXML(e, Integer16BitsColorDepthID.id()); } return color; } diff --git a/libs/ui/kis_selection_manager.cc b/libs/ui/kis_selection_manager.cc index 86e63f5bf2..92f4edf6b7 100644 --- a/libs/ui/kis_selection_manager.cc +++ b/libs/ui/kis_selection_manager.cc @@ -1,760 +1,762 @@ /* * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2007 Sven Langkamp * * The outline algorithm uses the limn algorithm of fontutils by * Karl Berry and Kathryn Hargreaves * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_selection_manager.h" #include #include #include #include #include #include #include #include #include #include #include "KoCanvasController.h" #include "KoChannelInfo.h" #include "KoIntegerMaths.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_adjustment_layer.h" #include "kis_node_manager.h" #include "canvas/kis_canvas2.h" #include "kis_config.h" #include "kis_convolution_painter.h" #include "kis_convolution_kernel.h" #include "kis_debug.h" #include "kis_fill_painter.h" #include "kis_group_layer.h" #include "kis_layer.h" #include "kis_statusbar.h" #include "kis_paint_device.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_transaction.h" #include "kis_selection.h" #include "kis_types.h" #include "kis_canvas_resource_provider.h" #include "kis_undo_adapter.h" #include "kis_pixel_selection.h" #include "flake/kis_shape_selection.h" #include "commands/kis_selection_commands.h" #include "kis_selection_mask.h" #include "flake/kis_shape_layer.h" #include "kis_selection_decoration.h" #include "canvas/kis_canvas_decoration.h" #include "kis_node_commands_adapter.h" #include "kis_iterator_ng.h" #include "kis_clipboard.h" #include "KisViewManager.h" #include "kis_selection_filters.h" #include "kis_figure_painting_tool_helper.h" #include "KisView.h" #include "dialogs/kis_dlg_stroke_selection_properties.h" #include "actions/kis_selection_action_factories.h" #include "actions/KisPasteActionFactories.h" #include "kis_action.h" #include "kis_action_manager.h" #include "operations/kis_operation_configuration.h" //new #include "kis_node_query_path.h" #include "kis_tool_shape.h" KisSelectionManager::KisSelectionManager(KisViewManager * view) : m_view(view), m_doc(0), m_imageView(0), m_adapter(new KisNodeCommandsAdapter(view)), m_copy(0), m_copyMerged(0), m_cut(0), m_paste(0), m_pasteNew(0), m_cutToNewLayer(0), m_selectAll(0), m_deselect(0), m_clear(0), m_reselect(0), m_invert(0), m_copyToNewLayer(0), m_fillForegroundColor(0), m_fillBackgroundColor(0), m_fillPattern(0), m_imageResizeToSelection(0), m_selectionDecoration(0) { m_clipboard = KisClipboard::instance(); } KisSelectionManager::~KisSelectionManager() { } void KisSelectionManager::setup(KisActionManager* actionManager) { m_cut = actionManager->createStandardAction(KStandardAction::Cut, this, SLOT(cut())); m_copy = actionManager->createStandardAction(KStandardAction::Copy, this, SLOT(copy())); m_paste = actionManager->createStandardAction(KStandardAction::Paste, this, SLOT(paste())); KisAction *action = actionManager->createAction("copy_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(copySharp())); action = actionManager->createAction("cut_sharp"); connect(action, SIGNAL(triggered()), this, SLOT(cutSharp())); m_pasteNew = actionManager->createAction("paste_new"); connect(m_pasteNew, SIGNAL(triggered()), this, SLOT(pasteNew())); m_pasteAt = actionManager->createAction("paste_at"); connect(m_pasteAt, SIGNAL(triggered()), this, SLOT(pasteAt())); m_pasteAsReference = actionManager->createAction("paste_as_reference"); connect(m_pasteAsReference, SIGNAL(triggered()), this, SLOT(pasteAsReference())); m_copyMerged = actionManager->createAction("copy_merged"); connect(m_copyMerged, SIGNAL(triggered()), this, SLOT(copyMerged())); m_selectAll = actionManager->createAction("select_all"); connect(m_selectAll, SIGNAL(triggered()), this, SLOT(selectAll())); m_deselect = actionManager->createAction("deselect"); connect(m_deselect, SIGNAL(triggered()), this, SLOT(deselect())); m_clear = actionManager->createAction("clear"); connect(m_clear, SIGNAL(triggered()), SLOT(clear())); m_reselect = actionManager->createAction("reselect"); connect(m_reselect, SIGNAL(triggered()), this, SLOT(reselect())); m_invert = actionManager->createAction("invert_selection"); m_invert->setOperationID("invertselection"); actionManager->registerOperation(new KisInvertSelectionOperation); m_copyToNewLayer = actionManager->createAction("copy_selection_to_new_layer"); connect(m_copyToNewLayer, SIGNAL(triggered()), this, SLOT(copySelectionToNewLayer())); m_cutToNewLayer = actionManager->createAction("cut_selection_to_new_layer"); connect(m_cutToNewLayer, SIGNAL(triggered()), this, SLOT(cutToNewLayer())); m_fillForegroundColor = actionManager->createAction("fill_selection_foreground_color"); connect(m_fillForegroundColor, SIGNAL(triggered()), this, SLOT(fillForegroundColor())); m_fillBackgroundColor = actionManager->createAction("fill_selection_background_color"); connect(m_fillBackgroundColor, SIGNAL(triggered()), this, SLOT(fillBackgroundColor())); m_fillPattern = actionManager->createAction("fill_selection_pattern"); connect(m_fillPattern, SIGNAL(triggered()), this, SLOT(fillPattern())); m_fillForegroundColorOpacity = actionManager->createAction("fill_selection_foreground_color_opacity"); connect(m_fillForegroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillForegroundColorOpacity())); m_fillBackgroundColorOpacity = actionManager->createAction("fill_selection_background_color_opacity"); connect(m_fillBackgroundColorOpacity, SIGNAL(triggered()), this, SLOT(fillBackgroundColorOpacity())); m_fillPatternOpacity = actionManager->createAction("fill_selection_pattern_opacity"); connect(m_fillPatternOpacity, SIGNAL(triggered()), this, SLOT(fillPatternOpacity())); m_strokeShapes = actionManager->createAction("stroke_shapes"); connect(m_strokeShapes, SIGNAL(triggered()), this, SLOT(paintSelectedShapes())); m_toggleDisplaySelection = actionManager->createAction("toggle_display_selection"); connect(m_toggleDisplaySelection, SIGNAL(triggered()), this, SLOT(toggleDisplaySelection())); m_toggleDisplaySelection->setChecked(true); m_imageResizeToSelection = actionManager->createAction("resizeimagetoselection"); connect(m_imageResizeToSelection, SIGNAL(triggered()), this, SLOT(imageResizeToSelection())); action = actionManager->createAction("edit_selection"); connect(action, SIGNAL(triggered()), SLOT(editSelection())); action = actionManager->createAction("convert_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToVectorSelection())); action = actionManager->createAction("convert_to_raster_selection"); connect(action, SIGNAL(triggered()), SLOT(convertToRasterSelection())); action = actionManager->createAction("convert_shapes_to_vector_selection"); connect(action, SIGNAL(triggered()), SLOT(convertShapesToVectorSelection())); action = actionManager->createAction("convert_selection_to_shape"); connect(action, SIGNAL(triggered()), SLOT(convertToShape())); m_toggleSelectionOverlayMode = actionManager->createAction("toggle-selection-overlay-mode"); connect(m_toggleSelectionOverlayMode, SIGNAL(triggered()), SLOT(slotToggleSelectionDecoration())); m_strokeSelected = actionManager->createAction("stroke_selection"); connect(m_strokeSelected, SIGNAL(triggered()), SLOT(slotStrokeSelection())); QClipboard *cb = QApplication::clipboard(); connect(cb, SIGNAL(dataChanged()), SLOT(clipboardDataChanged())); } void KisSelectionManager::setView(QPointerimageView) { if (m_imageView && m_imageView->canvasBase()) { disconnect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), this, SLOT(clipboardDataChanged())); KoSelection *selection = m_imageView->canvasBase()->globalShapeManager()->selection(); selection->disconnect(this, SLOT(shapeSelectionChanged())); KisSelectionDecoration *decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (decoration) { disconnect(SIGNAL(currentSelectionChanged()), decoration); } m_imageView->image()->undoAdapter()->disconnect(this); m_selectionDecoration = 0; } m_imageView = imageView; if (m_imageView) { connect(m_imageView->canvasBase()->selectedShapesProxy(), SIGNAL(selectionChanged()), this, SLOT(shapeSelectionChanged()), Qt::UniqueConnection); KisSelectionDecoration* decoration = qobject_cast(m_imageView->canvasBase()->decoration("selection").data()); if (!decoration) { decoration = new KisSelectionDecoration(m_imageView); decoration->setVisible(true); m_imageView->canvasBase()->addDecoration(decoration); } m_selectionDecoration = decoration; connect(this, SIGNAL(currentSelectionChanged()), decoration, SLOT(selectionChanged())); connect(m_imageView->image()->undoAdapter(), SIGNAL(selectionChanged()), SLOT(selectionChanged())); connect(m_imageView->canvasBase()->toolProxy(), SIGNAL(toolChanged(QString)), SLOT(clipboardDataChanged())); } } void KisSelectionManager::clipboardDataChanged() { m_view->updateGUI(); } bool KisSelectionManager::havePixelsSelected() { KisSelectionSP activeSelection = m_view->selection(); return activeSelection && !activeSelection->selectedRect().isEmpty(); } bool KisSelectionManager::havePixelsInClipboard() { return m_clipboard->hasClip(); } bool KisSelectionManager::haveShapesSelected() { if (m_view && m_view->canvasBase()) { return m_view->canvasBase()->selectedShapesProxy()->selection()->count() > 0; } return false; } bool KisSelectionManager::haveShapesInClipboard() { KoSvgPaste paste; return paste.hasShapes(); } bool KisSelectionManager::haveAnySelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection(); } bool KisSelectionManager::haveShapeSelectionWithShapes() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasShapeSelection(); } bool KisSelectionManager::haveRasterSelectionWithPixels() { KisSelectionSP selection = m_view->selection(); return selection && selection->hasPixelSelection() && !selection->hasShapeSelection(); } void KisSelectionManager::updateGUI() { Q_ASSERT(m_view); Q_ASSERT(m_clipboard); if (!m_view || !m_clipboard) return; bool havePixelsSelected = this->havePixelsSelected(); bool havePixelsInClipboard = this->havePixelsInClipboard(); bool haveShapesSelected = this->haveShapesSelected(); bool haveShapesInClipboard = this->haveShapesInClipboard(); bool haveDevice = m_view->activeDevice(); KisLayerSP activeLayer = m_view->activeLayer(); KisImageWSP image = activeLayer ? activeLayer->image() : 0; bool canReselect = image && image->canReselectGlobalSelection(); bool canDeselect = image && image->globalSelection(); m_clear->setEnabled(haveDevice || havePixelsSelected || haveShapesSelected); m_cut->setEnabled(havePixelsSelected || haveShapesSelected); m_copy->setEnabled(havePixelsSelected || haveShapesSelected); m_paste->setEnabled(havePixelsInClipboard || haveShapesInClipboard); m_pasteAt->setEnabled(havePixelsInClipboard || haveShapesInClipboard); // FIXME: how about pasting shapes? m_pasteNew->setEnabled(havePixelsInClipboard); m_pasteAsReference->setEnabled(haveDevice); m_selectAll->setEnabled(true); m_deselect->setEnabled(canDeselect); m_reselect->setEnabled(canReselect); // m_load->setEnabled(true); // m_save->setEnabled(havePixelsSelected); updateStatusBar(); emit signalUpdateGUI(); } void KisSelectionManager::updateStatusBar() { if (m_view && m_view->statusBar()) { m_view->statusBar()->setSelection(m_view->image()); } } void KisSelectionManager::selectionChanged() { m_view->updateGUI(); emit currentSelectionChanged(); } void KisSelectionManager::cut() { KisCutCopyActionFactory factory; factory.run(true, false, m_view); } void KisSelectionManager::copy() { KisCutCopyActionFactory factory; factory.run(false, false, m_view); } void KisSelectionManager::cutSharp() { KisCutCopyActionFactory factory; factory.run(true, true, m_view); } void KisSelectionManager::copySharp() { KisCutCopyActionFactory factory; factory.run(false, true, m_view); } void KisSelectionManager::copyMerged() { KisCopyMergedActionFactory factory; factory.run(m_view); } void KisSelectionManager::paste() { KisPasteActionFactory factory; factory.run(false, m_view); } void KisSelectionManager::pasteAt() { KisPasteActionFactory factory; factory.run(true, m_view); } void KisSelectionManager::pasteAsReference() { KisPasteReferenceActionFactory factory; factory.run(m_view); } void KisSelectionManager::pasteNew() { KisPasteNewActionFactory factory; factory.run(m_view); } void KisSelectionManager::selectAll() { KisSelectAllActionFactory factory; factory.run(m_view); } void KisSelectionManager::deselect() { KisDeselectActionFactory factory; factory.run(m_view); } void KisSelectionManager::invert() { if(m_invert) m_invert->trigger(); } void KisSelectionManager::reselect() { KisReselectActionFactory factory; factory.run(m_view); } #include #include void KisSelectionManager::editSelection() { KisSelectionSP selection = m_view->selection(); if (!selection) return; KisAction *action = m_view->actionManager()->actionByName("show-global-selection-mask"); KIS_SAFE_ASSERT_RECOVER_RETURN(action); if (!action->isChecked()) { action->setChecked(true); emit action->toggled(true); emit action->triggered(true); } KisNodeSP node = selection->parentNode(); KIS_SAFE_ASSERT_RECOVER_RETURN(node); m_view->nodeManager()->slotNonUiActivatedNode(node); if (selection->hasShapeSelection()) { KisShapeSelection *shapeSelection = dynamic_cast(selection->shapeSelection()); KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection); KoToolManager::instance()->switchToolRequested(KoInteractionTool_ID); QList shapes = shapeSelection->shapes(); if (shapes.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "no shapes"); return; } Q_FOREACH (KoShape *shape, shapes) { m_view->canvasBase()->selectedShapesProxy()->selection()->select(shape); } } else { KoToolManager::instance()->switchToolRequested("KisToolTransform"); } } void KisSelectionManager::convertToVectorSelection() { KisSelectionToVectorActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToRasterSelection() { KisSelectionToRasterActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertShapesToVectorSelection() { KisShapesToVectorSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::convertToShape() { KisSelectionToShapeActionFactory factory; factory.run(m_view); } void KisSelectionManager::clear() { KisClearActionFactory factory; factory.run(m_view); } void KisSelectionManager::fillForegroundColor() { KisFillActionFactory factory; factory.run("fg", m_view); } void KisSelectionManager::fillBackgroundColor() { KisFillActionFactory factory; factory.run("bg", m_view); } void KisSelectionManager::fillPattern() { KisFillActionFactory factory; factory.run("pattern", m_view); } void KisSelectionManager::fillForegroundColorOpacity() { KisFillActionFactory factory; factory.run("fg_opacity", m_view); } void KisSelectionManager::fillBackgroundColorOpacity() { KisFillActionFactory factory; factory.run("bg_opacity", m_view); } void KisSelectionManager::fillPatternOpacity() { KisFillActionFactory factory; factory.run("pattern_opacity", m_view); } void KisSelectionManager::copySelectionToNewLayer() { copy(); paste(); } void KisSelectionManager::cutToNewLayer() { cut(); paste(); } void KisSelectionManager::toggleDisplaySelection() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); m_selectionDecoration->toggleVisibility(); m_toggleDisplaySelection->blockSignals(true); m_toggleDisplaySelection->setChecked(m_selectionDecoration->visible()); m_toggleDisplaySelection->blockSignals(false); emit displaySelectionChanged(); } bool KisSelectionManager::displaySelection() { return m_toggleDisplaySelection->isChecked(); } void KisSelectionManager::shapeSelectionChanged() { KoShapeManager* shapeManager = m_view->canvasBase()->globalShapeManager(); KoSelection * selection = shapeManager->selection(); QList selectedShapes = selection->selectedShapes(); KoShapeStrokeSP border(new KoShapeStroke(0, Qt::lightGray)); Q_FOREACH (KoShape* shape, shapeManager->shapes()) { if (dynamic_cast(shape->parent())) { if (selectedShapes.contains(shape)) shape->setStroke(border); else shape->setStroke(KoShapeStrokeSP()); } } m_view->updateGUI(); } void KisSelectionManager::imageResizeToSelection() { KisImageResizeToSelectionActionFactory factory; factory.run(m_view); } void KisSelectionManager::paintSelectedShapes() { KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; QList shapes = m_view->canvasBase()->shapeManager()->selection()->selectedShapes(); KisPaintLayerSP paintLayer = new KisPaintLayer(image, i18n("Stroked Shapes"), OPACITY_OPAQUE_U8); KUndo2MagicString actionName = kundo2_i18n("Stroke Shapes"); m_adapter->beginMacro(actionName); m_adapter->addNode(paintLayer.data(), layer->parent().data(), layer.data()); KisFigurePaintingToolHelper helper(actionName, image, paintLayer.data(), m_view->canvasResourceProvider()->resourceManager(), KisToolShapeUtils::StrokeStyleForeground, KisToolShapeUtils::FillStyleNone); Q_FOREACH (KoShape* shape, shapes) { QTransform matrix = shape->absoluteTransformation(0) * QTransform::fromScale(image->xRes(), image->yRes()); QPainterPath mapedOutline = matrix.map(shape->outline()); helper.paintPainterPath(mapedOutline); } m_adapter->endMacro(); } void KisSelectionManager::slotToggleSelectionDecoration() { KIS_ASSERT_RECOVER_RETURN(m_selectionDecoration); KisSelectionDecoration::Mode mode = m_selectionDecoration->mode() ? KisSelectionDecoration::Ants : KisSelectionDecoration::Mask; m_selectionDecoration->setMode(mode); emit displaySelectionChanged(); } bool KisSelectionManager::showSelectionAsMask() const { if (m_selectionDecoration) { return m_selectionDecoration->mode() == KisSelectionDecoration::Mask; } return false; } void KisSelectionManager::slotStrokeSelection() { KisImageWSP image = m_view->image(); if (!image ) { return; } KisNodeSP currentNode = m_view->canvasResourceProvider()->resourceManager()->resource(KisCanvasResourceProvider::CurrentKritaNode).value(); bool isVectorLayer = false; if (currentNode->inherits("KisShapeLayer")) { isVectorLayer = true; } + qDebug() << "isVectorLayer" << isVectorLayer; + QPointer dlg = new KisDlgStrokeSelection(image, m_view, isVectorLayer); if (dlg->exec() == QDialog::Accepted) { StrokeSelectionOptions params = dlg->getParams(); if (params.brushSelected){ KisStrokeBrushSelectionActionFactory factory; factory.run(m_view, params); } else { KisStrokeSelectionActionFactory factory; factory.run(m_view, params); } } delete dlg; } #include "kis_image_barrier_locker.h" #include "kis_selection_tool_helper.h" void KisSelectionManager::selectOpaqueOnNode(KisNodeSP node, SelectionAction action) { KisImageSP image = m_view->image(); if (!m_view->blockUntilOperationsFinished(image)) { return; } KUndo2MagicString actionName; KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection()); KisCanvas2 *canvas = m_view->canvasBase(); { KisImageBarrierLocker locker(image); KisPaintDeviceSP device = node->projection(); if (!device) device = node->paintDevice(); if (!device) device = node->original(); if (!device) return; QRect rc = device->exactBounds(); if (rc.isEmpty()) return; KIS_ASSERT_RECOVER_RETURN(canvas); /** * If there is nothing selected, just create a new selection */ if (!canvas->imageView()->selection()) { action = SELECTION_REPLACE; } switch (action) { case SELECTION_ADD: actionName = kundo2_i18n("Select Opaque (Add)"); break; case SELECTION_SUBTRACT: actionName = kundo2_i18n("Select Opaque (Subtract)"); break; case SELECTION_INTERSECT: actionName = kundo2_i18n("Select Opaque (Intersect)"); break; case SELECTION_SYMMETRICDIFFERENCE: actionName = kundo2_i18n("Select Opaque (Symmetric Difference)"); break; default: actionName = kundo2_i18n("Select Opaque"); break; } qint32 x, y, w, h; rc.getRect(&x, &y, &w, &h); const KoColorSpace * cs = device->colorSpace(); KisHLineConstIteratorSP deviter = device->createHLineConstIteratorNG(x, y, w); KisHLineIteratorSP selIter = tmpSel ->createHLineIteratorNG(x, y, w); for (int row = y; row < h + y; ++row) { do { *selIter->rawData() = cs->opacityU8(deviter->oldRawData()); } while (deviter->nextPixel() && selIter->nextPixel()); deviter->nextRow(); selIter->nextRow(); } } KisSelectionToolHelper helper(canvas, actionName); tmpSel->invalidateOutlineCache(); helper.selectPixelSelection(tmpSel, action); } diff --git a/libs/ui/opengl/kis_opengl.cpp b/libs/ui/opengl/kis_opengl.cpp index bcfa3a7c9c..32171dc3c1 100644 --- a/libs/ui/opengl/kis_opengl.cpp +++ b/libs/ui/opengl/kis_opengl.cpp @@ -1,913 +1,913 @@ /* * Copyright (c) 2007 Adrian Page * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "opengl/kis_opengl.h" #include "opengl/kis_opengl_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisOpenGLModeProber.h" #include #include #include "kis_assert.h" #include #include #include #ifndef GL_RENDERER # define GL_RENDERER 0x1F01 #endif using namespace KisOpenGLPrivate; namespace { // config option, set manually by main() bool g_isDebugSynchronous = false; bool g_sanityDefaultFormatIsSet = false; boost::optional openGLCheckResult; bool g_needsFenceWorkaround = false; bool g_needsPixmapCacheWorkaround = false; QString g_surfaceFormatDetectionLog; QString g_debugText("OpenGL Info\n **OpenGL not initialized**"); QVector g_openglWarningStrings; KisOpenGL::OpenGLRenderers g_supportedRenderers; KisOpenGL::OpenGLRenderer g_rendererPreferredByQt; void overrideSupportedRenderers(KisOpenGL::OpenGLRenderers supportedRenderers, KisOpenGL::OpenGLRenderer preferredByQt) { g_supportedRenderers = supportedRenderers; g_rendererPreferredByQt = preferredByQt; } void openglOnMessageLogged(const QOpenGLDebugMessage& debugMessage) { qDebug() << "OpenGL:" << debugMessage; } KisOpenGL::OpenGLRenderer getRendererFromProbeResult(KisOpenGLModeProber::Result info) { KisOpenGL::OpenGLRenderer result = KisOpenGL::RendererDesktopGL; if (info.isOpenGLES()) { const QString rendererString = info.rendererString().toLower(); if (rendererString.contains("basic render driver") || rendererString.contains("software")) { result = KisOpenGL::RendererSoftware; } else { result = KisOpenGL::RendererOpenGLES; } } return result; } } KisOpenGLPrivate::OpenGLCheckResult::OpenGLCheckResult(QOpenGLContext &context) { if (!context.isValid()) { return; } QOpenGLFunctions *funcs = context.functions(); // funcs is ready to be used m_rendererString = QString(reinterpret_cast(funcs->glGetString(GL_RENDERER))); m_driverVersionString = QString(reinterpret_cast(funcs->glGetString(GL_VERSION))); m_glMajorVersion = context.format().majorVersion(); m_glMinorVersion = context.format().minorVersion(); m_supportsDeprecatedFunctions = (context.format().options() & QSurfaceFormat::DeprecatedFunctions); m_isOpenGLES = context.isOpenGLES(); } void KisOpenGLPrivate::appendOpenGLWarningString(KLocalizedString warning) { g_openglWarningStrings << warning; } void KisOpenGLPrivate::overrideOpenGLWarningString(QVector warnings) { g_openglWarningStrings = warnings; } void KisOpenGL::initialize() { if (openGLCheckResult) return; KIS_SAFE_ASSERT_RECOVER_NOOP(g_sanityDefaultFormatIsSet); KisOpenGL::RendererConfig config; config.format = QSurfaceFormat::defaultFormat(); openGLCheckResult = KisOpenGLModeProber::instance()->probeFormat(config, false); g_debugText.clear(); QDebug debugOut(&g_debugText); debugOut << "OpenGL Info\n"; if (openGLCheckResult) { debugOut << "\n Vendor: " << openGLCheckResult->vendorString(); debugOut << "\n Renderer: " << openGLCheckResult->rendererString(); debugOut << "\n Version: " << openGLCheckResult->driverVersionString(); debugOut << "\n Shading language: " << openGLCheckResult->shadingLanguageString(); debugOut << "\n Requested format: " << QSurfaceFormat::defaultFormat(); debugOut << "\n Current format: " << openGLCheckResult->format(); debugOut.nospace(); debugOut << "\n Version: " << openGLCheckResult->glMajorVersion() << "." << openGLCheckResult->glMinorVersion(); debugOut.resetFormat(); debugOut << "\n Supports deprecated functions" << openGLCheckResult->supportsDeprecatedFunctions(); debugOut << "\n is OpenGL ES:" << openGLCheckResult->isOpenGLES(); } debugOut << "\n\nQPA OpenGL Detection Info"; debugOut << "\n supportsDesktopGL:" << bool(g_supportedRenderers & RendererDesktopGL); #ifdef Q_OS_WIN debugOut << "\n supportsAngleD3D11:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferAngle:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #else debugOut << "\n supportsOpenGLES:" << bool(g_supportedRenderers & RendererOpenGLES); debugOut << "\n isQtPreferOpenGLES:" << bool(g_rendererPreferredByQt == RendererOpenGLES); #endif // debugOut << "\n== log ==\n"; // debugOut.noquote(); // debugOut << g_surfaceFormatDetectionLog; // debugOut.resetFormat(); // debugOut << "\n== end log =="; dbgOpenGL.noquote().nospace() << g_debugText; - KisUsageLogger::write(g_debugText); + KisUsageLogger::writeSysInfo(g_debugText); if (!openGLCheckResult) { return; } // Check if we have a bugged driver that needs fence workaround bool isOnX11 = false; #ifdef HAVE_X11 isOnX11 = true; #endif KisConfig cfg(true); if ((isOnX11 && openGLCheckResult->rendererString().startsWith("AMD")) || cfg.forceOpenGLFenceWorkaround()) { g_needsFenceWorkaround = true; } /** * NVidia + Qt's openGL don't play well together and one cannot * draw a pixmap on a widget more than once in one rendering cycle. * * It can be workarounded by drawing strictly via QPixmapCache and * only when the pixmap size in bigger than doubled size of the * display framebuffer. That is for 8-bit HD display, you should have * a cache bigger than 16 MiB. Don't ask me why. (DK) * * See bug: https://bugs.kde.org/show_bug.cgi?id=361709 * * TODO: check if this workaround is still needed after merging * Qt5+openGL3 branch. */ if (openGLCheckResult->vendorString().toUpper().contains("NVIDIA")) { g_needsPixmapCacheWorkaround = true; const QRect screenSize = QGuiApplication::primaryScreen()->availableGeometry(); const int minCacheSize = 20 * 1024; const int cacheSize = 2048 + 2 * 4 * screenSize.width() * screenSize.height() / 1024; //KiB QPixmapCache::setCacheLimit(qMax(minCacheSize, cacheSize)); } } void KisOpenGL::initializeContext(QOpenGLContext *ctx) { KisConfig cfg(true); initialize(); const bool isDebugEnabled = ctx->format().testOption(QSurfaceFormat::DebugContext); dbgUI << "OpenGL: Opening new context"; if (isDebugEnabled) { // Passing ctx for ownership management only, not specifying context. // QOpenGLDebugLogger only function on the current active context. // FIXME: Do we need to make sure ctx is the active context? QOpenGLDebugLogger* openglLogger = new QOpenGLDebugLogger(ctx); if (openglLogger->initialize()) { qDebug() << "QOpenGLDebugLogger is initialized. Check whether you get a message below."; QObject::connect(openglLogger, &QOpenGLDebugLogger::messageLogged, &openglOnMessageLogged); openglLogger->startLogging(g_isDebugSynchronous ? QOpenGLDebugLogger::SynchronousLogging : QOpenGLDebugLogger::AsynchronousLogging); openglLogger->logMessage(QOpenGLDebugMessage::createApplicationMessage(QStringLiteral("QOpenGLDebugLogger is logging."))); } else { qDebug() << "QOpenGLDebugLogger cannot be initialized."; delete openglLogger; } } // Double check we were given the version we requested QSurfaceFormat format = ctx->format(); QOpenGLFunctions *f = ctx->functions(); f->initializeOpenGLFunctions(); QFile log(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/krita-opengl.txt"); log.open(QFile::WriteOnly); QString vendor((const char*)f->glGetString(GL_VENDOR)); log.write(vendor.toLatin1()); log.write(", "); log.write(openGLCheckResult->rendererString().toLatin1()); log.write(", "); QString version((const char*)f->glGetString(GL_VERSION)); log.write(version.toLatin1()); log.close(); } const QString &KisOpenGL::getDebugText() { initialize(); return g_debugText; } QStringList KisOpenGL::getOpenGLWarnings() { QStringList strings; Q_FOREACH (const KLocalizedString &item, g_openglWarningStrings) { strings << item.toString(); } return strings; } // XXX Temporary function to allow LoD on OpenGL3 without triggering // all of the other 3.2 functionality, can be removed once we move to Qt5.7 bool KisOpenGL::supportsLoD() { initialize(); return openGLCheckResult && openGLCheckResult->supportsLoD(); } bool KisOpenGL::hasOpenGL3() { initialize(); return openGLCheckResult && openGLCheckResult->hasOpenGL3(); } bool KisOpenGL::hasOpenGLES() { initialize(); return openGLCheckResult && openGLCheckResult->isOpenGLES(); } bool KisOpenGL::supportsFenceSync() { initialize(); return openGLCheckResult && openGLCheckResult->supportsFenceSync(); } bool KisOpenGL::needsFenceWorkaround() { initialize(); return g_needsFenceWorkaround; } bool KisOpenGL::needsPixmapCacheWorkaround() { initialize(); return g_needsPixmapCacheWorkaround; } void KisOpenGL::testingInitializeDefaultSurfaceFormat() { setDefaultSurfaceConfig(selectSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false)); } void KisOpenGL::setDebugSynchronous(bool value) { g_isDebugSynchronous = value; } KisOpenGL::OpenGLRenderer KisOpenGL::getCurrentOpenGLRenderer() { if (!openGLCheckResult) return RendererAuto; return getRendererFromProbeResult(*openGLCheckResult); } KisOpenGL::OpenGLRenderer KisOpenGL::getQtPreferredOpenGLRenderer() { return g_rendererPreferredByQt; } KisOpenGL::OpenGLRenderers KisOpenGL::getSupportedOpenGLRenderers() { return g_supportedRenderers; } KisOpenGL::OpenGLRenderer KisOpenGL::getUserPreferredOpenGLRendererConfig() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); return convertConfigToOpenGLRenderer(kritarc.value("OpenGLRenderer", "auto").toString()); } void KisOpenGL::setUserPreferredOpenGLRendererConfig(KisOpenGL::OpenGLRenderer renderer) { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); kritarc.setValue("OpenGLRenderer", KisOpenGL::convertOpenGLRendererToConfig(renderer)); } QString KisOpenGL::convertOpenGLRendererToConfig(KisOpenGL::OpenGLRenderer renderer) { switch (renderer) { case RendererNone: return QStringLiteral("none"); case RendererSoftware: return QStringLiteral("software"); case RendererDesktopGL: return QStringLiteral("desktop"); case RendererOpenGLES: return QStringLiteral("angle"); default: return QStringLiteral("auto"); } } KisOpenGL::OpenGLRenderer KisOpenGL::convertConfigToOpenGLRenderer(QString renderer) { if (renderer == "desktop") { return RendererDesktopGL; } else if (renderer == "angle") { return RendererOpenGLES; } else if (renderer == "software") { return RendererSoftware; } else if (renderer == "none") { return RendererNone; } else { return RendererAuto; } } KisOpenGL::OpenGLRenderer KisOpenGL::RendererConfig::rendererId() const { KisOpenGL::OpenGLRenderer result = RendererAuto; if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11Warp) { result = RendererSoftware; } else if (format.renderableType() == QSurfaceFormat::OpenGLES && angleRenderer == AngleRendererD3d11) { result = RendererOpenGLES; } else if (format.renderableType() == QSurfaceFormat::OpenGL) { result = RendererDesktopGL; } else if (format.renderableType() == QSurfaceFormat::DefaultRenderableType && angleRenderer == AngleRendererD3d11) { // noop } else { qWarning() << "WARNING: unsupported combination of OpenGL renderer" << ppVar(format.renderableType()) << ppVar(angleRenderer); } return result; } namespace { typedef std::pair RendererInfo; RendererInfo getRendererInfo(KisOpenGL::OpenGLRenderer renderer) { RendererInfo info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererD3d11}; switch (renderer) { case KisOpenGL::RendererNone: info = {QSurfaceFormat::DefaultRenderableType, KisOpenGL::AngleRendererDefault}; break; case KisOpenGL::RendererAuto: break; case KisOpenGL::RendererDesktopGL: info = {QSurfaceFormat::OpenGL, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererOpenGLES: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11}; break; case KisOpenGL::RendererSoftware: info = {QSurfaceFormat::OpenGLES, KisOpenGL::AngleRendererD3d11Warp}; break; } return info; } KisOpenGL::RendererConfig generateSurfaceConfig(KisOpenGL::OpenGLRenderer renderer, KisConfig::RootSurfaceFormat rootSurfaceFormat, bool debugContext) { RendererInfo info = getRendererInfo(renderer); KisOpenGL::RendererConfig config; config.angleRenderer = info.second; QSurfaceFormat &format = config.format; #ifdef Q_OS_MACOS format.setVersion(3, 2); format.setProfile(QSurfaceFormat::CoreProfile); #elif !defined(Q_OS_ANDROID) // XXX This can be removed once we move to Qt5.7 format.setVersion(3, 0); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOptions(QSurfaceFormat::DeprecatedFunctions); #endif format.setDepthBufferSize(24); format.setStencilBufferSize(8); KisOpenGLModeProber::initSurfaceFormatFromConfig(rootSurfaceFormat, &format); format.setRenderableType(info.first); format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); format.setSwapInterval(0); // Disable vertical refresh syncing if (debugContext) { format.setOption(QSurfaceFormat::DebugContext, true); } return config; } bool isOpenGLRendererBlacklisted(const QString &rendererString, const QString &driverVersionString, QVector *warningMessage) { bool isBlacklisted = false; #ifndef Q_OS_WIN Q_UNUSED(rendererString); Q_UNUSED(driverVersionString); Q_UNUSED(warningMessage); #else // Special blacklisting of OpenGL/ANGLE is tracked on: // https://phabricator.kde.org/T7411 // HACK: Specifically detect for Intel driver build number // See https://www.intel.com/content/www/us/en/support/articles/000005654/graphics-drivers.html if (rendererString.startsWith("Intel")) { KLocalizedString knownBadIntelWarning = ki18n("The Intel graphics driver in use is known to have issues with OpenGL."); KLocalizedString grossIntelWarning = ki18n( "Intel graphics drivers tend to have issues with OpenGL so ANGLE will be used by default. " "You may manually switch to OpenGL but it is not guaranteed to work properly." ); QRegularExpression regex("\\b\\d{1,2}\\.\\d{2}\\.\\d{1,3}\\.(\\d{4})\\b"); QRegularExpressionMatch match = regex.match(driverVersionString); if (match.hasMatch()) { int driverBuild = match.captured(1).toInt(); if (driverBuild > 4636 && driverBuild < 4729) { // Make ANGLE the preferred renderer for Intel driver versions // between build 4636 and 4729 (exclusive) due to an UI offset bug. // See https://communities.intel.com/thread/116003 // (Build 4636 is known to work from some test results) qDebug() << "Detected Intel driver build between 4636 and 4729, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else if (driverBuild == 4358) { // There are several reports on a bug where the canvas is not being // updated properly which has debug info pointing to this build. qDebug() << "Detected Intel driver build 4358, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << knownBadIntelWarning; } else { // Intel tends to randomly break OpenGL in some of their new driver // builds, therefore we just shouldn't use OpenGL by default to // reduce bug report noises. qDebug() << "Detected Intel driver, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } else { // In case Intel changed the driver version format to something that // we don't understand, we still select ANGLE. qDebug() << "Detected Intel driver with unknown version format, making ANGLE the preferred renderer"; isBlacklisted = true; *warningMessage << grossIntelWarning; } } #endif return isBlacklisted; } boost::optional orderPreference(bool lhs, bool rhs) { if (lhs == rhs) return boost::none; if (lhs && !rhs) return true; if (!lhs && rhs) return false; return false; } #define ORDER_BY(lhs, rhs) if (auto res = orderPreference((lhs), (rhs))) { return *res; } class FormatPositionLess { public: FormatPositionLess() { } bool operator()(const KisOpenGL::RendererConfig &lhs, const KisOpenGL::RendererConfig &rhs) const { KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredColorSpace != KisSurfaceColorSpace::DefaultColorSpace); if (m_preferredRendererByUser != KisOpenGL::RendererSoftware) { ORDER_BY(!isFallbackOnly(lhs.rendererId()), !isFallbackOnly(rhs.rendererId())); } #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) ORDER_BY(isPreferredColorSpace(lhs.format.colorSpace()), isPreferredColorSpace(rhs.format.colorSpace())); #endif if (doPreferHDR()) { ORDER_BY(isHDRFormat(lhs.format), isHDRFormat(rhs.format)); } else { ORDER_BY(!isHDRFormat(lhs.format), !isHDRFormat(rhs.format)); } if (m_preferredRendererByUser != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByUser, rhs.rendererId() == m_preferredRendererByUser); } ORDER_BY(!isBlacklisted(lhs.rendererId()), !isBlacklisted(rhs.rendererId())); if (doPreferHDR() && m_preferredRendererByHDR != KisOpenGL::RendererAuto) { ORDER_BY(lhs.rendererId() == m_preferredRendererByHDR, rhs.rendererId() == m_preferredRendererByHDR); } KIS_SAFE_ASSERT_RECOVER_NOOP(m_preferredRendererByQt != KisOpenGL::RendererAuto); ORDER_BY(lhs.rendererId() == m_preferredRendererByQt, rhs.rendererId() == m_preferredRendererByQt); return false; } public: void setPreferredColorSpace(const KisSurfaceColorSpace &preferredColorSpace) { m_preferredColorSpace = preferredColorSpace; } void setPreferredRendererByQt(const KisOpenGL::OpenGLRenderer &preferredRendererByQt) { m_preferredRendererByQt = preferredRendererByQt; } void setPreferredRendererByUser(const KisOpenGL::OpenGLRenderer &preferredRendererByUser) { m_preferredRendererByUser = preferredRendererByUser; } void setPreferredRendererByHDR(const KisOpenGL::OpenGLRenderer &preferredRendererByHDR) { m_preferredRendererByHDR = preferredRendererByHDR; } void setOpenGLBlacklisted(bool openGLBlacklisted) { m_openGLBlacklisted = openGLBlacklisted; } void setOpenGLESBlacklisted(bool openGLESBlacklisted) { m_openGLESBlacklisted = openGLESBlacklisted; } bool isOpenGLBlacklisted() const { return m_openGLBlacklisted; } bool isOpenGLESBlacklisted() const { return m_openGLESBlacklisted; } KisSurfaceColorSpace preferredColorSpace() const { return m_preferredColorSpace; } KisOpenGL::OpenGLRenderer preferredRendererByUser() const { return m_preferredRendererByUser; } private: bool isHDRFormat(const QSurfaceFormat &f) const { #ifdef HAVE_HDR return f.colorSpace() == KisSurfaceColorSpace::bt2020PQColorSpace || f.colorSpace() == KisSurfaceColorSpace::scRGBColorSpace; #else Q_UNUSED(f); return false; #endif } bool isFallbackOnly(KisOpenGL::OpenGLRenderer r) const { return r == KisOpenGL::RendererSoftware; } bool isBlacklisted(KisOpenGL::OpenGLRenderer r) const { KIS_SAFE_ASSERT_RECOVER_NOOP(r == KisOpenGL::RendererAuto || r == KisOpenGL::RendererDesktopGL || r == KisOpenGL::RendererOpenGLES || r == KisOpenGL::RendererSoftware || r == KisOpenGL::RendererNone); return (r == KisOpenGL::RendererDesktopGL && m_openGLBlacklisted) || (r == KisOpenGL::RendererOpenGLES && m_openGLESBlacklisted) || (r == KisOpenGL::RendererSoftware && m_openGLESBlacklisted); } bool doPreferHDR() const { #ifdef HAVE_HDR return m_preferredColorSpace == KisSurfaceColorSpace::bt2020PQColorSpace || m_preferredColorSpace == KisSurfaceColorSpace::scRGBColorSpace; #else return false; #endif } bool isPreferredColorSpace(const KisSurfaceColorSpace cs) const { return KisOpenGLModeProber::fuzzyCompareColorSpaces(m_preferredColorSpace, cs); return false; } private: KisSurfaceColorSpace m_preferredColorSpace = KisSurfaceColorSpace::DefaultColorSpace; KisOpenGL::OpenGLRenderer m_preferredRendererByQt = KisOpenGL::RendererDesktopGL; KisOpenGL::OpenGLRenderer m_preferredRendererByUser = KisOpenGL::RendererAuto; KisOpenGL::OpenGLRenderer m_preferredRendererByHDR = KisOpenGL::RendererAuto; bool m_openGLBlacklisted = false; bool m_openGLESBlacklisted = false; }; struct DetectionDebug : public QDebug { DetectionDebug(QString *string) : QDebug(string), m_string(string), m_originalSize(string->size()) {} ~DetectionDebug() { dbgOpenGL << m_string->right(m_string->size() - m_originalSize); *this << endl; } QString *m_string; int m_originalSize; }; } #define dbgDetection() DetectionDebug(&g_surfaceFormatDetectionLog) KisOpenGL::RendererConfig KisOpenGL::selectSurfaceConfig(KisOpenGL::OpenGLRenderer preferredRenderer, KisConfig::RootSurfaceFormat preferredRootSurfaceFormat, bool enableDebug) { QVector warningMessages; using Info = boost::optional; QHash renderersToTest; renderersToTest.insert(RendererDesktopGL, Info()); renderersToTest.insert(RendererOpenGLES, Info()); #ifdef Q_OS_WIN renderersToTest.insert(RendererSoftware, Info()); #endif #ifdef HAVE_HDR QVector formatSymbols({KisConfig::BT709_G22, KisConfig::BT709_G10, KisConfig::BT2020_PQ}); #else QVector formatSymbols({KisConfig::BT709_G22}); #endif KisOpenGL::RendererConfig defaultConfig = generateSurfaceConfig(KisOpenGL::RendererAuto, KisConfig::BT709_G22, false); Info info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); #ifdef Q_OS_WIN if (!info) { // try software rasterizer (WARP) defaultConfig = generateSurfaceConfig(KisOpenGL::RendererSoftware, KisConfig::BT709_G22, false); info = KisOpenGLModeProber::instance()->probeFormat(defaultConfig); if (!info) { renderersToTest.remove(RendererSoftware); } } #endif if (!info) return KisOpenGL::RendererConfig(); const OpenGLRenderer defaultRenderer = getRendererFromProbeResult(*info); OpenGLRenderers supportedRenderers = RendererNone; FormatPositionLess compareOp; compareOp.setPreferredRendererByQt(defaultRenderer); #ifdef HAVE_HDR compareOp.setPreferredColorSpace( preferredRootSurfaceFormat == KisConfig::BT709_G22 ? KisSurfaceColorSpace::sRGBColorSpace : preferredRootSurfaceFormat == KisConfig::BT709_G10 ? KisSurfaceColorSpace::scRGBColorSpace : KisSurfaceColorSpace::bt2020PQColorSpace); #else Q_UNUSED(preferredRootSurfaceFormat); compareOp.setPreferredColorSpace(KisSurfaceColorSpace::sRGBColorSpace); #endif #ifdef Q_OS_WIN compareOp.setPreferredRendererByHDR(KisOpenGL::RendererOpenGLES); #endif compareOp.setPreferredRendererByUser(preferredRenderer); compareOp.setOpenGLESBlacklisted(false); // We cannot blacklist ES drivers atm renderersToTest[defaultRenderer] = info; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { Info info = it.value(); if (!info) { info = KisOpenGLModeProber::instance()-> probeFormat(generateSurfaceConfig(it.key(), KisConfig::BT709_G22, false)); *it = info; } compareOp.setOpenGLBlacklisted( !info || isOpenGLRendererBlacklisted(info->rendererString(), info->driverVersionString(), &warningMessages)); if (info && info->isSupportedVersion()) { supportedRenderers |= it.key(); } } OpenGLRenderer preferredByQt = defaultRenderer; if (preferredByQt == RendererDesktopGL && supportedRenderers & RendererDesktopGL && compareOp.isOpenGLBlacklisted()) { preferredByQt = RendererOpenGLES; } else if (preferredByQt == RendererOpenGLES && supportedRenderers & RendererOpenGLES && compareOp.isOpenGLESBlacklisted()) { preferredByQt = RendererDesktopGL; } QVector preferredConfigs; for (auto it = renderersToTest.begin(); it != renderersToTest.end(); ++it) { // if default mode of the renderer doesn't work, then custom won't either if (!it.value()) continue; Q_FOREACH (const KisConfig::RootSurfaceFormat formatSymbol, formatSymbols) { preferredConfigs << generateSurfaceConfig(it.key(), formatSymbol, enableDebug); } } std::stable_sort(preferredConfigs.begin(), preferredConfigs.end(), compareOp); dbgDetection() << "Supported renderers:" << supportedRenderers; dbgDetection() << "Surface format preference list:"; Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { dbgDetection() << "*" << config.format; dbgDetection() << " " << config.rendererId(); } KisOpenGL::RendererConfig resultConfig = defaultConfig; if (preferredRenderer != RendererNone) { Q_FOREACH (const KisOpenGL::RendererConfig &config, preferredConfigs) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) dbgDetection() <<"Probing format..." << config.format.colorSpace() << config.rendererId(); #else dbgDetection() <<"Probing format..." << config.rendererId(); #endif Info info = KisOpenGLModeProber::instance()->probeFormat(config); if (info && info->isSupportedVersion()) { #ifdef Q_OS_WIN // HACK: Block ANGLE with Direct3D9 // Direct3D9 does not give OpenGL ES 3.0 // Some versions of ANGLE returns OpenGL version 3.0 incorrectly if (info->isUsingAngle() && info->rendererString().contains("Direct3D9", Qt::CaseInsensitive)) { dbgDetection() << "Skipping Direct3D 9 Angle implementation, it shouldn't have happened."; continue; } #endif dbgDetection() << "Found format:" << config.format; dbgDetection() << " " << config.rendererId(); resultConfig = config; break; } } { const bool colorSpaceIsCorrect = #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) KisOpenGLModeProber::fuzzyCompareColorSpaces(compareOp.preferredColorSpace(), resultConfig.format.colorSpace()); #else true; #endif const bool rendererIsCorrect = compareOp.preferredRendererByUser() == KisOpenGL::RendererAuto || compareOp.preferredRendererByUser() == resultConfig.rendererId(); if (!rendererIsCorrect && colorSpaceIsCorrect) { warningMessages << ki18n("Preferred renderer doesn't support requested surface format. Another renderer has been selected."); } else if (!colorSpaceIsCorrect) { warningMessages << ki18n("Preferred output format is not supported by available renderers"); } } } else { resultConfig.format = QSurfaceFormat(); resultConfig.angleRenderer = AngleRendererDefault; } overrideSupportedRenderers(supportedRenderers, preferredByQt); overrideOpenGLWarningString(warningMessages); return resultConfig; } void KisOpenGL::setDefaultSurfaceConfig(const KisOpenGL::RendererConfig &config) { KIS_SAFE_ASSERT_RECOVER_NOOP(!g_sanityDefaultFormatIsSet); g_sanityDefaultFormatIsSet = true; QSurfaceFormat::setDefaultFormat(config.format); #ifdef Q_OS_WIN // Force ANGLE to use Direct3D11. D3D9 doesn't support OpenGL ES 3 and WARP // might get weird crashes atm. qputenv("QT_ANGLE_PLATFORM", KisOpenGLModeProber::angleRendererToString(config.angleRenderer).toLatin1()); #endif if (config.format.renderableType() == QSurfaceFormat::OpenGLES) { QCoreApplication::setAttribute(Qt::AA_UseOpenGLES, true); } else if (config.format.renderableType() == QSurfaceFormat::OpenGL) { QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL, true); } } bool KisOpenGL::hasOpenGL() { return openGLCheckResult->isSupportedVersion(); } diff --git a/libs/ui/osx.mm b/libs/ui/osx.mm index bf2bc26d10..ebf2206d44 100644 --- a/libs/ui/osx.mm +++ b/libs/ui/osx.mm @@ -1,32 +1,32 @@ /* * Copyright (c) 2017 Bernhard Liebl * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#import +#import extern "C" { bool isMouseCoalescingEnabled(); void setMouseCoalescingEnabled(bool enabled); } bool isMouseCoalescingEnabled() { return NSEvent.isMouseCoalescingEnabled; } void setMouseCoalescingEnabled(bool enabled) { NSEvent.mouseCoalescingEnabled = enabled; } diff --git a/libs/ui/widgets/KoFillConfigWidget.cpp b/libs/ui/widgets/KoFillConfigWidget.cpp index 5a078468d2..43a444d34f 100644 --- a/libs/ui/widgets/KoFillConfigWidget.cpp +++ b/libs/ui/widgets/KoFillConfigWidget.cpp @@ -1,916 +1,913 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2012 Jean-Nicolas Artaud * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoFillConfigWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoResourceServerProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoZoomHandler.h" #include "KoColorPopupButton.h" #include "ui_KoFillConfigWidget.h" #include #include #include #include #include "kis_canvas_resource_provider.h" #include #include #include #include "kis_global.h" #include "kis_debug.h" static const char* const buttonnone[]={ "16 16 3 1", "# c #000000", "e c #ff0000", "- c #ffffff", "################", "#--------------#", "#-e----------e-#", "#--e--------e--#", "#---e------e---#", "#----e----e----#", "#-----e--e-----#", "#------ee------#", "#------ee------#", "#-----e--e-----#", "#----e----e----#", "#---e------e---#", "#--e--------e--#", "#-e----------e-#", "#--------------#", "################"}; static const char* const buttonsolid[]={ "16 16 2 1", "# c #000000", ". c #969696", "################", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "#..............#", "################"}; // FIXME: Smoother gradient button. static const char* const buttongradient[]={ "16 16 15 1", "# c #000000", "n c #101010", "m c #202020", "l c #303030", "k c #404040", "j c #505050", "i c #606060", "h c #707070", "g c #808080", "f c #909090", "e c #a0a0a0", "d c #b0b0b0", "c c #c0c0c0", "b c #d0d0d0", "a c #e0e0e0", "################", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "#abcdefghijklmn#", "################"}; static const char* const buttonpattern[]={ "16 16 4 1", ". c #0a0a0a", "# c #333333", "a c #a0a0a0", "b c #ffffffff", "################", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#bbbbbaaaabbbbb#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "#aaaaabbbbaaaaa#", "################"}; class Q_DECL_HIDDEN KoFillConfigWidget::Private { public: Private(KoFlake::FillVariant _fillVariant) : canvas(0), colorChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), gradientChangedCompressor(100, KisSignalCompressor::FIRST_ACTIVE), shapeChangedCompressor(200,KisSignalCompressor::FIRST_ACTIVE), fillVariant(_fillVariant), noSelectionTrackingMode(false) { } KoColorPopupAction *colorAction; KoResourcePopupAction *gradientAction; KoResourcePopupAction *patternAction; QButtonGroup *group; KoCanvasBase *canvas; KisSignalCompressor colorChangedCompressor; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; KoFillConfigWidget::StyleButton selectedFillIndex {KoFillConfigWidget::None}; KoStopGradientSP activeGradient; KisSignalCompressor gradientChangedCompressor; KisSignalCompressor shapeChangedCompressor; KoFlake::FillVariant fillVariant; bool noSelectionTrackingMode; Ui_KoFillConfigWidget *ui; std::vector deactivationLocks; boost::optional overriddenColorFromProvider; }; KoFillConfigWidget::KoFillConfigWidget(KoCanvasBase *canvas, KoFlake::FillVariant fillVariant, bool trackShapeSelection, QWidget *parent) : QWidget(parent) , d(new Private(fillVariant)) { d->canvas = canvas; if (trackShapeSelection) { d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), &d->shapeChangedCompressor, SLOT(start())); d->shapeChangedAcyclicConnector.connectBackwardVoid( d->canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), &d->shapeChangedCompressor, SLOT(start())); connect(&d->shapeChangedCompressor, SIGNAL(timeout()), this, SLOT(shapeChanged())); } d->resourceManagerAcyclicConnector.connectBackwardResourcePair( d->canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(slotCanvasResourceChanged(int,QVariant))); d->resourceManagerAcyclicConnector.connectForwardVoid( this, SIGNAL(sigInternalRequestColorToResourceManager()), this, SLOT(slotProposeCurrentColorToResourceManager())); KisAcyclicSignalConnector *resetConnector = d->resourceManagerAcyclicConnector.createCoordinatedConnector(); resetConnector->connectForwardVoid( this, SIGNAL(sigInternalRecoverColorInResourceManager()), this, SLOT(slotRecoverColorInResourceManager())); // configure GUI d->ui = new Ui_KoFillConfigWidget(); d->ui->setupUi(this); d->group = new QButtonGroup(this); d->group->setExclusive(true); d->ui->btnNoFill->setIcon(QPixmap((const char **) buttonnone)); d->group->addButton(d->ui->btnNoFill, None); d->ui->btnSolidFill->setIcon(QPixmap((const char **) buttonsolid)); d->group->addButton(d->ui->btnSolidFill, Solid); d->ui->btnGradientFill->setIcon(QPixmap((const char **) buttongradient)); d->group->addButton(d->ui->btnGradientFill, Gradient); d->ui->btnPatternFill->setIcon(QPixmap((const char **) buttonpattern)); d->group->addButton(d->ui->btnPatternFill, Pattern); d->ui->btnPatternFill->setVisible(false); d->colorAction = new KoColorPopupAction(d->ui->btnChooseSolidColor); d->colorAction->setToolTip(i18n("Change the filling color")); d->colorAction->setCurrentColor(Qt::white); d->ui->btnChooseSolidColor->setDefaultAction(d->colorAction); d->ui->btnChooseSolidColor->setPopupMode(QToolButton::InstantPopup); d->ui->btnSolidColorPick->setIcon(KisIconUtils::loadIcon("krita_tool_color_picker")); // TODO: for now the color picking button is disabled! d->ui->btnSolidColorPick->setEnabled(false); d->ui->btnSolidColorPick->setVisible(false); connect(d->colorAction, SIGNAL(colorChanged(KoColor)), &d->colorChangedCompressor, SLOT(start())); connect(&d->colorChangedCompressor, SIGNAL(timeout()), SLOT(colorChanged())); connect(d->ui->btnChooseSolidColor, SIGNAL(iconSizeChanged()), d->colorAction, SLOT(updateIcon())); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(styleButtonPressed(int))); connect(d->group, SIGNAL(buttonClicked(int)), SLOT(slotUpdateFillTitle())); slotUpdateFillTitle(); styleButtonPressed(d->group->checkedId()); // Gradient selector d->ui->wdgGradientEditor->setCompactMode(true); connect(d->ui->wdgGradientEditor, SIGNAL(sigGradientChanged()), &d->gradientChangedCompressor, SLOT(start())); connect(&d->gradientChangedCompressor, SIGNAL(timeout()), SLOT(activeGradientChanged())); d->gradientAction = new KoResourcePopupAction(ResourceType::Gradients, d->ui->btnChoosePredefinedGradient); d->gradientAction->setToolTip(i18n("Change filling gradient")); d->ui->btnChoosePredefinedGradient->setDefaultAction(d->gradientAction); d->ui->btnChoosePredefinedGradient->setPopupMode(QToolButton::InstantPopup); connect(d->gradientAction, SIGNAL(resourceSelected(QSharedPointer)), SLOT(gradientResourceChanged())); connect(d->ui->btnChoosePredefinedGradient, SIGNAL(iconSizeChanged()), d->gradientAction, SLOT(updateIcon())); d->ui->btnSaveGradient->setIcon(KisIconUtils::loadIcon("document-save")); connect(d->ui->btnSaveGradient, SIGNAL(clicked()), SLOT(slotSavePredefinedGradientClicked())); connect(d->ui->cmbGradientRepeat, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientRepeatChanged())); connect(d->ui->cmbGradientType, SIGNAL(currentIndexChanged(int)), SLOT(slotGradientTypeChanged())); deactivate(); /* // Pattern selector d->patternAction = new KoResourcePopupAction(ResourceType::Patterns, d->colorButton); d->patternAction->setToolTip(i18n("Change the filling pattern")); connect(d->patternAction, SIGNAL(resourceSelected(QSharedPointer)), this, SLOT(patternChanged(QSharedPointer))); connect(d->colorButton, SIGNAL(iconSizeChanged()), d->patternAction, SLOT(updateIcon())); */ } KoFillConfigWidget::~KoFillConfigWidget() { delete d; } void KoFillConfigWidget::activate() { - KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); - if (!d->noSelectionTrackingMode) { d->shapeChangedCompressor.start(); } else { loadCurrentFillFromResourceServer(); } updateWidgetComponentVisbility(); } void KoFillConfigWidget::deactivate() { emit sigInternalRecoverColorInResourceManager(); - - KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); + d->deactivationLocks.clear(); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); } void KoFillConfigWidget::forceUpdateOnSelectionChanged() { d->shapeChangedCompressor.start(); } void KoFillConfigWidget::setNoSelectionTrackingMode(bool value) { d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { d->shapeChangedCompressor.start(); } } void KoFillConfigWidget::slotUpdateFillTitle() { QString text = d->group->checkedButton() ? d->group->checkedButton()->text() : QString(); text.replace('&', QString()); d->ui->lblFillTitle->setText(text); } void KoFillConfigWidget::slotCanvasResourceChanged(int key, const QVariant &value) { if ((key == KoCanvasResourceProvider::ForegroundColor && d->fillVariant == KoFlake::Fill) || (key == KoCanvasResourceProvider::BackgroundColor && d->fillVariant == KoFlake::StrokeFill && !d->noSelectionTrackingMode) || (key == KoCanvasResourceProvider::ForegroundColor && d->noSelectionTrackingMode)) { KoColor color = value.value(); const int checkedId = d->group->checkedId(); if ((checkedId < 0 || checkedId == None || checkedId == Solid) && !(checkedId == Solid && d->colorAction->currentKoColor() == color)) { d->group->button(Solid)->setChecked(true); d->selectedFillIndex = Solid; d->colorAction->setCurrentColor(color); d->colorChangedCompressor.start(); } else if (checkedId == Gradient && key == KoCanvasResourceProvider::ForegroundColor) { d->ui->wdgGradientEditor->notifyGlobalColorChanged(color); } } else if (key == KisCanvasResourceProvider::CurrentGradient) { KoResourceSP gradient = value.value(); const int checkedId = d->group->checkedId(); if (gradient && (checkedId < 0 || checkedId == None || checkedId == Gradient)) { d->group->button(Gradient)->setChecked(true); d->gradientAction->setCurrentResource(gradient); } } } QList KoFillConfigWidget::currentShapes() { return d->canvas->selectedShapesProxy()->selection()->selectedEditableShapes(); } int KoFillConfigWidget::selectedFillIndex() { return d->selectedFillIndex; } void KoFillConfigWidget::styleButtonPressed(int buttonId) { QList shapes = currentShapes(); switch (buttonId) { case KoFillConfigWidget::None: noColorSelected(); break; case KoFillConfigWidget::Solid: colorChanged(); break; case KoFillConfigWidget::Gradient: if (d->activeGradient) { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } else { gradientResourceChanged(); } break; case KoFillConfigWidget::Pattern: // Only select mode in the widget, don't set actual pattern :/ //d->colorButton->setDefaultAction(d->patternAction); //patternChanged(d->patternAction->currentBackground()); break; } // update tool option fields with first selected object if (shapes.isEmpty() == false) { KoShape *firstShape = shapes.first(); updateFillIndexFromShape(firstShape); updateFillColorFromShape(firstShape); } updateWidgetComponentVisbility(); } KoShapeStrokeSP KoFillConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(new KoShapeStroke()); KIS_ASSERT_RECOVER_RETURN_VALUE(d->fillVariant == KoFlake::StrokeFill, stroke); switch (d->group->checkedId()) { case KoFillConfigWidget::None: stroke->setColor(Qt::transparent); break; case KoFillConfigWidget::Solid: stroke->setColor(d->colorAction->currentColor()); break; case KoFillConfigWidget::Gradient: { QScopedPointer g(d->activeGradient->toQGradient()); QBrush newBrush = *g; stroke->setLineBrush(newBrush); stroke->setColor(Qt::transparent); break; } case KoFillConfigWidget::Pattern: break; } return stroke; } void KoFillConfigWidget::noColorSelected() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(QColor()); if (command) { d->canvas->addCommand(command); } if (d->fillVariant == KoFlake::StrokeFill) { KUndo2Command *lineCommand = wrapper.setLineWidth(0.0); if (lineCommand) { d->canvas->addCommand(lineCommand); } } emit sigFillChanged(); } void KoFillConfigWidget::colorChanged() { KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigInternalRequestColorToResourceManager(); emit sigFillChanged(); return; } d->overriddenColorFromProvider = boost::none; KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); KUndo2Command *command = wrapper.setColor(d->colorAction->currentColor()); if (command) { d->canvas->addCommand(command); } // only returns true if it is a stroke object that has a 0 for line width if (wrapper.hasZeroLineWidth() ) { KUndo2Command *lineCommand = wrapper.setLineWidth(1.0); if (lineCommand) { d->canvas->addCommand(lineCommand); } // * line to test out QColor solidColor = d->colorAction->currentColor(); solidColor.setAlpha(255); command = wrapper.setColor(solidColor); if (command) { d->canvas->addCommand(command); } } d->colorAction->setCurrentColor(wrapper.color()); emit sigFillChanged(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::slotProposeCurrentColorToResourceManager() { const int checkedId = d->group->checkedId(); bool hasColor = false; KoColor color; KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor; if (checkedId == Solid) { if (d->fillVariant == KoFlake::StrokeFill) { colorSlot = KoCanvasResourceProvider::BackgroundColor; } color = d->colorAction->currentKoColor(); hasColor = true; } else if (checkedId == Gradient) { if (boost::optional gradientColor = d->ui->wdgGradientEditor->currentActiveStopColor()) { color = *gradientColor; hasColor = true; } } if (hasColor) { if (!d->overriddenColorFromProvider) { d->overriddenColorFromProvider = d->canvas->resourceManager()->resource(colorSlot).value(); } /** * Don't let opacity leak to our resource manager system * * NOTE: theoretically, we could guarantee it on a level of the * resource manager itself, */ color.setOpacity(OPACITY_OPAQUE_U8); d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(color)); } } void KoFillConfigWidget::slotRecoverColorInResourceManager() { if (d->overriddenColorFromProvider) { KoCanvasResourceProvider::CanvasResource colorSlot = KoCanvasResourceProvider::ForegroundColor; if (d->fillVariant == KoFlake::StrokeFill) { colorSlot = KoCanvasResourceProvider::BackgroundColor; } d->canvas->resourceManager()->setResource(colorSlot, QVariant::fromValue(*d->overriddenColorFromProvider)); d->overriddenColorFromProvider = boost::none; } } void KoFillConfigWidget::slotSavePredefinedGradientClicked() { KoResourceServerProvider *serverProvider = KoResourceServerProvider::instance(); auto server = serverProvider->gradientServer(); const QString defaultGradientNamePrefix = i18nc("default prefix for the saved gradient", "gradient"); const QString saveLocation = server->saveLocation(); QString name = d->activeGradient->name().isEmpty() ? defaultGradientNamePrefix : d->activeGradient->name(); QFileInfo fileInfo(saveLocation + name.split(" ").join("_") + d->activeGradient->defaultFileExtension()); bool fileOverWriteAccepted = false; while(!fileOverWriteAccepted) { name = QInputDialog::getText(this, i18nc("@title:window", "Save Gradient"), i18n("Enter gradient name:"), QLineEdit::Normal, name); if (name.isNull() || name.isEmpty()) { return; } else { fileInfo = QFileInfo(saveLocation + name.split(" ").join("_") + d->activeGradient->defaultFileExtension()); if (fileInfo.exists()) { int res = QMessageBox::warning(this, i18nc("@title:window", "Name Already Exists") , i18n("The name '%1' already exists, do you wish to overwrite it?", name) , QMessageBox::Yes | QMessageBox::No); if (res == QMessageBox::Yes) fileOverWriteAccepted = true; } else { fileOverWriteAccepted = true; } } } d->activeGradient->setName(name); d->activeGradient->setFilename(name.split(" ").join("_") + d->activeGradient->defaultFileExtension()); KoAbstractGradientSP newGradient = d->activeGradient->clone().dynamicCast(); server->addResource(newGradient); d->gradientAction->setCurrentResource(newGradient); } void KoFillConfigWidget::activeGradientChanged() { setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); emit sigInternalRequestColorToResourceManager(); } void KoFillConfigWidget::gradientResourceChanged() { QSharedPointer bg = qSharedPointerDynamicCast( d->gradientAction->currentBackground()); uploadNewGradientBackground(bg->gradient()); setNewGradientBackgroundToShape(); updateGradientSaveButtonAvailability(); } void KoFillConfigWidget::slotGradientTypeChanged() { QGradient::Type type = d->ui->cmbGradientType->currentIndex() == 0 ? QGradient::LinearGradient : QGradient::RadialGradient; d->activeGradient->setType(type); activeGradientChanged(); } void KoFillConfigWidget::slotGradientRepeatChanged() { QGradient::Spread spread = QGradient::Spread(d->ui->cmbGradientRepeat->currentIndex()); d->activeGradient->setSpread(spread); activeGradientChanged(); } void KoFillConfigWidget::uploadNewGradientBackground(const QGradient *gradient) { KisSignalsBlocker b1(d->ui->wdgGradientEditor, d->ui->cmbGradientType, d->ui->cmbGradientRepeat); d->ui->wdgGradientEditor->setGradient(0); d->activeGradient = KoStopGradient::fromQGradient(gradient); d->ui->wdgGradientEditor->setGradient(d->activeGradient); d->ui->cmbGradientType->setCurrentIndex(d->activeGradient->type() != QGradient::LinearGradient); d->ui->cmbGradientRepeat->setCurrentIndex(int(d->activeGradient->spread())); } void KoFillConfigWidget::setNewGradientBackgroundToShape() { QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { emit sigFillChanged(); return; } KisAcyclicSignalConnector::Blocker b(d->shapeChangedAcyclicConnector); KoShapeFillWrapper wrapper(selectedShapes, d->fillVariant); QScopedPointer srcQGradient(d->activeGradient->toQGradient()); KUndo2Command *command = wrapper.applyGradientStopsOnly(srcQGradient.data()); if (command) { d->canvas->addCommand(command); } emit sigFillChanged(); } void KoFillConfigWidget::updateGradientSaveButtonAvailability() { bool savingEnabled = false; QScopedPointer currentGradient(d->activeGradient->toQGradient()); QSharedPointer bg = d->gradientAction->currentBackground(); if (bg) { QSharedPointer resourceBackground = qSharedPointerDynamicCast(bg); savingEnabled = resourceBackground->gradient()->stops() != currentGradient->stops(); savingEnabled |= resourceBackground->gradient()->type() != currentGradient->type(); savingEnabled |= resourceBackground->gradient()->spread() != currentGradient->spread(); } d->ui->btnSaveGradient->setEnabled(savingEnabled); } void KoFillConfigWidget::patternChanged(QSharedPointer background) { Q_UNUSED(background); #if 0 QSharedPointer patternBackground = qSharedPointerDynamicCast(background); if (! patternBackground) { return; } QList selectedShapes = currentShapes(); if (selectedShapes.isEmpty()) { return; } KoImageCollection *imageCollection = d->canvas->shapeController()->resourceManager()->imageCollection(); if (imageCollection) { QSharedPointer fill(new KoPatternBackground(imageCollection)); fill->setPattern(patternBackground->pattern()); d->canvas->addCommand(new KoShapeBackgroundCommand(selectedShapes, fill)); } #endif } void KoFillConfigWidget::loadCurrentFillFromResourceServer() { { KoColor color = d->canvas->resourceManager()->backgroundColor(); slotCanvasResourceChanged(KoCanvasResourceProvider::BackgroundColor, QVariant::fromValue(color)); } { KoColor color = d->canvas->resourceManager()->foregroundColor(); slotCanvasResourceChanged(KoCanvasResourceProvider::ForegroundColor, QVariant::fromValue(color)); } Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } emit sigFillChanged(); } void KoFillConfigWidget::shapeChanged() { if (d->noSelectionTrackingMode) return; QList shapes = currentShapes(); bool shouldUploadColorToResourceManager = false; if (shapes.isEmpty() || (shapes.size() > 1 && KoShapeFillWrapper(shapes, d->fillVariant).isMixedFill())) { Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(!shapes.isEmpty()); } } else { // only one vector object selected Q_FOREACH (QAbstractButton *button, d->group->buttons()) { button->setEnabled(true); } // update active index of shape KoShape *shape = shapes.first(); updateFillIndexFromShape(shape); updateFillColorFromShape(shape); // updates tool options fields shouldUploadColorToResourceManager = true; } // updates the UI d->group->button(d->selectedFillIndex)->setChecked(true); updateWidgetComponentVisbility(); slotUpdateFillTitle(); if (shouldUploadColorToResourceManager) { emit sigInternalRequestColorToResourceManager(); } else { emit sigInternalRecoverColorInResourceManager(); } } void KoFillConfigWidget::updateFillIndexFromShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: d->selectedFillIndex = KoFillConfigWidget::None; break; case KoFlake::Solid: d->selectedFillIndex = KoFillConfigWidget::Solid; break; case KoFlake::Gradient: d->selectedFillIndex = KoFillConfigWidget::Gradient; break; case KoFlake::Pattern: d->selectedFillIndex = KoFillConfigWidget::Pattern; break; } } void KoFillConfigWidget::updateFillColorFromShape(KoShape *shape) { KIS_SAFE_ASSERT_RECOVER_RETURN(shape); KoShapeFillWrapper wrapper(shape, d->fillVariant); switch (wrapper.type()) { case KoFlake::None: break; case KoFlake::Solid: { QColor color = wrapper.color(); if (color.alpha() > 0) { d->colorAction->setCurrentColor(wrapper.color()); } break; } case KoFlake::Gradient: uploadNewGradientBackground(wrapper.gradient()); updateGradientSaveButtonAvailability(); break; case KoFlake::Pattern: break; } } void KoFillConfigWidget::updateWidgetComponentVisbility() { // The UI is showing/hiding things like this because the 'stacked widget' isn't very flexible // and makes it difficult to put anything underneath it without a lot empty space // hide everything first d->ui->wdgGradientEditor->setVisible(false); d->ui->btnChoosePredefinedGradient->setVisible(false); d->ui->btnChooseSolidColor->setVisible(false); d->ui->typeLabel->setVisible(false); d->ui->repeatLabel->setVisible(false); d->ui->cmbGradientRepeat->setVisible(false); d->ui->cmbGradientType->setVisible(false); d->ui->btnSolidColorPick->setVisible(false); d->ui->btnSaveGradient->setVisible(false); d->ui->gradientTypeLine->setVisible(false); d->ui->soldStrokeColorLabel->setVisible(false); d->ui->presetLabel->setVisible(false); // keep options hidden if no vector shapes are selected if(currentShapes().isEmpty()) { return; } switch (d->selectedFillIndex) { case KoFillConfigWidget::None: break; case KoFillConfigWidget::Solid: d->ui->btnChooseSolidColor->setVisible(true); d->ui->btnSolidColorPick->setVisible(false); d->ui->soldStrokeColorLabel->setVisible(true); break; case KoFillConfigWidget::Gradient: d->ui->wdgGradientEditor->setVisible(true); d->ui->btnChoosePredefinedGradient->setVisible(true); d->ui->typeLabel->setVisible(true); d->ui->repeatLabel->setVisible(true); d->ui->cmbGradientRepeat->setVisible(true); d->ui->cmbGradientType->setVisible(true); d->ui->btnSaveGradient->setVisible(true); d->ui->gradientTypeLine->setVisible(true); d->ui->presetLabel->setVisible(true); break; case KoFillConfigWidget::Pattern: break; } } diff --git a/libs/ui/widgets/KoStrokeConfigWidget.cpp b/libs/ui/widgets/KoStrokeConfigWidget.cpp index 608e57a261..f02e88bd3c 100644 --- a/libs/ui/widgets/KoStrokeConfigWidget.cpp +++ b/libs/ui/widgets/KoStrokeConfigWidget.cpp @@ -1,804 +1,800 @@ /* This file is part of the KDE project * Made by Tomislav Lukman (tomislav.lukman@ck.tel.hr) * Copyright (C) 2002 Tomislav Lukman * Copyright (C) 2002-2003 Rob Buis * Copyright (C) 2005-2006 Tim Beaulen * Copyright (C) 2005-2007 Thomas Zander * Copyright (C) 2005-2006, 2011 Inge Wallin * Copyright (C) 2005-2008 Jan Hambrecht * Copyright (C) 2006 C. Boemann * Copyright (C) 2006 Peter Simonsson * Copyright (C) 2006 Laurent Montel * Copyright (C) 2007,2011 Thorsten Zachmann * Copyright (C) 2011 Jean-Nicolas Artaud * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // Own #include "KoStrokeConfigWidget.h" // Qt #include #include #include #include #include #include #include // KDE #include // Calligra #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_KoStrokeConfigWidget.h" #include #include #include "kis_canvas_resource_provider.h" #include "kis_acyclic_signal_connector.h" #include // Krita #include "kis_double_parse_unit_spin_box.h" class CapNJoinMenu : public QMenu { public: CapNJoinMenu(QWidget *parent = 0); QSize sizeHint() const override; KisDoubleParseUnitSpinBox *miterLimit {0}; QButtonGroup *capGroup {0}; QButtonGroup *joinGroup {0}; }; CapNJoinMenu::CapNJoinMenu(QWidget *parent) : QMenu(parent) { QGridLayout *mainLayout = new QGridLayout(); mainLayout->setMargin(2); // The cap group capGroup = new QButtonGroup(this); capGroup->setExclusive(true); QToolButton *button = 0; button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-butt")); button->setCheckable(true); button->setToolTip(i18n("Butt cap")); capGroup->addButton(button, Qt::FlatCap); mainLayout->addWidget(button, 2, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-round")); button->setCheckable(true); button->setToolTip(i18n("Round cap")); capGroup->addButton(button, Qt::RoundCap); mainLayout->addWidget(button, 2, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-cap-square")); button->setCheckable(true); button->setToolTip(i18n("Square cap")); capGroup->addButton(button, Qt::SquareCap); mainLayout->addWidget(button, 2, 2, Qt::AlignLeft); // The join group joinGroup = new QButtonGroup(this); joinGroup->setExclusive(true); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-miter")); button->setCheckable(true); button->setToolTip(i18n("Miter join")); joinGroup->addButton(button, Qt::MiterJoin); mainLayout->addWidget(button, 3, 0); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-round")); button->setCheckable(true); button->setToolTip(i18n("Round join")); joinGroup->addButton(button, Qt::RoundJoin); mainLayout->addWidget(button, 3, 1); button = new QToolButton(this); button->setIcon(koIcon("stroke-join-bevel")); button->setCheckable(true); button->setToolTip(i18n("Bevel join")); joinGroup->addButton(button, Qt::BevelJoin); mainLayout->addWidget(button, 3, 2, Qt::AlignLeft); // Miter limit // set min/max/step and value in points, then set actual unit miterLimit = new KisDoubleParseUnitSpinBox(this); miterLimit->setMinMaxStep(0.0, 1000.0, 0.5); miterLimit->setDecimals(2); miterLimit->setUnit(KoUnit(KoUnit::Point)); miterLimit->setToolTip(i18n("Miter limit")); mainLayout->addWidget(miterLimit, 4, 0, 1, 3); mainLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); setLayout(mainLayout); } QSize CapNJoinMenu::sizeHint() const { return layout()->sizeHint(); } class Q_DECL_HIDDEN KoStrokeConfigWidget::Private { public: Private() : selectionChangedCompressor(200, KisSignalCompressor::FIRST_ACTIVE) { } KoLineStyleSelector *lineStyle {0}; KisDoubleParseUnitSpinBox *lineWidth {0}; KoMarkerSelector *startMarkerSelector {0}; KoMarkerSelector *midMarkerSelector {0}; KoMarkerSelector *endMarkerSelector {0}; CapNJoinMenu *capNJoinMenu {0}; QWidget*spacer {0}; KoCanvasBase *canvas {0}; bool active {true}; bool allowLocalUnitManagement {false}; KoFillConfigWidget *fillConfigWidget {0}; bool noSelectionTrackingMode {false}; KisAcyclicSignalConnector shapeChangedAcyclicConnector; KisAcyclicSignalConnector resourceManagerAcyclicConnector; KisSignalCompressor selectionChangedCompressor; std::vector deactivationLocks; Ui_KoStrokeConfigWidget *ui; }; KoStrokeConfigWidget::KoStrokeConfigWidget(KoCanvasBase *canvas, QWidget * parent) : QWidget(parent) , d(new Private()) { // configure GUI d->ui = new Ui_KoStrokeConfigWidget(); d->ui->setupUi(this); setObjectName("Stroke widget"); { // connect the canvas d->shapeChangedAcyclicConnector.connectBackwardVoid( canvas->selectedShapesProxy(), SIGNAL(selectionChanged()), &d->selectionChangedCompressor, SLOT(start())); d->shapeChangedAcyclicConnector.connectBackwardVoid( canvas->selectedShapesProxy(), SIGNAL(selectionContentChanged()), &d->selectionChangedCompressor, SLOT(start())); connect(&d->selectionChangedCompressor, SIGNAL(timeout()), this, SLOT(selectionChanged())); d->resourceManagerAcyclicConnector.connectBackwardResourcePair( canvas->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(canvasResourceChanged(int,QVariant))); d->canvas = canvas; } { d->fillConfigWidget = new KoFillConfigWidget(canvas, KoFlake::StrokeFill, true, this); d->fillConfigWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); d->ui->fillConfigWidgetLayout->addWidget(d->fillConfigWidget); connect(d->fillConfigWidget, SIGNAL(sigFillChanged()), SIGNAL(sigStrokeChanged())); } d->ui->thicknessLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); d->ui->thicknessLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // set min/max/step and value in points, then set actual unit d->ui->lineWidth->setMinMaxStep(0.5, 1000.0, 0.5); // if someone wants 0, just set to "none" on UI d->ui->lineWidth->setDecimals(2); d->ui->lineWidth->setUnit(KoUnit(KoUnit::Point)); d->ui->lineWidth->setToolTip(i18n("Set line width of actual selection")); d->ui->capNJoinButton->setMinimumHeight(25); d->capNJoinMenu = new CapNJoinMenu(this); d->ui->capNJoinButton->setMenu(d->capNJoinMenu); d->ui->capNJoinButton->setText("..."); d->ui->capNJoinButton->setPopupMode(QToolButton::InstantPopup); { // Line style d->ui->strokeStyleLabel->setText(i18n("Line Style:")); d->ui->strokeStyleLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); d->ui->lineStyle->setToolTip(i18nc("@info:tooltip", "Line style")); d->ui->lineStyle->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector()); } { QList emptyMarkers; d->startMarkerSelector = new KoMarkerSelector(KoFlake::StartMarker, this); d->startMarkerSelector->setToolTip(i18nc("@info:tooltip", "Start marker")); d->startMarkerSelector->updateMarkers(emptyMarkers); d->startMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); d->ui->markerLayout->addWidget(d->startMarkerSelector); d->midMarkerSelector = new KoMarkerSelector(KoFlake::MidMarker, this); d->midMarkerSelector->setToolTip(i18nc("@info:tooltip", "Node marker")); d->midMarkerSelector->updateMarkers(emptyMarkers); d->midMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); d->ui->markerLayout->addWidget(d->midMarkerSelector); d->endMarkerSelector = new KoMarkerSelector(KoFlake::EndMarker, this); d->endMarkerSelector->setToolTip(i18nc("@info:tooltip", "End marker")); d->endMarkerSelector->updateMarkers(emptyMarkers); d->endMarkerSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred ); d->ui->markerLayout->addWidget(d->endMarkerSelector); } // Spacer d->spacer = new QWidget(); d->spacer->setObjectName("SpecialSpacer"); d->ui->markerLayout->addWidget(d->spacer); connect(d->ui->lineStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(applyDashStyleChanges())); connect(d->ui->lineWidth, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyLineWidthChanges())); connect(d->capNJoinMenu->capGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges())); connect(d->capNJoinMenu->joinGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyJoinCapChanges())); connect(d->capNJoinMenu->miterLimit, SIGNAL(valueChangedPt(qreal)), this, SLOT(applyJoinCapChanges())); { // Map the marker signals correctly KisSignalMapper *mapper = new KisSignalMapper(this); connect(mapper, SIGNAL(mapped(int)), SLOT(applyMarkerChanges(int))); connect(d->startMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); connect(d->midMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); connect(d->endMarkerSelector, SIGNAL(currentIndexChanged(int)), mapper, SLOT(map())); mapper->setMapping(d->startMarkerSelector, KoFlake::StartMarker); mapper->setMapping(d->midMarkerSelector, KoFlake::MidMarker); mapper->setMapping(d->endMarkerSelector, KoFlake::EndMarker); } KoDocumentResourceManager *resourceManager = canvas->shapeController()->resourceManager(); if (resourceManager) { KoMarkerCollection *collection = resourceManager->resource(KoDocumentResourceManager::MarkerCollection).value(); if (collection) { updateMarkers(collection->markers()); } } d->selectionChangedCompressor.start(); d->fillConfigWidget->activate(); deactivate(); } KoStrokeConfigWidget::~KoStrokeConfigWidget() { delete d; } void KoStrokeConfigWidget::setNoSelectionTrackingMode(bool value) { d->fillConfigWidget->setNoSelectionTrackingMode(value); d->noSelectionTrackingMode = value; if (!d->noSelectionTrackingMode) { d->selectionChangedCompressor.start(); } } // ---------------------------------------------------------------- // getters and setters Qt::PenStyle KoStrokeConfigWidget::lineStyle() const { return d->ui->lineStyle->lineStyle(); } QVector KoStrokeConfigWidget::lineDashes() const { return d->ui->lineStyle->lineDashes(); } qreal KoStrokeConfigWidget::lineWidth() const { return d->ui->lineWidth->value(); } qreal KoStrokeConfigWidget::miterLimit() const { return d->capNJoinMenu->miterLimit->value(); } KoMarker *KoStrokeConfigWidget::startMarker() const { return d->startMarkerSelector->marker(); } KoMarker *KoStrokeConfigWidget::endMarker() const { return d->endMarkerSelector->marker(); } Qt::PenCapStyle KoStrokeConfigWidget::capStyle() const { return static_cast(d->capNJoinMenu->capGroup->checkedId()); } Qt::PenJoinStyle KoStrokeConfigWidget::joinStyle() const { return static_cast(d->capNJoinMenu->joinGroup->checkedId()); } KoShapeStrokeSP KoStrokeConfigWidget::createShapeStroke() { KoShapeStrokeSP stroke(d->fillConfigWidget->createShapeStroke()); stroke->setLineWidth(lineWidth()); stroke->setCapStyle(capStyle()); stroke->setJoinStyle(joinStyle()); stroke->setMiterLimit(miterLimit()); stroke->setLineStyle(lineStyle(), lineDashes()); return stroke; } // ---------------------------------------------------------------- // Other public functions void KoStrokeConfigWidget::updateStyleControlsAvailability(bool enabled) { d->ui->lineWidth->setEnabled(enabled); d->capNJoinMenu->setEnabled(enabled); d->ui->lineStyle->setEnabled(enabled); d->startMarkerSelector->setEnabled(enabled); d->midMarkerSelector->setEnabled(enabled); d->endMarkerSelector->setEnabled(enabled); } void KoStrokeConfigWidget::setUnit(const KoUnit &unit, KoShape *representativeShape) { if (!d->allowLocalUnitManagement) { return; //the unit management is completely transferred to the unitManagers. } blockChildSignals(true); /** * KoStrokeShape knows nothing about the transformations applied * to the shape, which doesn't prevent the shape to apply them and * display the stroke differently. So just take that into account * and show the user correct values using the multiplier in KoUnit. */ KoUnit newUnit(unit); if (representativeShape) { newUnit.adjustByPixelTransform(representativeShape->absoluteTransformation(0)); } d->ui->lineWidth->setUnit(newUnit); d->capNJoinMenu->miterLimit->setUnit(newUnit); d->ui->lineWidth->setLineStep(1.0); d->capNJoinMenu->miterLimit->setLineStep(1.0); blockChildSignals(false); } void KoStrokeConfigWidget::setUnitManagers(KisSpinBoxUnitManager* managerLineWidth, KisSpinBoxUnitManager *managerMitterLimit) { blockChildSignals(true); d->allowLocalUnitManagement = false; d->ui->lineWidth->setUnitManager(managerLineWidth); d->capNJoinMenu->miterLimit->setUnitManager(managerMitterLimit); blockChildSignals(false); } void KoStrokeConfigWidget::updateMarkers(const QList &markers) { d->startMarkerSelector->updateMarkers(markers); d->midMarkerSelector->updateMarkers(markers); d->endMarkerSelector->updateMarkers(markers); } void KoStrokeConfigWidget::activate() { - KIS_SAFE_ASSERT_RECOVER_RETURN(!d->deactivationLocks.empty()); d->deactivationLocks.clear(); d->fillConfigWidget->activate(); - if (!d->noSelectionTrackingMode) { - // selectionChanged(); d->selectionChangedCompressor.start(); } else { loadCurrentStrokeFillFromResourceServer(); } } void KoStrokeConfigWidget::deactivate() { - KIS_SAFE_ASSERT_RECOVER_RETURN(d->deactivationLocks.empty()); - + d->deactivationLocks.clear(); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->shapeChangedAcyclicConnector)); d->deactivationLocks.push_back(KisAcyclicSignalConnector::Blocker(d->resourceManagerAcyclicConnector)); d->fillConfigWidget->deactivate(); } void KoStrokeConfigWidget::blockChildSignals(bool block) { d->ui->lineWidth->blockSignals(block); d->capNJoinMenu->capGroup->blockSignals(block); d->capNJoinMenu->joinGroup->blockSignals(block); d->capNJoinMenu->miterLimit->blockSignals(block); d->ui->lineStyle->blockSignals(block); d->startMarkerSelector->blockSignals(block); d->midMarkerSelector->blockSignals(block); d->endMarkerSelector->blockSignals(block); } void KoStrokeConfigWidget::setActive(bool active) { d->active = active; } //------------------------ template auto applyChangeToStrokes(KoCanvasBase *canvas, ModifyFunction modifyFunction) -> decltype(modifyFunction(KoShapeStrokeSP()), void()) { KoSelection *selection = canvas->selectedShapesProxy()->selection(); if (!selection) return; QList shapes = selection->selectedEditableShapes(); KUndo2Command *command = KoFlake::modifyShapesStrokes(shapes, modifyFunction); if (command) { canvas->addCommand(command); } } void KoStrokeConfigWidget::applyDashStyleChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setLineStyle(lineStyle(), lineDashes()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyLineWidthChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setLineWidth(lineWidth()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyJoinCapChanges() { applyChangeToStrokes( d->canvas, [this] (KoShapeStrokeSP stroke) { stroke->setCapStyle(static_cast(d->capNJoinMenu->capGroup->checkedId())); stroke->setJoinStyle(static_cast(d->capNJoinMenu->joinGroup->checkedId())); stroke->setMiterLimit(miterLimit()); }); emit sigStrokeChanged(); } void KoStrokeConfigWidget::applyMarkerChanges(int rawPosition) { KoSelection *selection = d->canvas->selectedShapesProxy()->selection(); if (!selection) { emit sigStrokeChanged(); return; } QList shapes = selection->selectedEditableShapes(); QList pathShapes; Q_FOREACH (KoShape *shape, shapes) { KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { pathShapes << pathShape; } } if (pathShapes.isEmpty()) { emit sigStrokeChanged(); return; } KoFlake::MarkerPosition position = KoFlake::MarkerPosition(rawPosition); QScopedPointer marker; switch (position) { case KoFlake::StartMarker: if (d->startMarkerSelector->marker()) { marker.reset(new KoMarker(*d->startMarkerSelector->marker())); } break; case KoFlake::MidMarker: if (d->midMarkerSelector->marker()) { marker.reset(new KoMarker(*d->midMarkerSelector->marker())); } break; case KoFlake::EndMarker: if (d->endMarkerSelector->marker()) { marker.reset(new KoMarker(*d->endMarkerSelector->marker())); } break; } KUndo2Command* command = new KoPathShapeMarkerCommand(pathShapes, marker.take(), position); d->canvas->addCommand(command); emit sigStrokeChanged(); } // ---------------------------------------------------------------- struct CheckShapeStrokeStyleBasePolicy { typedef KoShapeStrokeSP PointerType; static PointerType getProperty(KoShape *shape) { return qSharedPointerDynamicCast(shape->stroke()); } }; struct CheckShapeStrokeDashesPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->lineStyle() == p2->lineStyle() && p1->lineDashes() == p2->lineDashes() && p1->dashOffset() == p2->dashOffset(); } }; struct CheckShapeStrokeCapJoinPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->capStyle() == p2->capStyle() && p1->joinStyle() == p2->joinStyle() && p1->miterLimit() == p2->miterLimit(); } }; struct CheckShapeStrokeWidthPolicy : public CheckShapeStrokeStyleBasePolicy { static bool compareTo(PointerType p1, PointerType p2) { return p1->lineWidth() == p2->lineWidth(); } }; struct CheckShapeMarkerPolicy { CheckShapeMarkerPolicy(KoFlake::MarkerPosition position) : m_position(position) { } typedef KoMarker* PointerType; PointerType getProperty(KoShape *shape) const { KoPathShape *pathShape = dynamic_cast(shape); return pathShape ? pathShape->marker(m_position) : 0; } bool compareTo(PointerType p1, PointerType p2) const { if ((!p1 || !p2) && p1 != p2) return false; if (!p1 && p1 == p2) return true; return p1 == p2 || *p1 == *p2; } KoFlake::MarkerPosition m_position; }; void KoStrokeConfigWidget::selectionChanged() { if (d->noSelectionTrackingMode) return; KoSelection *selection = d->canvas->selectedShapesProxy()->selection(); if (!selection) return; // we need to linearize update order, and force the child widget to update // before we start doing it QList shapes = selection->selectedEditableShapes(); d->fillConfigWidget->forceUpdateOnSelectionChanged(); // calls shapeChanged() logic KoShape *shape = !shapes.isEmpty() ? shapes.first() : 0; const KoShapeStrokeSP stroke = shape ? qSharedPointerDynamicCast(shape->stroke()) : KoShapeStrokeSP(); // setUnit uses blockChildSignals() so take care not to use it inside the block setUnit(d->canvas->unit(), shape); blockChildSignals(true); // line width if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { d->ui->lineWidth->changeValue(stroke->lineWidth()); } else { d->ui->lineWidth->changeValue(0); } // caps & joins if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { Qt::PenCapStyle capStyle = stroke->capStyle() >= 0 ? stroke->capStyle() : Qt::FlatCap; Qt::PenJoinStyle joinStyle = stroke->joinStyle() >= 0 ? stroke->joinStyle() : Qt::MiterJoin; { QAbstractButton *button = d->capNJoinMenu->capGroup->button(capStyle); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } { QAbstractButton *button = d->capNJoinMenu->joinGroup->button(joinStyle); KIS_SAFE_ASSERT_RECOVER_RETURN(button); button->setChecked(true); } d->capNJoinMenu->miterLimit->changeValue(stroke->miterLimit()); d->capNJoinMenu->miterLimit->setEnabled(joinStyle == Qt::MiterJoin); } else { d->capNJoinMenu->capGroup->button(Qt::FlatCap)->setChecked(true); d->capNJoinMenu->joinGroup->button(Qt::MiterJoin)->setChecked(true); d->capNJoinMenu->miterLimit->changeValue(0.0); d->capNJoinMenu->miterLimit->setEnabled(true); } // dashes style if (stroke && KoFlake::compareShapePropertiesEqual(shapes)) { d->ui->lineStyle->setLineStyle(stroke->lineStyle(), stroke->lineDashes()); } else { d->ui->lineStyle->setLineStyle(Qt::SolidLine, QVector()); } // markers KoPathShape *pathShape = dynamic_cast(shape); if (pathShape) { if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::StartMarker))) { d->startMarkerSelector->setMarker(pathShape->marker(KoFlake::StartMarker)); } if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::MidMarker))) { d->midMarkerSelector->setMarker(pathShape->marker(KoFlake::MidMarker)); } if (KoFlake::compareShapePropertiesEqual(shapes, CheckShapeMarkerPolicy(KoFlake::EndMarker))) { d->endMarkerSelector->setMarker(pathShape->marker(KoFlake::EndMarker)); } } const bool lineOptionsVisible = (d->fillConfigWidget->selectedFillIndex() != 0); // This switch statement is to help the tab widget "pages" to be closer to the correct size // if we don't do this the internal widgets get rendered, then the tab page has to get resized to // fill up the space, then the internal widgets have to resize yet again...causing flicker switch(d->fillConfigWidget->selectedFillIndex()) { case 0: // no fill this->setMinimumHeight(130); break; case 1: // solid fill this->setMinimumHeight(200); break; case 2: // gradient fill this->setMinimumHeight(350); case 3: // pattern fill break; } d->ui->thicknessLineBreak->setVisible(lineOptionsVisible); d->ui->lineWidth->setVisible(lineOptionsVisible); d->ui->capNJoinButton->setVisible(lineOptionsVisible); d->ui->lineStyle->setVisible(lineOptionsVisible); d->startMarkerSelector->setVisible(lineOptionsVisible); d->midMarkerSelector->setVisible(lineOptionsVisible); d->endMarkerSelector->setVisible(lineOptionsVisible); d->ui->thicknessLabel->setVisible(lineOptionsVisible); d->ui->strokeStyleLabel->setVisible(lineOptionsVisible); blockChildSignals(false); updateStyleControlsAvailability(!shapes.isEmpty()); } void KoStrokeConfigWidget::canvasResourceChanged(int key, const QVariant &value) { switch (key) { case KoCanvasResourceProvider::Unit: // we request the whole selection to reload because the // unit of the stroke width depends on the selected shape d->selectionChangedCompressor.start(); break; case KisCanvasResourceProvider::Size: if (d->noSelectionTrackingMode) { d->ui->lineWidth->changeValue(d->canvas->unit().fromUserValue(value.toReal())); } break; } } void KoStrokeConfigWidget::loadCurrentStrokeFillFromResourceServer() { if (d->canvas) { const QVariant value = d->canvas->resourceManager()->resource(KisCanvasResourceProvider::Size); canvasResourceChanged(KisCanvasResourceProvider::Size, value); updateStyleControlsAvailability(true); emit sigStrokeChanged(); } } diff --git a/libs/ui/widgets/kis_cmb_gradient.cpp b/libs/ui/widgets/kis_cmb_gradient.cpp index 5771502306..cfa82ac91a 100644 --- a/libs/ui/widgets/kis_cmb_gradient.cpp +++ b/libs/ui/widgets/kis_cmb_gradient.cpp @@ -1,78 +1,77 @@ /* * Copyright (c) 2015 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cmb_gradient.h" #include #include #include #include #include #include #include #include "kis_gradient_chooser.h" KisCmbGradient::KisCmbGradient(QWidget *parent) : KisPopupButton(parent) , m_gradientChooser(new KisGradientChooser(this)) { connect(m_gradientChooser, SIGNAL(resourceSelected(KoResourceSP )), SLOT(gradientSelected(KoResourceSP ))); setPopupWidget(m_gradientChooser); } void KisCmbGradient::setGradient(KoAbstractGradientSP gradient) { m_gradientChooser->setCurrentResource(gradient); } KoAbstractGradientSP KisCmbGradient::gradient() const { return m_gradientChooser->currentResource().dynamicCast(); } void KisCmbGradient::gradientSelected(KoResourceSP resource) { KoAbstractGradientSP gradient = resource.dynamicCast(); if (!gradient) return; QImage pm = gradient->generatePreview(iconSize().width(), iconSize().height()); setIcon(QIcon(QPixmap::fromImage(pm))); emit gradientChanged(gradient); } QSize KisCmbGradient::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = 7 * fm.width(QChar('x')) + 18; int maxH = qMax(fm.lineSpacing(), 14) + 2; QStyleOptionComboBox options; options.initFrom(this); - return style()->sizeFromContents(QStyle::CT_ComboBox, &options, - QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this); } void KisCmbGradient::resizeEvent(QResizeEvent *event) { setIconSize(QSize(event->size().width() - 30, event->size().height() - 4)); KisPopupButton::resizeEvent(event); } diff --git a/libs/ui/widgets/kis_color_filter_combo.cpp b/libs/ui/widgets/kis_color_filter_combo.cpp index 518d2e8eb7..224054391f 100644 --- a/libs/ui/widgets/kis_color_filter_combo.cpp +++ b/libs/ui/widgets/kis_color_filter_combo.cpp @@ -1,348 +1,371 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_filter_combo.h" #include #include #include #include +#include #include #include #include #include #include #include "kis_node_view_color_scheme.h" #include "kis_debug.h" #include "kis_icon_utils.h" #include "krita_utils.h" #include "kis_node.h" enum AdditionalRoles { OriginalLabelIndex = Qt::UserRole + 1000 }; struct LabelFilteringModel : public QSortFilterProxyModel { LabelFilteringModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const int labelIndex = index.data(OriginalLabelIndex).toInt(); return labelIndex < 0 || m_acceptedLabels.contains(labelIndex); } void setAcceptedLabels(const QSet &value) { m_acceptedLabels = value; invalidateFilter(); } private: QSet m_acceptedLabels; }; class ComboEventFilter : public QObject { public: ComboEventFilter(KisColorFilterCombo *parent) : m_parent(parent), m_buttonPressed(false) {} protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::Leave) { m_buttonPressed = false; } else if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mevent = static_cast(event); m_buttonPressed = mevent->button() == Qt::LeftButton; } else if (event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mevent = static_cast(event); QModelIndex index = m_parent->view()->indexAt(mevent->pos()); if (!index.isValid()) return false; /** * We should eat the first event that arrives exactly when * the drop down appears on screen. */ if (!m_buttonPressed) return true; const bool toUncheckedState = index.data(Qt::CheckStateRole) == Qt::Checked; if (toUncheckedState) { m_parent->model()->setData(index, Qt::Unchecked, Qt::CheckStateRole); } else { m_parent->model()->setData(index, Qt::Checked, Qt::CheckStateRole); } if (index.data(OriginalLabelIndex).toInt() == -1) { for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { m_parent->model()->setData(other, toUncheckedState ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); } } } else { bool prevChecked = false; bool checkedVaries = false; QModelIndex allLabelsIndex; for (int i = 0; i < m_parent->model()->rowCount(); i++) { const QModelIndex &other = m_parent->model()->index(i, 0); if (other.data(OriginalLabelIndex) != -1) { const bool currentChecked = other.data(Qt::CheckStateRole) == Qt::Checked; if (i == 0) { prevChecked = currentChecked; } else { if (prevChecked != currentChecked) { checkedVaries = true; break; } } } else { allLabelsIndex = other; } } const bool allLabelsIndexShouldBeChecked = prevChecked && !checkedVaries; if (allLabelsIndexShouldBeChecked != (allLabelsIndex.data(Qt::CheckStateRole) == Qt::Checked)) { m_parent->model()->setData(allLabelsIndex, allLabelsIndexShouldBeChecked ? Qt::Checked : Qt::Unchecked, Qt::CheckStateRole); } } emit m_parent->selectedColorsChanged(); m_buttonPressed = false; return true; } return QObject::eventFilter(obj, event); } private: KisColorFilterCombo *m_parent; bool m_buttonPressed; }; class FullSizedListView : public QListView { public: QSize sizeHint() const override { return contentsSize(); } }; +class PopupComboBoxStyle : public QProxyStyle +{ +public: + PopupComboBoxStyle(QStyle *baseStyle = nullptr) : QProxyStyle(baseStyle) {} + + int styleHint(QStyle::StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const override + { + // This flag makes ComboBox popup float ontop of its parent ComboBox, like in Fusion style. + // Only when this hint is set will Qt respect combobox popup size hints, otherwise the popup + // can never exceed the width of its parent ComboBox, like in Breeze style. + if (hint == QStyle::SH_ComboBox_Popup) { + return true; + } + + return QProxyStyle::styleHint(hint, option, widget, returnData); + } +}; + struct KisColorFilterCombo::Private { LabelFilteringModel *filteringModel; }; KisColorFilterCombo::KisColorFilterCombo(QWidget *parent) : QComboBox(parent), m_d(new Private) { QStandardItemModel *newModel = new QStandardItemModel(this); setModel(newModel); + PopupComboBoxStyle *proxyStyle = new PopupComboBoxStyle(style()); + proxyStyle->setParent(this); + setStyle(proxyStyle); + setView(new FullSizedListView); m_eventFilters.append(new ComboEventFilter(this)); m_eventFilters.append(new ComboEventFilter(this)); view()->installEventFilter(m_eventFilters[0]); view()->viewport()->installEventFilter(m_eventFilters[1]); KisNodeViewColorScheme scm; QStandardItem* item = new QStandardItem(i18nc("combo box: show all layers", "All")); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(QColor(Qt::transparent), Qt::BackgroundColorRole); item->setData(int(-1), OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); int labelIndex = 0; foreach (const QColor &color, scm.allColorLabels()) { const QString title = color.alpha() > 0 ? "" : i18nc("combo box: select all layers without a label", "No Label"); QStandardItem* item = new QStandardItem(title); item->setCheckable(true); item->setCheckState(Qt::Unchecked); item->setData(color, Qt::BackgroundColorRole); item->setData(labelIndex, OriginalLabelIndex); item->setData(QSize(30, scm.rowHeight()), Qt::SizeHintRole); newModel->appendRow(item); labelIndex++; } m_d->filteringModel = new LabelFilteringModel(this); QAbstractItemModel *originalModel = model(); originalModel->setParent(m_d->filteringModel); m_d->filteringModel->setSourceModel(originalModel); setModel(m_d->filteringModel); } KisColorFilterCombo::~KisColorFilterCombo() { qDeleteAll(m_eventFilters); } void collectAvailableLabels(KisNodeSP root, QSet *labels) { labels->insert(root->colorLabelIndex()); KisNodeSP node = root->firstChild(); while (node) { collectAvailableLabels(node, labels); node = node->nextSibling(); } } void KisColorFilterCombo::updateAvailableLabels(KisNodeSP rootNode) { QSet labels; collectAvailableLabels(rootNode, &labels); updateAvailableLabels(labels); } void KisColorFilterCombo::updateAvailableLabels(const QSet &labels) { m_d->filteringModel->setAcceptedLabels(labels); } QList KisColorFilterCombo::selectedColors() const { QList colors; for (int i = 0; i < model()->rowCount(); i++) { const QModelIndex &other = model()->index(i, 0); const int label = other.data(OriginalLabelIndex).toInt(); if (label != -1 && other.data(Qt::CheckStateRole) == Qt::Checked) { colors << label; } } return colors; } void KisColorFilterCombo::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QStylePainter painter(this); painter.setPen(palette().color(QPalette::Text)); // draw the combobox frame, focusrect and selected etc. QStyleOptionComboBox opt; initStyleOption(&opt); painter.drawComplexControl(QStyle::CC_ComboBox, opt); { KisNodeViewColorScheme scm; const QRect editRect = style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this); const int size = qMin(editRect.width(), editRect.height()); const QList selectedColors = this->selectedColors(); if (selectedColors.size() == 0 || selectedColors.size() == model()->rowCount() - 1) { QIcon icon = KisIconUtils::loadIcon("view-filter"); QPixmap pixmap = icon.pixmap(QSize(size, size), !isEnabled() ? QIcon::Disabled : QIcon::Normal); painter.drawPixmap(editRect.right() - size, editRect.top(), pixmap); } else if (selectedColors.size() == 1) { const int currentLabel = selectedColors.first(); QColor currentColor = scm.colorLabel(currentLabel); if (currentColor.alpha() > 0) { painter.fillRect(editRect, currentColor); } else if (currentLabel == 0) { QPen oldPen = painter.pen(); const int border = 4; QRect crossRect(0, 0, size - 2 * border, size - 2 * border); crossRect.moveCenter(editRect.center()); QColor shade = opt.palette.dark().color(); painter.setPen(QPen(shade, 2)); painter.drawLine(crossRect.topLeft(), crossRect.bottomRight()); painter.drawLine(crossRect.bottomLeft(), crossRect.topRight()); } } else { const int border = 0; QRect pieRect(0, 0, size - 2 * border, size - 2 * border); pieRect.moveCenter(editRect.center()); const int numColors = selectedColors.size(); const int step = 16 * 360 / numColors; int currentAngle = 0; //painter.save(); // optimize out! painter.setRenderHint(QPainter::Antialiasing); for (int i = 0; i < numColors; i++) { QColor color = scm.colorLabel(selectedColors[i]); QBrush brush = color.alpha() > 0 ? QBrush(color) : QBrush(Qt::black, Qt::Dense4Pattern); painter.setPen(color); painter.setBrush(brush); painter.drawPie(pieRect, currentAngle, step); currentAngle += step; } //painter.restore(); // optimize out! } } // draw the icon and text //painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } QSize KisColorFilterCombo::minimumSizeHint() const { return sizeHint(); } QSize KisColorFilterCombo::sizeHint() const { QStyleOptionComboBox opt; initStyleOption(&opt); const QStyleOption *baseOption = qstyleoption_cast(&opt); const int arrowSize = style()->pixelMetric(QStyle::PM_ScrollBarExtent, baseOption, this); const QSize originalHint = QComboBox::sizeHint(); QSize sh(3 * arrowSize, originalHint.height()); - return sh.expandedTo(QApplication::globalStrut()); + return sh; } diff --git a/libs/ui/widgets/kis_slider_spin_box.cpp b/libs/ui/widgets/kis_slider_spin_box.cpp index 8db1984887..5e36564366 100644 --- a/libs/ui/widgets/kis_slider_spin_box.cpp +++ b/libs/ui/widgets/kis_slider_spin_box.cpp @@ -1,1052 +1,1052 @@ /* This file is part of the KDE project * Copyright (c) 2010 Justin Noel * Copyright (c) 2010 Cyrille Berger * Copyright (c) 2015 Moritz Molch * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_slider_spin_box.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_cursor.h" #include "KisPart.h" #include "input/kis_input_manager.h" #include "kis_num_parser.h" class KisAbstractSliderSpinBoxPrivate { public: enum Style { STYLE_NOQUIRK, STYLE_PLASTIQUE, STYLE_BREEZE, STYLE_FUSION, }; QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; qreal slowFactor; qreal shiftPercent; bool shiftMode; QString prefix; QString suffix; qreal exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; Style style; bool blockUpdateSignalOnDrag; bool isDragging; bool parseInt; }; KisAbstractSliderSpinBox::KisAbstractSliderSpinBox(QWidget* parent, KisAbstractSliderSpinBoxPrivate* _d) : QWidget(parent) , d_ptr(_d) { Q_D(KisAbstractSliderSpinBox); QEvent e(QEvent::StyleChange); changeEvent(&e); d->upButtonDown = false; d->downButtonDown = false; d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->setContentsMargins(0,0,0,0); d->edit->installEventFilter(this); //Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); d->edit->setContextMenuPolicy(Qt::PreventContextMenu); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->value = 0; d->minimum = 0; d->maximum = 100; d->factor = 1.0; d->singleStep = 1; d->fastSliderStep = 5; d->slowFactor = 0.1; d->shiftMode = false; d->blockUpdateSignalOnDrag = false; d->isDragging = false; d->parseInt = false; setExponentRatio(1.0); setCursor(KisCursor::splitHCursor()); //Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); //dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } KisAbstractSliderSpinBox::~KisAbstractSliderSpinBox() { Q_D(KisAbstractSliderSpinBox); delete d; } void KisAbstractSliderSpinBox::showEdit() { Q_D(KisAbstractSliderSpinBox); if (d->edit->isVisible()) return; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE) { d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0,0,-2,0)); } else { d->edit->setGeometry(progressRect(spinBoxOptions())); } d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); } void KisAbstractSliderSpinBox::hideEdit() { Q_D(KisAbstractSliderSpinBox); d->edit->hide(); update(); } void KisAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(KisAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: paintFusion(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: paintPlastique(painter); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: paintBreeze(painter); break; default: paint(painter); break; } painter.end(); } void KisAbstractSliderSpinBox::paint(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); //Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.rect.adjust(0, 2, 0, -2); //Draw "SpinBox".Clip off the area of the lineEdit to avoid double //borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(progressRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); QStyleOptionProgressBar progressOpts = progressBarOptions(); progressOpts.rect.adjust(0, 2, 0, -2); style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); //Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void KisAbstractSliderSpinBox::paintFusion(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); spinOpts.frame = true; spinOpts.rect.adjust(0, -1, 0, 1); //spinOpts.palette().setBrush(QPalette::Base, palette().highlight()); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(1,2,-4,-2); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setClipRect(leftRect.adjusted(0, -1, 1, 1)); painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); spinOpts.palette.setBrush(QPalette::Base, palette().highlight()); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); } painter.setClipping(false); } painter.restore(); } void KisAbstractSliderSpinBox::paintPlastique(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.save(); QRect rect = progressOpts.rect.adjusted(2,0,-2,0); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width()) { leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height()); } else if (progressIndicatorPos > rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = rect; rightRect = rightRect.subtracted(leftRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.setClipRegion(rightRect); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } if (!leftRect.isNull()) { painter.setPen(palette().highlight().color()); painter.setBrush(palette().highlight()); painter.drawRect(leftRect.adjusted(0,0,0,-1)); if (!(d->edit && d->edit->isVisible())) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect.adjusted(0,0,1,0)); painter.setClipping(true); painter.drawText(rect.adjusted(-2,0,2,0), progressOpts.text, textOption); painter.setClipping(false); } } painter.restore(); } void KisAbstractSliderSpinBox::paintBreeze(QPainter &painter) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); QString valueText = progressOpts.text; progressOpts.text = ""; progressOpts.rect.adjust(0, 1, 0, -1); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this); style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this); painter.save(); QRect leftRect; int progressIndicatorPos = (progressOpts.progress - qreal(progressOpts.minimum)) / qMax(qreal(1.0), qreal(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width(); if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width()) { leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height()); } else if (progressIndicatorPos > progressOpts.rect.width()) { painter.setPen(palette().highlightedText().color()); } else { painter.setPen(palette().buttonText().color()); } QRegion rightRect = progressOpts.rect; rightRect = rightRect.subtracted(leftRect); painter.setClipRegion(rightRect); QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter); textOption.setWrapMode(QTextOption::NoWrap); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } if (!leftRect.isNull()) { painter.setPen(palette().highlightedText().color()); painter.setClipRect(leftRect); style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this); if (!(d->edit && d->edit->isVisible())) { painter.drawText(progressOpts.rect, valueText, textOption); } } painter.restore(); } void KisAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Depress buttons or highlight slider //Also used to emulate mouse grab... if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } update(); } void KisAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); d->isDragging = false; //Step up/down for buttons //Emualting mouse grab too if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (progressRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { //Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } else { // Confirm the last known value, since we might be ignoring move events setInternalValue(d->value); } d->upButtonDown = false; d->downButtonDown = false; update(); } void KisAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(KisAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; } } else { d->shiftMode = false; } //Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { d->isDragging = true; setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag); update(); } } void KisAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(KisAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( qreal(d->value - d->minimum)/qreal(d->maximum - d->minimum), 1/qreal(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: //Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void KisAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(KisAbstractSliderSpinBox); if ( e->delta() > 0) { setInternalValue(d->value + d->singleStep); } else { setInternalValue(d->value - d->singleStep); } update(); e->accept(); } bool KisAbstractSliderSpinBox::event(QEvent *event) { if (event->type() == QEvent::ShortcutOverride){ QKeyEvent* key = static_cast(event); if (key->modifiers() == Qt::NoModifier){ switch(key->key()){ case Qt::Key_Up: case Qt::Key_Right: case Qt::Key_Down: case Qt::Key_Left: event->accept(); return true; default: break; } } } return QWidget::event(event); } void KisAbstractSliderSpinBox::commitEnteredValue() { Q_D(KisAbstractSliderSpinBox); //QLocale locale; bool ok = false; //qreal value = locale.toDouble(d->edit->text(), &ok) * d->factor; qreal value; if (d->parseInt) { value = KisNumericParser::parseIntegerMathExpr(d->edit->text(), &ok) * d->factor; } else { value = KisNumericParser::parseSimpleMathExpr(d->edit->text(), &ok) * d->factor; } if (ok) { setInternalValue(value); } } bool KisAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(KisAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: { commitEnteredValue(); hideEdit(); return true; } case Qt::Key_Escape: d->edit->setText(valueString()); hideEdit(); return true; default: break; } } return false; } QSize KisAbstractSliderSpinBox::sizeHint() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFont ft(font()); if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK) { // Some styles use bold font in progressbars // unfortunately there is no reliable way to check for that ft.setBold(true); } QFontMetrics fm(ft); QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size()); hint += QSize(0, 2); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_FUSION: hint += QSize(8, 8); break; case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: hint += QSize(8, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: hint += QSize(2, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK: // almost all "modern" styles have a margin around controls hint += QSize(6, 6); break; default: break; } //Getting the size of the buttons is a pain as the calcs require a rect //that is "big enough". We run the calc twice to get the "smallest" buttons //This code was inspired by QAbstractSpinBox QSize extra(1000, 0); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect.setSize(hint); return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint) .expandedTo(QApplication::globalStrut()); } QSize KisAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QSize KisAbstractSliderSpinBox::minimumSize() const { - return QWidget::minimumSize().expandedTo(minimumSizeHint()); + return QWidget::minimumSize(); } QStyleOptionSpinBox KisAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; //Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } //Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar KisAbstractSliderSpinBox::progressBarOptions() const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); //Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; qreal minDbl = d->minimum; qreal dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = d->prefix + valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); //Change opts rect to be only the ComboBox's text area progressOpts.rect = progressRect(spinOpts); return progressOpts; } QRect KisAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const { const Q_D(KisAbstractSliderSpinBox); QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); switch (d->style) { case KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE: ret.adjust(-2, 0, 1, 0); break; case KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE: ret.adjust(1, 0, 0, 0); break; default: break; } return ret; } QRect KisAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect KisAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int KisAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(KisAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QRect correctedProgRect; if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_FUSION) { correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0); } else if (d->style == KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE) { correctedProgRect = progressRect(spinOpts); } else { //Adjust for magic number in style code (margins) correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2); } //Compute the distance of the progress bar, in pixel qreal leftDbl = correctedProgRect.left(); qreal xDbl = x - leftDbl; //Compute the ration of the progress bar used, linearly (ignoring the exponent) qreal rightDbl = correctedProgRect.right(); qreal minDbl = d->minimum; qreal maxDbl = d->maximum; qreal dValues = (maxDbl - minDbl); qreal percent = (xDbl / (rightDbl - leftDbl)); //If SHIFT is pressed, movement should be slowed. if( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + ( percent - d->shiftPercent ) * d->slowFactor; } //Final value qreal exp_percent = pow(percent, d->exponentRatio); qreal realvalue = ((dValues * (percent * exp_percent >= 0 ? exp_percent : -exp_percent)) + minDbl); //If key CTRL is pressed, round to the closest step. if( modifiers & Qt::ControlModifier ) { qreal fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep*=d->slowFactor; } realvalue = floor( (realvalue+fstep/2) / fstep ) * fstep; } //Return the value return int(realvalue); } void KisAbstractSliderSpinBox::setPrefix(const QString& prefix) { Q_D(KisAbstractSliderSpinBox); d->prefix = prefix; } void KisAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(KisAbstractSliderSpinBox); d->suffix = suffix; } void KisAbstractSliderSpinBox::setExponentRatio(qreal dbl) { Q_D(KisAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void KisAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->blockUpdateSignalOnDrag = blockUpdateSignal; } void KisAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void KisAbstractSliderSpinBox::editLostFocus() { Q_D(KisAbstractSliderSpinBox); if (!d->edit->hasFocus()) { commitEnteredValue(); hideEdit(); } } void KisAbstractSliderSpinBox::setInternalValue(int value) { setInternalValue(value, false); } bool KisAbstractSliderSpinBox::isDragging() const { Q_D(const KisAbstractSliderSpinBox); return d->isDragging; } void KisAbstractSliderSpinBox::setPrivateValue(int value) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, value, d->maximum); } class KisSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisSliderSpinBox::KisSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisSliderSpinBoxPrivate) { Q_D(KisSliderSpinBox); d->parseInt = true; setRange(0,99); } KisSliderSpinBox::~KisSliderSpinBox() { } void KisSliderSpinBox::setRange(int minimum, int maximum) { Q_D(KisSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int KisSliderSpinBox::minimum() const { const Q_D(KisSliderSpinBox); return d->minimum; } void KisSliderSpinBox::setMinimum(int minimum) { Q_D(KisSliderSpinBox); setRange(minimum, d->maximum); } int KisSliderSpinBox::maximum() const { const Q_D(KisSliderSpinBox); return d->maximum; } void KisSliderSpinBox::setMaximum(int maximum) { Q_D(KisSliderSpinBox); setRange(d->minimum, maximum); } int KisSliderSpinBox::fastSliderStep() const { const Q_D(KisSliderSpinBox); return d->fastSliderStep; } void KisSliderSpinBox::setFastSliderStep(int step) { Q_D(KisSliderSpinBox); d->fastSliderStep = step; } int KisSliderSpinBox::value() { Q_D(KisSliderSpinBox); return d->value; } void KisSliderSpinBox::setValue(int value) { setInternalValue(value, false); update(); } QString KisSliderSpinBox::valueString() const { const Q_D(KisSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value, 'f', d->validator->decimals()); } void KisSliderSpinBox::setSingleStep(int value) { Q_D(KisSliderSpinBox); d->singleStep = value; } void KisSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void KisSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } class KisDoubleSliderSpinBoxPrivate : public KisAbstractSliderSpinBoxPrivate { }; KisDoubleSliderSpinBox::KisDoubleSliderSpinBox(QWidget* parent) : KisAbstractSliderSpinBox(parent, new KisDoubleSliderSpinBoxPrivate) { Q_D(KisDoubleSliderSpinBox); d->parseInt = false; } KisDoubleSliderSpinBox::~KisDoubleSliderSpinBox() { } void KisDoubleSliderSpinBox::setRange(qreal minimum, qreal maximum, int decimals) { Q_D(KisDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; //This code auto-compute a new step when pressing control. //A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if(maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } qreal KisDoubleSliderSpinBox::minimum() const { const Q_D(KisAbstractSliderSpinBox); return d->minimum / d->factor; } void KisDoubleSliderSpinBox::setMinimum(qreal minimum) { Q_D(KisAbstractSliderSpinBox); setRange(minimum, d->maximum); } qreal KisDoubleSliderSpinBox::maximum() const { const Q_D(KisAbstractSliderSpinBox); return d->maximum / d->factor; } void KisDoubleSliderSpinBox::setMaximum(qreal maximum) { Q_D(KisAbstractSliderSpinBox); setRange(d->minimum, maximum); } qreal KisDoubleSliderSpinBox::fastSliderStep() const { const Q_D(KisAbstractSliderSpinBox); return d->fastSliderStep; } void KisDoubleSliderSpinBox::setFastSliderStep(qreal step) { Q_D(KisAbstractSliderSpinBox); d->fastSliderStep = step; } qreal KisDoubleSliderSpinBox::value() { Q_D(KisAbstractSliderSpinBox); return (qreal)d->value / d->factor; } void KisDoubleSliderSpinBox::setValue(qreal value) { Q_D(KisAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor), false); update(); } void KisDoubleSliderSpinBox::setSingleStep(qreal value) { Q_D(KisAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString KisDoubleSliderSpinBox::valueString() const { const Q_D(KisAbstractSliderSpinBox); QLocale locale; return locale.toString((qreal)d->value / d->factor, 'f', d->validator->decimals()); } void KisDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal) { Q_D(KisAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); if(!blockUpdateSignal) { emit(valueChanged(value())); } } void KisAbstractSliderSpinBox::changeEvent(QEvent *e) { Q_D(KisAbstractSliderSpinBox); QWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: if (style()->objectName() == "fusion") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_FUSION; } else if (style()->objectName() == "plastique") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE; } else if (style()->objectName() == "breeze") { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_BREEZE; } else { d->style = KisAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK; } break; default: break; } } diff --git a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp index 0937a7063c..259f958594 100644 --- a/libs/ui/widgets/kis_stopgradient_slider_widget.cpp +++ b/libs/ui/widgets/kis_stopgradient_slider_widget.cpp @@ -1,368 +1,367 @@ /* * Copyright (c) 2004 Cyrille Berger * 2016 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. */ #include "widgets/kis_stopgradient_slider_widget.h" #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "kis_debug.h" #include "krita_utils.h" KisStopGradientSliderWidget::KisStopGradientSliderWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) , m_selectedStop(0) , m_drag(0) { QLinearGradient defaultGradient; m_defaultGradient = KoStopGradient::fromQGradient(&defaultGradient); setGradientResource(0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); setMouseTracking(true); QWindow *window = this->window()->windowHandle(); if (window) { connect(window, SIGNAL(screenChanged(QScreen*)), SLOT(updateHandleSize())); } updateHandleSize(); } void KisStopGradientSliderWidget::updateHandleSize() { QFontMetrics fm(font()); const int h = fm.height(); m_handleSize = QSize(0.34 * h, h); } int KisStopGradientSliderWidget::handleClickTolerance() const { // the size of the default text! return m_handleSize.width(); } void KisStopGradientSliderWidget::setGradientResource(KoStopGradientSP gradient) { m_gradient = gradient ? gradient : m_defaultGradient; if (m_gradient && m_selectedStop >= 0) { m_selectedStop = qBound(0, m_selectedStop, m_gradient->stops().size() - 1); emit sigSelectedStop(m_selectedStop); } else { m_selectedStop = -1; } } void KisStopGradientSliderWidget::paintHandle(qreal position, const QColor &color, bool isSelected, QPainter *painter) { const QRect handlesRect = this->handlesStipeRect(); const int handleCenter = handlesRect.left() + position * handlesRect.width(); const int handlesHalfWidth = handlesRect.height() * 0.26; // = 1.0 / 0.66 * 0.34 / 2.0 <-- golden ratio QPolygon triangle(3); triangle[0] = QPoint(handleCenter, handlesRect.top()); triangle[1] = QPoint(handleCenter - handlesHalfWidth, handlesRect.bottom()); triangle[2] = QPoint(handleCenter + handlesHalfWidth, handlesRect.bottom()); const qreal lineWidth = 1.0; if (!isSelected) { painter->setPen(QPen(palette().text(), lineWidth)); painter->setBrush(QBrush(color)); painter->setRenderHint(QPainter::Antialiasing); painter->drawPolygon(triangle); } else { painter->setPen(QPen(palette().highlight(), 1.5 * lineWidth)); painter->setBrush(QBrush(color)); painter->setRenderHint(QPainter::Antialiasing); painter->drawPolygon(triangle); } } void KisStopGradientSliderWidget::paintEvent(QPaintEvent* pe) { QWidget::paintEvent(pe); QPainter painter(this); painter.setPen(Qt::black); const QRect previewRect = gradientStripeRect(); KritaUtils::renderExactRect(&painter, kisGrowRect(previewRect, 1)); painter.drawRect(previewRect); if (m_gradient) { QImage image = m_gradient->generatePreview(previewRect.width(), previewRect.height()); if (!image.isNull()) { painter.drawImage(previewRect.topLeft(), image); } QList handlePositions = m_gradient->stops(); for (int i = 0; i < handlePositions.count(); i++) { if (i == m_selectedStop) continue; paintHandle(handlePositions[i].first, handlePositions[i].second.toQColor(), false, &painter); } if (m_selectedStop >= 0) { paintHandle(handlePositions[m_selectedStop].first, handlePositions[m_selectedStop].second.toQColor(), true, &painter); } } } int findNearestHandle(qreal t, const qreal tolerance, const QList &stops) { int result = -1; qreal minDistance = tolerance; for (int i = 0; i < stops.size(); i++) { const KoGradientStop &stop = stops[i]; const qreal distance = qAbs(t - stop.first); if (distance < minDistance) { minDistance = distance; result = i; } } return result; } void KisStopGradientSliderWidget::mousePressEvent(QMouseEvent * e) { if (!allowedClickRegion(handleClickTolerance()).contains(e->pos())) { QWidget::mousePressEvent(e); return; } if (e->buttons() != Qt::LeftButton ) { QWidget::mousePressEvent(e); return; } const QRect handlesRect = this->handlesStipeRect(); const qreal t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width(); const QList stops = m_gradient->stops(); const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops); if (clickedStop >= 0) { if (m_selectedStop != clickedStop) { m_selectedStop = clickedStop; emit sigSelectedStop(m_selectedStop); } m_drag = true; } else { insertStop(qBound(0.0, t, 1.0)); m_drag = true; } update(); updateCursor(e->pos()); } void KisStopGradientSliderWidget::mouseReleaseEvent(QMouseEvent * e) { Q_UNUSED(e); m_drag = false; updateCursor(e->pos()); } int getNewInsertPosition(const KoGradientStop &stop, const QList &stops) { int result = 0; for (int i = 0; i < stops.size(); i++) { if (stop.first <= stops[i].first) break; result = i + 1; } return result; } void KisStopGradientSliderWidget::mouseMoveEvent(QMouseEvent * e) { updateCursor(e->pos()); if (m_drag) { const QRect handlesRect = this->handlesStipeRect(); double t = (qreal(e->x()) - handlesRect.x()) / handlesRect.width(); QList stops = m_gradient->stops(); if (t < -0.1 || t > 1.1) { if (stops.size() > 2 && m_selectedStop >= 0) { m_removedStop = stops[m_selectedStop]; stops.removeAt(m_selectedStop); m_selectedStop = -1; } } else { if (m_selectedStop < 0) { m_removedStop.first = qBound(0.0, t, 1.0); const int newPos = getNewInsertPosition(m_removedStop, stops); stops.insert(newPos, m_removedStop); m_selectedStop = newPos; } else { KoGradientStop draggedStop = stops[m_selectedStop]; draggedStop.first = qBound(0.0, t, 1.0); stops.removeAt(m_selectedStop); const int newPos = getNewInsertPosition(draggedStop, stops); stops.insert(newPos, draggedStop); m_selectedStop = newPos; } } m_gradient->setStops(stops); emit sigSelectedStop(m_selectedStop); update(); } else { QWidget::mouseMoveEvent(e); } } void KisStopGradientSliderWidget::updateCursor(const QPoint &pos) { const bool isInAllowedRegion = allowedClickRegion(handleClickTolerance()).contains(pos); QCursor currentCursor; if (isInAllowedRegion) { const QRect handlesRect = this->handlesStipeRect(); const qreal t = (qreal(pos.x()) - handlesRect.x()) / handlesRect.width(); const QList stops = m_gradient->stops(); const int clickedStop = findNearestHandle(t, qreal(handleClickTolerance()) / handlesRect.width(), stops); if (clickedStop >= 0) { currentCursor = m_drag ? Qt::ClosedHandCursor : Qt::OpenHandCursor; } } if (currentCursor.shape() != Qt::ArrowCursor) { setCursor(currentCursor); } else { unsetCursor(); } } void KisStopGradientSliderWidget::insertStop(double t) { KIS_ASSERT_RECOVER(t >= 0 && t <= 1.0 ) { t = qBound(0.0, t, 1.0); } QList stops = m_gradient->stops(); KoColor color; m_gradient->colorAt(color, t); const KoGradientStop stop(t, color); const int newPos = getNewInsertPosition(stop, stops); stops.insert(newPos, stop); m_gradient->setStops(stops); m_selectedStop = newPos; emit sigSelectedStop(m_selectedStop); } QRect KisStopGradientSliderWidget::sliderRect() const { return QRect(QPoint(), size()).adjusted(m_handleSize.width(), 1, -m_handleSize.width(), -1); } QRect KisStopGradientSliderWidget::gradientStripeRect() const { const QRect rc = sliderRect(); return rc.adjusted(0, 0, 0, -m_handleSize.height()); } QRect KisStopGradientSliderWidget::handlesStipeRect() const { const QRect rc = sliderRect(); return rc.adjusted(0, rc.height() - m_handleSize.height(), 0, 0); } QRegion KisStopGradientSliderWidget::allowedClickRegion(int tolerance) const { QRegion result; result += sliderRect(); result += handlesStipeRect().adjusted(-tolerance, 0, tolerance, 0); return result; } int KisStopGradientSliderWidget::selectedStop() { return m_selectedStop; } void KisStopGradientSliderWidget::setSelectedStop(int selected) { m_selectedStop = selected; emit sigSelectedStop(m_selectedStop); update(); } int KisStopGradientSliderWidget::minimalHeight() const { QFontMetrics fm(font()); const int h = fm.height(); QStyleOptionToolButton opt; - QSize sz = (style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this). - expandedTo(QApplication::globalStrut())); + QSize sz = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(h, h), this); return sz.height() + m_handleSize.height(); } QSize KisStopGradientSliderWidget::sizeHint() const { const int h = minimalHeight(); return QSize(2 * h, h); } QSize KisStopGradientSliderWidget::minimumSizeHint() const { const int h = minimalHeight(); return QSize(h, h); } diff --git a/libs/version/kritaversion.h.cmake b/libs/version/kritaversion.h.cmake index d9f56ab093..8cbd89ad03 100644 --- a/libs/version/kritaversion.h.cmake +++ b/libs/version/kritaversion.h.cmake @@ -1,172 +1,162 @@ /* This file is part of the Krita libraries Copyright (c) 2003 David Faure Copyright (c) 2003 Lukas Tinkl Copyright (c) 2004 Nicolas Goutte Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KRITA_VERSION_H_ #define _KRITA_VERSION_H_ #include "kritaversion_export.h" // -- WARNING: do not edit values below, instead edit KRITA_* in /CMakeLists.txt -- /** * @def KRITA_VERSION_STRING * @ingroup KritaMacros * @brief Version of Krita as string, at compile time * * This macro contains the Krita version in string form. As it is a macro, * it contains the version at compile time. * * @note The version string might contain spaces and special characters, * especially for development versions of Krita. * If you use that macro directly for a file format (e.g. OASIS Open Document) * or for a protocol (e.g. http) be careful that it is appropriate. * (Fictional) example: "3.0 Alpha" */ #define KRITA_VERSION_STRING "@KRITA_VERSION_STRING@" /** * @def KRITA_STABLE_VERSION_MAJOR * @ingroup KritaMacros * @brief Major version of stable Krita, at compile time * KRITA_VERSION_MAJOR is computed based on this value. */ #define KRITA_STABLE_VERSION_MAJOR @KRITA_STABLE_VERSION_MAJOR@ /** * @def KRITA_VERSION_MAJOR * @ingroup KritaMacros * @brief Major version of Krita, at compile time * * Generally it's the same as KRITA_STABLE_VERSION_MAJOR but for unstable x.0 * x is decreased by one, e.g. 3.0 Beta is 2.99. */ #if !defined KRITA_STABLE && @KRITA_STABLE_VERSION_MINOR@ == 0 # define KRITA_VERSION_MAJOR (KRITA_STABLE_VERSION_MAJOR - 1) #else # define KRITA_VERSION_MAJOR KRITA_STABLE_VERSION_MAJOR #endif /** * @def KRITA_STABLE_VERSION_MINOR * @ingroup KritaMacros * @brief Minor version of stable Krita, at compile time * KRITA_VERSION_MINOR is computed based on this value. */ #define KRITA_STABLE_VERSION_MINOR @KRITA_STABLE_VERSION_MINOR@ /** * @def KRITA_VERSION_MINOR * @ingroup KritaMacros * @brief Minor version of Krita, at compile time * * Generally it's equal to KRITA_STABLE_VERSION_MINOR for stable releases, * equal to 99 for x.0 unstable releases (e.g. it's 3.0 Beta has minor version 99), * and equal to KRITA_STABLE_VERSION_MINOR-1 for unstable releases other than x.0. */ #ifdef KRITA_STABLE # define KRITA_VERSION_MINOR KRITA_STABLE_VERSION_MINOR #elif KRITA_STABLE_VERSION_MINOR == 0 # define KRITA_VERSION_MINOR 99 #else # define KRITA_VERSION_MINOR (KRITA_STABLE_VERSION_MINOR - 1) #endif /** * @def KRITA_VERSION_RELEASE * @ingroup KritaMacros * @brief Release version of Krita, at compile time. * 89 for Alpha. */ #define KRITA_VERSION_RELEASE @KRITA_VERSION_RELEASE@ /** * @def KRITA_STABLE_VERSION_RELEASE * @ingroup KritaMacros * @brief Release version of Krita, at compile time. * * Equal to KRITA_VERSION_RELEASE for stable releases and 0 for unstable ones. */ #ifdef KRITA_STABLE # define KRITA_STABLE_VERSION_RELEASE 0 #else # define KRITA_STABLE_VERSION_RELEASE @KRITA_VERSION_RELEASE@ #endif /** * @def KRITA_ALPHA * @ingroup KritaMacros * @brief If defined (1..9), indicates at compile time that Krita is in alpha stage */ #cmakedefine KRITA_ALPHA @KRITA_ALPHA@ /** * @def KRITA_BETA * @ingroup KritaMacros * @brief If defined (1..9), indicates at compile time that Krita is in beta stage */ #cmakedefine KRITA_BETA @KRITA_BETA@ /** * @def KRITA_RC * @ingroup KritaMacros * @brief If defined (1..9), indicates at compile time that Krita is in "release candidate" stage */ #cmakedefine KRITA_RC @KRITA_RC@ /** * @def KRITA_STABLE * @ingroup KritaMacros * @brief If defined, indicates at compile time that Krita is in stable stage */ #cmakedefine KRITA_STABLE @KRITA_STABLE@ /** * @ingroup KritaMacros * @brief Make a number from the major, minor and release number of a Krita version * * This function can be used for preprocessing when KRITA_IS_VERSION is not * appropriate. */ #define KRITA_MAKE_VERSION( a,b,c ) (((a) << 16) | ((b) << 8) | (c)) /** * @ingroup KritaMacros * @brief Version of Krita as number, at compile time * * This macro contains the Krita version in number form. As it is a macro, * it contains the version at compile time. See version() if you need * the Krita version used at runtime. */ #define KRITA_VERSION \ KRITA_MAKE_VERSION(KRITA_VERSION_MAJOR,KRITA_VERSION_MINOR,KRITA_VERSION_RELEASE) -/** - * @def KRITA_YEAR - * @ingroup KritaMacros - * @brief Year of the Krita release, set at compile time - * - * This macro is used in "About application" dialog for strings such as "© 2012-..., The Author Team". -*/ -#define KRITA_YEAR "@KRITA_YEAR@" - - #endif // _KRITA_VERSION_H_ diff --git a/libs/widgets/KisDlgInternalColorSelector.cpp b/libs/widgets/KisDlgInternalColorSelector.cpp index 2c50ac43b7..f773267203 100644 --- a/libs/widgets/KisDlgInternalColorSelector.cpp +++ b/libs/widgets/KisDlgInternalColorSelector.cpp @@ -1,354 +1,348 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include #include #include #include #include #include #include "kis_signal_compressor.h" #include "KoColorDisplayRendererInterface.h" #include "kis_spinbox_color_selector.h" #include "KisDlgInternalColorSelector.h" #include "ui_WdgDlgInternalColorSelector.h" #include "kis_config_notifier.h" #include "kis_color_input.h" #include "kis_icon_utils.h" #include "KisSqueezedComboBox.h" std::function KisDlgInternalColorSelector::s_screenColorPickerFactory = 0; struct KisDlgInternalColorSelector::Private { bool allowUpdates = true; KoColor currentColor; KoColor previousColor; KoColor sRGB = KoColor(KoColorSpaceRegistry::instance()->rgb8()); const KoColorSpace *currentColorSpace; bool lockUsedCS = false; bool chooseAlpha = false; KisSignalCompressor *compressColorChanges; const KoColorDisplayRendererInterface *displayRenderer; KisHexColorInput *hexColorInput = 0; KisPaletteModel *paletteModel = 0; KisPaletteListWidget *paletteChooser = 0; KisScreenColorPickerBase *screenColorPicker = 0; }; KisDlgInternalColorSelector::KisDlgInternalColorSelector(QWidget *parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer) : QDialog(parent) , m_d(new Private) { setModal(config.modal); setFocusPolicy(Qt::ClickFocus); m_ui = new Ui_WdgDlgInternalColorSelector(); m_ui->setupUi(this); setWindowTitle(caption); m_d->currentColor = color; m_d->currentColorSpace = m_d->currentColor.colorSpace(); m_d->displayRenderer = displayRenderer; m_ui->spinboxselector->slotSetColor(color); connect(m_ui->spinboxselector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->visualSelector->slotSetColor(color); m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->visualSelector->setConfig(false, config.modal); if (config.visualColorSelector) { connect(m_ui->visualSelector, SIGNAL(sigNewColor(KoColor)), this, SLOT(slotColorUpdated(KoColor))); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), m_ui->visualSelector, SLOT(configurationChanged())); } else { m_ui->visualSelector->hide(); } m_d->paletteChooser = new KisPaletteListWidget(this); m_d->paletteModel = new KisPaletteModel(this); m_ui->bnPaletteChooser->setIcon(KisIconUtils::loadIcon("hi16-palette_library")); m_ui->paletteBox->setPaletteModel(m_d->paletteModel); m_ui->paletteBox->setDisplayRenderer(displayRenderer); m_ui->cmbNameList->setCompanionView(m_ui->paletteBox); connect(m_d->paletteChooser, SIGNAL(sigPaletteSelected(KoColorSetSP)), this, SLOT(slotChangePalette(KoColorSetSP))); connect(m_ui->cmbNameList, SIGNAL(sigColorSelected(KoColor)), SLOT(slotColorUpdated(KoColor))); // For some bizarre reason, the modal dialog doesn't like having the colorset set, so let's not. if (config.paletteBox) { //TODO: Add disable signal as well. Might be not necessary...? KConfigGroup cfg(KSharedConfig::openConfig()->group("")); QString paletteName = cfg.readEntry("internal_selector_active_color_set", QString()); KoResourceServer* rServer = KoResourceServerProvider::instance()->paletteServer(); KoColorSetSP savedPal = rServer->resourceByName(paletteName); if (savedPal) { this->slotChangePalette(savedPal); } else { if (rServer->resourceCount()) { savedPal = rServer->firstResource(); if (savedPal) { this->slotChangePalette(savedPal); } } } connect(m_ui->paletteBox, SIGNAL(sigColorSelected(KoColor)), this, SLOT(slotColorUpdated(KoColor))); m_ui->bnPaletteChooser->setPopupWidget(m_d->paletteChooser); } else { m_ui->paletteBox->setEnabled(false); m_ui->cmbNameList->setEnabled(false); m_ui->bnPaletteChooser->setEnabled(false); } if (config.prevNextButtons) { m_ui->currentColor->setColor(m_d->currentColor); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setColor(m_d->currentColor); m_ui->previousColor->setDisplayRenderer(displayRenderer); connect(m_ui->previousColor, SIGNAL(triggered(KoColorPatch*)), SLOT(slotSetColorFromPatch(KoColorPatch*))); } else { m_ui->currentColor->hide(); m_ui->previousColor->hide(); } if (config.hexInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput = new KisHexColorInput(this, &m_d->sRGB); m_d->hexColorInput->update(); connect(m_d->hexColorInput, SIGNAL(updated()), SLOT(slotSetColorFromHex())); m_ui->rightPane->addWidget(m_d->hexColorInput); m_d->hexColorInput->setToolTip(i18n("This is a hexcode input, for webcolors. It can only get colors in the sRGB space.")); } // KisScreenColorPicker is in the kritaui module, so dependency inversion is used to access it. m_ui->screenColorPickerWidget->setLayout(new QHBoxLayout(m_ui->screenColorPickerWidget)); if (s_screenColorPickerFactory) { m_d->screenColorPicker = s_screenColorPickerFactory(m_ui->screenColorPickerWidget); m_ui->screenColorPickerWidget->layout()->addWidget(m_d->screenColorPicker); if (config.screenColorPicker) { connect(m_d->screenColorPicker, SIGNAL(sigNewColorPicked(KoColor)),this, SLOT(slotColorUpdated(KoColor))); } else { m_d->screenColorPicker->hide(); } } m_d->compressColorChanges = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE, this); connect(m_d->compressColorChanges, SIGNAL(timeout()), this, SLOT(endUpdateWithNewColor())); connect(m_ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()), Qt::UniqueConnection); connect(m_ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject()), Qt::UniqueConnection); connect(this, SIGNAL(finished(int)), SLOT(slotFinishUp())); } KisDlgInternalColorSelector::~KisDlgInternalColorSelector() { delete m_ui; } void KisDlgInternalColorSelector::slotColorUpdated(KoColor newColor) { // not-so-nice solution: if someone calls this slot directly and that code was // triggered by our compressor signal, our compressor is technically the sender()! if (sender() == m_d->compressColorChanges) { return; } // Do not accept external updates while a color update emit is pending; // Note: Assumes external updates only come from parent(), a separate slot might be better if (m_d->allowUpdates || (QObject::sender() && QObject::sender() != this->parent())) { // Enforce palette colors KConfigGroup group(KSharedConfig::openConfig(), ""); if (group.readEntry("colorsettings/forcepalettecolors", false)) { newColor = m_ui->paletteBox->closestColor(newColor); } if (m_d->lockUsedCS){ newColor.convertTo(m_d->currentColorSpace); m_d->currentColor = newColor; } else { m_d->currentColor = newColor; } updateAllElements(QObject::sender()); } } void KisDlgInternalColorSelector::slotSetColorFromPatch(KoColorPatch *patch) { slotColorUpdated(patch->color()); } void KisDlgInternalColorSelector::colorSpaceChanged(const KoColorSpace *cs) { if (cs == m_d->currentColorSpace) { return; } m_d->currentColorSpace = KoColorSpaceRegistry::instance()->colorSpace(cs->colorModelId().id(), cs->colorDepthId().id(), cs->profile()); m_ui->spinboxselector->slotSetColorSpace(m_d->currentColorSpace); m_ui->visualSelector->slotsetColorSpace(m_d->currentColorSpace); } void KisDlgInternalColorSelector::lockUsedColorSpace(const KoColorSpace *cs) { colorSpaceChanged(cs); if (m_d->currentColor.colorSpace() != m_d->currentColorSpace) { m_d->currentColor.convertTo(m_d->currentColorSpace); } m_d->lockUsedCS = true; } void KisDlgInternalColorSelector::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { m_d->displayRenderer = displayRenderer; m_ui->visualSelector->setDisplayRenderer(displayRenderer); m_ui->currentColor->setDisplayRenderer(displayRenderer); m_ui->previousColor->setDisplayRenderer(displayRenderer); m_ui->paletteBox->setDisplayRenderer(displayRenderer); } else { m_d->displayRenderer = KoDumbColorDisplayRenderer::instance(); } } KoColor KisDlgInternalColorSelector::getModalColorDialog(const KoColor color, QWidget* parent, QString caption) { Config config = Config(); KisDlgInternalColorSelector dialog(parent, color, config, caption); dialog.setPreviousColor(color); dialog.exec(); return dialog.getCurrentColor(); } KoColor KisDlgInternalColorSelector::getCurrentColor() { return m_d->currentColor; } void KisDlgInternalColorSelector::chooseAlpha(bool chooseAlpha) { m_d->chooseAlpha = chooseAlpha; } -void KisDlgInternalColorSelector::slotConfigurationChanged() -{ - //m_d->canvas->displayColorConverter()-> - //slotColorSpaceChanged(m_d->canvas->image()->colorSpace()); -} - void KisDlgInternalColorSelector::setPreviousColor(KoColor c) { m_d->previousColor = c; } void KisDlgInternalColorSelector::reject() { slotColorUpdated(m_d->previousColor); QDialog::reject(); } void KisDlgInternalColorSelector::updateAllElements(QObject *source) { //update everything!!! if (source != m_ui->spinboxselector) { m_ui->spinboxselector->slotSetColor(m_d->currentColor); } if (source != m_ui->visualSelector) { m_ui->visualSelector->slotSetColor(m_d->currentColor); } if (source != m_d->hexColorInput) { m_d->sRGB.fromKoColor(m_d->currentColor); m_d->hexColorInput->update(); } if (source != m_ui->paletteBox) { m_ui->paletteBox->selectClosestColor(m_d->currentColor); } m_ui->previousColor->setColor(m_d->previousColor); m_ui->currentColor->setColor(m_d->currentColor); if (source && source != this->parent()) { m_d->allowUpdates = false; m_d->compressColorChanges->start(); } if (m_d->screenColorPicker) { m_d->screenColorPicker->updateIcons(); } } void KisDlgInternalColorSelector::endUpdateWithNewColor() { emit signalForegroundColorChosen(m_d->currentColor); m_d->allowUpdates = true; } void KisDlgInternalColorSelector::focusInEvent(QFocusEvent *) { //setPreviousColor(); } void KisDlgInternalColorSelector::slotFinishUp() { setPreviousColor(m_d->currentColor); KConfigGroup cfg(KSharedConfig::openConfig()->group("")); if (m_d->paletteModel) { if (m_d->paletteModel->colorSet()) { cfg.writeEntry("internal_selector_active_color_set", m_d->paletteModel->colorSet()->name()); } } } void KisDlgInternalColorSelector::slotSetColorFromHex() { slotColorUpdated(m_d->sRGB); } void KisDlgInternalColorSelector::slotChangePalette(KoColorSetSP set) { if (!set) { return; } m_d->paletteModel->setPalette(set); } void KisDlgInternalColorSelector::showEvent(QShowEvent *event) { updateAllElements(0); QDialog::showEvent(event); } diff --git a/libs/widgets/KisDlgInternalColorSelector.h b/libs/widgets/KisDlgInternalColorSelector.h index 83c852ea4f..6c90a35044 100644 --- a/libs/widgets/KisDlgInternalColorSelector.h +++ b/libs/widgets/KisDlgInternalColorSelector.h @@ -1,192 +1,187 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KISDLGINTERNALCOLORSELECTOR_H #define KISDLGINTERNALCOLORSELECTOR_H #include "kritawidgets_export.h" #include "KoColor.h" #include "KoColorSpace.h" #include "KoColorDisplayRendererInterface.h" #include "KoColorSet.h" #include #include #include "KisScreenColorPickerBase.h" class Ui_WdgDlgInternalColorSelector; class KoColorPatch; /** * @brief The KisInternalColorSelector class * * A non-modal color selector dialog that is not a plugin and can thus be used for filters. */ class KRITAWIDGETS_EXPORT KisDlgInternalColorSelector : public QDialog { Q_OBJECT static std::function s_screenColorPickerFactory; public: static void setScreenColorPickerFactory(std::function f) { s_screenColorPickerFactory = f; } struct Config { Config() : modal(true), visualColorSelector(true), paletteBox(true), screenColorPicker(true), prevNextButtons(true), hexInput(true), useAlpha(false){} bool modal; bool visualColorSelector; bool paletteBox; bool screenColorPicker; bool prevNextButtons; bool hexInput; bool useAlpha; }; KisDlgInternalColorSelector(QWidget* parent, KoColor color, Config config, const QString &caption, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance()); ~KisDlgInternalColorSelector() override; /** * @brief slotColorSpaceChanged * Color space has changed, use this dialog to change the colorspace. */ void colorSpaceChanged(const KoColorSpace *cs); /** * @brief lockUsedColorSpace * Lock the used colorspace of this selector. * @param cs */ void lockUsedColorSpace(const KoColorSpace *cs); /** * @brief setDisplayRenderer * Set the display renderer. This is necessary for HDR color manage support. * @param displayRenderer */ void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer); /** * @brief getModalColorDialog * Execute this dialog modally. The function returns * the KoColor you want. * @param color - The current color. Make sure this is in the color space you want your * end color to be in. * @param parent parent widget. * @param caption the dialog caption. */ static KoColor getModalColorDialog(const KoColor color, QWidget* parent = 0, QString caption = QString()); /** * @brief getCurrentColor * @return gives currently active color; */ KoColor getCurrentColor(); void chooseAlpha(bool chooseAlpha); Q_SIGNALS: /** * @brief signalForegroundColorChosen * The most important signal. This will sent out when a color has been picked from the selector. * There will be a small delay to make sure that the selector causes too many updates. * * Do not connect this to slotColorUpdated. * @param color The new color chosen */ void signalForegroundColorChosen(KoColor color); public Q_SLOTS: /** * @brief slotColorUpdated * Very important slot. Is connected to krita's resources to make sure it has * the currently active color. It's very important that this function is able to understand * when the signal came from itself. * @param newColor This is the new color. */ void slotColorUpdated(KoColor newColor); /** * @brief slotSetColorFromPatch * update current color from kocolorpatch. * @param patch */ void slotSetColorFromPatch(KoColorPatch* patch); /** * @brief setPreviousColor * set the previous color. */ void setPreviousColor(KoColor c); void reject() override; private Q_SLOTS: - /** - * @brief slotConfigurationChanged - * Wrapper slot for changes to the colorspace. - */ - void slotConfigurationChanged(); void endUpdateWithNewColor(); /** * @brief slotFinishUp * This is called when the selector is closed, for saving the current palette. */ void slotFinishUp(); /** * @brief slotSetColorFromHex * Update from the hex color input. */ void slotSetColorFromHex(); void slotChangePalette(KoColorSetSP set); protected: void showEvent(QShowEvent *event) override; private: void focusInEvent(QFocusEvent *) override; /** * @brief updateAllElements * Updates each widget with the new element, and if it's responsible for the update sents * a signal out that there's a new color. */ void updateAllElements(QObject *source); private: Ui_WdgDlgInternalColorSelector *m_ui; struct Private; //The private struct const QScopedPointer m_d; //the private pointer }; #endif // KISDLGINTERNALCOLORSELECTOR_H diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp index b81ec2c286..61e6cf6b5f 100644 --- a/libs/widgets/KisPaletteModel.cpp +++ b/libs/widgets/KisPaletteModel.cpp @@ -1,511 +1,511 @@ /* * Copyright (c) 2013 Sven Langkamp * Copyright (c) 2018 Michael Zhou * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisPaletteModel.h" #include #include #include #include #include #include #include #include #include KisPaletteModel::KisPaletteModel(QObject* parent) : QAbstractTableModel(parent) , m_colorSet(0) , m_displayRenderer(KoDumbColorDisplayRenderer::instance()) { connect(this, SIGNAL(sigPaletteModified()), SLOT(slotPaletteModified())); } KisPaletteModel::~KisPaletteModel() { } QVariant KisPaletteModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } bool groupNameRow = m_rowGroupNameMap.contains(index.row()); if (role == IsGroupNameRole) { return groupNameRow; } if (groupNameRow) { return dataForGroupNameRow(index, role); } else { return dataForSwatch(index, role); } } int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const { if (!m_colorSet) return 0; return m_colorSet->rowCount() // count of color rows + m_rowGroupNameMap.size() // rows for names - 1; // global doesn't have a name } int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const { if (m_colorSet && m_colorSet->columnCount() > 0) { return m_colorSet->columnCount(); } if (!m_colorSet) { return 0; } return 16; } Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const { if (index.isValid()) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return Qt::ItemIsDropEnabled; } QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const { - Q_UNUSED(parent); + Q_UNUSED(parent) Q_ASSERT(m_colorSet); - int groupNameRow = groupNameRowForRow(row); - if (groupNameRow == -1) { + if (m_rowGroupNameMap.isEmpty()) { return {}; } + int groupNameRow = groupNameRowForRow(row); KisSwatchGroup *group = m_colorSet->getGroup(m_rowGroupNameMap[groupNameRow]); - Q_ASSERT(group); + KIS_ASSERT_RECOVER_RETURN_VALUE(group,QModelIndex()); return createIndex(row, column, group); } void KisPaletteModel::resetGroupNameRows() { m_rowGroupNameMap.clear(); int row = -1; for (const QString &groupName : m_colorSet->getGroupNames()) { m_rowGroupNameMap[row] = groupName; row += m_colorSet->getGroup(groupName)->rowCount(); row += 1; // row for group name } } void KisPaletteModel::setPalette(KoColorSetSP palette) { beginResetModel(); m_colorSet = palette; if (palette) { resetGroupNameRows(); } endResetModel(); emit sigPaletteChanged(); } KoColorSetSP KisPaletteModel::colorSet() const { return m_colorSet; } int KisPaletteModel::rowNumberInGroup(int rowInModel) const { if (m_rowGroupNameMap.contains(rowInModel)) { return -1; } QList rowNumberList = m_rowGroupNameMap.keys(); for (auto it = rowNumberList.rbegin(); it != rowNumberList.rend(); it++) { if (*it < rowInModel) { return rowInModel - *it - 1; } } return rowInModel; } int KisPaletteModel::groupNameRowForName(const QString &groupName) { for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == groupName) { return it.key(); } } return -1; } bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName) { beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1); m_colorSet->add(entry, groupName); endInsertRows(); if (m_colorSet->isGlobal()) { m_colorSet->save(); } emit sigPaletteModified(); return true; } bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors) { if (!qvariant_cast(data(index, IsGroupNameRole))) { static_cast(index.internalPointer())->removeEntry(index.column(), rowNumberInGroup(index.row())); emit dataChanged(index, index); } else { int groupNameRow = groupNameRowForRow(index.row()); QString groupName = m_rowGroupNameMap[groupNameRow]; removeGroup(groupName, keepColors); } emit sigPaletteModified(); return true; } void KisPaletteModel::removeGroup(const QString &groupName, bool keepColors) { int removeStart = groupNameRowForName(groupName); int removedRowCount = m_colorSet->getGroup(groupName)->rowCount(); int insertStart = m_colorSet->getGlobalGroup()->rowCount(); beginRemoveRows(QModelIndex(), removeStart, removeStart + removedRowCount); m_colorSet->removeGroup(groupName, keepColors); resetGroupNameRows(); endRemoveRows(); beginInsertRows(QModelIndex(), insertStart, m_colorSet->getGlobalGroup()->rowCount()); endInsertRows(); emit sigPaletteModified(); } bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) { return false; } if (action == Qt::IgnoreAction) { return false; } QModelIndex finalIndex = parent; if (!finalIndex.isValid()) { return false; } if (data->hasFormat("krita/x-colorsetgroup")) { // dragging group not supported for now QByteArray encodedData = data->data("krita/x-colorsetgroup"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { QString groupNameDroppedOn = qvariant_cast(finalIndex.data(GroupNameRole)); if (groupNameDroppedOn == KoColorSet::GLOBAL_GROUP_NAME) { return false; } QString groupNameDragged; stream >> groupNameDragged; KisSwatchGroup *groupDragged = m_colorSet->getGroup(groupNameDragged); int start = groupNameRowForName(groupNameDragged); int end = start + groupDragged->rowCount(); if (!beginMoveRows(QModelIndex(), start, end, QModelIndex(), groupNameRowForName(groupNameDroppedOn))) { return false; } m_colorSet->moveGroup(groupNameDragged, groupNameDroppedOn); resetGroupNameRows(); endMoveRows(); emit sigPaletteModified(); if (m_colorSet->isGlobal()) { m_colorSet->save(); } } return true; } if (qvariant_cast(finalIndex.data(KisPaletteModel::IsGroupNameRole))) { return true; } QByteArray encodedData = data->data("krita/x-colorsetentry"); QDataStream stream(&encodedData, QIODevice::ReadOnly); while (!stream.atEnd()) { KisSwatch entry; QString name, id; bool spotColor; QString oldGroupName; int oriRow; int oriColumn; QString colorXml; stream >> name >> id >> spotColor >> oriRow >> oriColumn >> oldGroupName >> colorXml; entry.setName(name); entry.setId(id); entry.setSpotColor(spotColor); QDomDocument doc; doc.setContent(colorXml); QDomElement e = doc.documentElement(); QDomElement c = e.firstChildElement(); if (!c.isNull()) { QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id()); entry.setColor(KoColor::fromXML(c, colorDepthId)); } if (action == Qt::MoveAction){ KisSwatchGroup *g = m_colorSet->getGroup(oldGroupName); if (g) { if (qvariant_cast(finalIndex.data(KisPaletteModel::CheckSlotRole))) { g->setEntry(getEntry(finalIndex), oriColumn, oriRow); } else { g->removeEntry(oriColumn, oriRow); } } setEntry(entry, finalIndex); emit sigPaletteModified(); if (m_colorSet->isGlobal()) { m_colorSet->save(); } } } return true; } QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const { QMimeData *mimeData = new QMimeData(); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QModelIndex index = indexes.last(); if (index.isValid() && qvariant_cast(index.data(CheckSlotRole))) { QString mimeTypeName = "krita/x-colorsetentry"; if (qvariant_cast(index.data(IsGroupNameRole))==false) { KisSwatch entry = getEntry(index); QDomDocument doc; QDomElement root = doc.createElement("Color"); root.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id()); doc.appendChild(root); entry.color().toXML(doc, root); stream << entry.name() << entry.id() << entry.spotColor() << rowNumberInGroup(index.row()) << index.column() << qvariant_cast(index.data(GroupNameRole)) << doc.toString(); } else { mimeTypeName = "krita/x-colorsetgroup"; QString groupName = qvariant_cast(index.data(GroupNameRole)); stream << groupName; } mimeData->setData(mimeTypeName, encodedData); } return mimeData; } QStringList KisPaletteModel::mimeTypes() const { return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup"; } Qt::DropActions KisPaletteModel::supportedDropActions() const { return Qt::MoveAction; } void KisPaletteModel::setEntry(const KisSwatch &entry, const QModelIndex &index) { KisSwatchGroup *group = static_cast(index.internalPointer()); Q_ASSERT(group); group->setEntry(entry, index.column(), rowNumberInGroup(index.row())); emit sigPaletteModified(); emit dataChanged(index, index); if (m_colorSet->isGlobal()) { m_colorSet->save(); } } bool KisPaletteModel::renameGroup(const QString &groupName, const QString &newName) { beginResetModel(); bool success = m_colorSet->changeGroupName(groupName, newName); for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == groupName) { m_rowGroupNameMap[it.key()] = newName; break; } } endResetModel(); emit sigPaletteModified(); return success; } void KisPaletteModel::addGroup(const KisSwatchGroup &group) { beginInsertRows(QModelIndex(), rowCount(), rowCount() + group.rowCount()); m_colorSet->addGroup(group.name()); *m_colorSet->getGroup(group.name()) = group; endInsertColumns(); emit sigPaletteModified(); } void KisPaletteModel::setRowNumber(const QString &groupName, int rowCount) { beginResetModel(); KisSwatchGroup *g = m_colorSet->getGroup(groupName); if (g) { g->setRowCount(rowCount); } endResetModel(); } void KisPaletteModel::clear() { beginResetModel(); m_colorSet->clear(); endResetModel(); } QVariant KisPaletteModel::dataForGroupNameRow(const QModelIndex &idx, int role) const { KisSwatchGroup *group = static_cast(idx.internalPointer()); Q_ASSERT(group); QString groupName = group->name(); switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return groupName; } case GroupNameRole: { return groupName; } case CheckSlotRole: { return true; } case RowInGroupRole: { return -1; } default: { return QVariant(); } } } QVariant KisPaletteModel::dataForSwatch(const QModelIndex &idx, int role) const { KisSwatchGroup *group = static_cast(idx.internalPointer()); Q_ASSERT(group); int rowInGroup = rowNumberInGroup(idx.row()); bool entryPresent = group->checkEntry(idx.column(), rowInGroup); KisSwatch entry; if (entryPresent) { entry = group->getEntry(idx.column(), rowInGroup); } switch (role) { case Qt::ToolTipRole: case Qt::DisplayRole: { return entryPresent ? entry.name() : i18n("Empty slot"); } case Qt::BackgroundRole: { QColor color(0, 0, 0, 0); if (entryPresent) { color = m_displayRenderer->toQColor(entry.color()); } return QBrush(color); } case GroupNameRole: { return group->name(); } case CheckSlotRole: { return entryPresent; } case RowInGroupRole: { return rowInGroup; } default: { return QVariant(); } } } void KisPaletteModel::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer) { if (displayRenderer) { if (m_displayRenderer) { disconnect(m_displayRenderer, 0, this, 0); } m_displayRenderer = displayRenderer; connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection); } else { m_displayRenderer = KoDumbColorDisplayRenderer::instance(); } } void KisPaletteModel::slotDisplayConfigurationChanged() { beginResetModel(); endResetModel(); } void KisPaletteModel::slotPaletteModified() { m_colorSet->setPaletteType(KoColorSet::KPL); } QModelIndex KisPaletteModel::indexForClosest(const KoColor &compare) { KisSwatchGroup::SwatchInfo info = colorSet()->getClosestColorInfo(compare); return createIndex(indexRowForInfo(info), info.column, colorSet()->getGroup(info.group)); } int KisPaletteModel::indexRowForInfo(const KisSwatchGroup::SwatchInfo &info) { for (auto it = m_rowGroupNameMap.begin(); it != m_rowGroupNameMap.end(); it++) { if (it.value() == info.group) { return it.key() + info.row + 1; } } return info.row; } KisSwatch KisPaletteModel::getEntry(const QModelIndex &index) const { KisSwatchGroup *group = static_cast(index.internalPointer()); if (!group || !group->checkEntry(index.column(), rowNumberInGroup(index.row()))) { return KisSwatch(); } return group->getEntry(index.column(), rowNumberInGroup(index.row())); } int KisPaletteModel::groupNameRowForRow(int rowInModel) const { return rowInModel - rowNumberInGroup(rowInModel) - 1; } diff --git a/libs/widgets/KisVisualColorSelector.cpp b/libs/widgets/KisVisualColorSelector.cpp index d87947a2e1..e18eb62ee9 100644 --- a/libs/widgets/KisVisualColorSelector.cpp +++ b/libs/widgets/KisVisualColorSelector.cpp @@ -1,566 +1,563 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisVisualColorSelector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoColorProfile.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "KisVisualColorSelectorShape.h" #include "KisVisualRectangleSelectorShape.h" #include "KisVisualTriangleSelectorShape.h" #include "KisVisualEllipticalSelectorShape.h" struct KisVisualColorSelector::Private { KoColor currentcolor; const KoColorSpace *currentCS {0}; QList widgetlist; bool updateLonesome {false}; // currently redundant; remove? bool circular {false}; bool exposureSupported = false; bool isRGBA = false; bool isLinear = false; int displayPosition[4]; // map channel index to storage index for display int colorChannelCount; QVector4D channelValues; QVector4D channelMaxValues; ColorModel model; const KoColorDisplayRendererInterface *displayRenderer {0}; KisColorSelectorConfiguration acs_config; KisSignalCompressor *updateTimer {0}; }; KisVisualColorSelector::KisVisualColorSelector(QWidget *parent) : KisColorSelectorInterface(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); m_d->updateTimer = new KisSignalCompressor(100 /* ms */, KisSignalCompressor::POSTPONE); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(slotRebuildSelectors()), Qt::UniqueConnection); } KisVisualColorSelector::~KisVisualColorSelector() { delete m_d->updateTimer; } void KisVisualColorSelector::slotSetColor(const KoColor &c) { m_d->currentcolor = c; if (m_d->currentCS != c.colorSpace()) { slotsetColorSpace(c.colorSpace()); } else { m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } } } void KisVisualColorSelector::slotsetColorSpace(const KoColorSpace *cs) { if (m_d->currentCS != cs) { m_d->currentCS = cs; slotRebuildSelectors(); } } void KisVisualColorSelector::setConfig(bool forceCircular, bool forceSelfUpdate) { m_d->circular = forceCircular; m_d->updateLonesome = forceSelfUpdate; } KoColor KisVisualColorSelector::getCurrentColor() const { return m_d->currentcolor; } QVector4D KisVisualColorSelector::getChannelValues() const { return m_d->channelValues; } KoColor KisVisualColorSelector::convertShapeCoordsToKoColor(const QVector4D &coordinates) const { KoColor c(m_d->currentCS); QVector4D baseValues(coordinates); QVector channelValues(c.colorSpace()->channelCount()); channelValues.fill(1.0); if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->model == ColorModel::HSV) { HSVToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); } else if (m_d->model == ColorModel::HSL) { HSLToRGB(coordinates.x()*360, coordinates.y(), coordinates.z(), &baseValues[0], &baseValues[1], &baseValues[2]); } else if (m_d->model == ColorModel::HSI) { // why suddenly qreal? qreal temp[3]; HSIToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2]); baseValues.setX(temp[0]); baseValues.setY(temp[1]); baseValues.setZ(temp[2]); } else /*if (m_d->model == ColorModel::HSY)*/ { QVector luma= m_d->currentCS->lumaCoefficients(); qreal temp[3]; HSYToRGB(coordinates.x(), coordinates.y(), coordinates.z(), &temp[0], &temp[1], &temp[2], luma[0], luma[1], luma[2]); baseValues.setX(temp[0]); baseValues.setY(temp[1]); baseValues.setZ(temp[2]); } if (m_d->isLinear) { for (int i=0; i<3; i++) { baseValues[i] = pow(baseValues[i], 2.2); } } } if (m_d->exposureSupported) { baseValues *= m_d->channelMaxValues; } for (int i=0; icolorChannelCount; i++) { // TODO: proper exposure control channelValues[m_d->displayPosition[i]] = baseValues[i] /* *(maxvalue[i]) */; } c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelValues); return c; } QVector4D KisVisualColorSelector::convertKoColorToShapeCoordinates(KoColor c) const { if (c.colorSpace() != m_d->currentCS) { c.convertTo(m_d->currentCS); } QVector channelValues (c.colorSpace()->channelCount()); channelValues.fill(1.0); m_d->currentCS->normalisedChannelsValue(c.data(), channelValues); QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0); // TODO: L*a*b is apparently not [0, 1]^3 as "normalized" values, needs extra transform (old bug) for (int i =0; icolorChannelCount; i++) { channelValuesDisplay[i] = channelValues[m_d->displayPosition[i]]; } if (m_d->exposureSupported) { channelValuesDisplay /= m_d->channelMaxValues; } if (m_d->model != ColorModel::Channel && m_d->isRGBA == true) { if (m_d->isRGBA == true) { if (m_d->isLinear) { for (int i=0; i<3; i++) { channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2); } } if (m_d->model == ColorModel::HSV){ QVector3D hsv; // TODO: handle undefined hue case (returns -1) RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]); hsv[0] /= 360; coordinates = QVector4D(hsv, 0.f); } else if (m_d->model == ColorModel::HSL) { QVector3D hsl; RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]); hsl[0] /= 360; coordinates = QVector4D(hsl, 0.f); } else if (m_d->model == ColorModel::HSI) { qreal hsi[3]; RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]); coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f); } else if (m_d->model == ColorModel::HSY) { QVector luma = m_d->currentCS->lumaCoefficients(); qreal hsy[3]; RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2], luma[0], luma[1], luma[2]); coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f); } for (int i=0; i<3; i++) { coordinates[i] = qBound(0.f, coordinates[i], 1.f); } } } else { for (int i=0; i<4; i++) { coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f); } } return coordinates; } void KisVisualColorSelector::configurationChanged() { if (m_d->updateTimer) { m_d->updateTimer->start(); } } void KisVisualColorSelector::slotDisplayConfigurationChanged() { Q_ASSERT(m_d->displayRenderer); if (m_d->currentCS) { m_d->channelMaxValues = QVector4D(1, 1, 1, 1); QList channels = m_d->currentCS->channels(); for (int i=0; icolorChannelCount; ++i) { m_d->channelMaxValues[i] = m_d->displayRenderer->maxVisibleFloatValue(channels[m_d->displayPosition[i]]); } // need to re-scale our normalized channel values on exposure changes: m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); } } } void KisVisualColorSelector::slotRebuildSelectors() { KConfigGroup cfg = KSharedConfig::openConfig()->group("advancedColorSelector"); m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString())); QList channelList = m_d->currentCS->channels(); int cCount = 0; Q_FOREACH(const KoChannelInfo *channel, channelList) { if (channel->channelType() != KoChannelInfo::ALPHA) { m_d->displayPosition[cCount] = channel->displayPosition(); ++cCount; } } Q_ASSERT_X(cCount < 5, "", "unsupported channel count!"); m_d->colorChannelCount = cCount; // TODO: The following is done because the IDs are actually strings. Ideally, in the future, we // refactor everything so that the IDs are actually proper enums or something faster. if (m_d->displayRenderer && (m_d->currentCS->colorDepthId() == Float16BitsColorDepthID || m_d->currentCS->colorDepthId() == Float32BitsColorDepthID || m_d->currentCS->colorDepthId() == Float64BitsColorDepthID) && m_d->currentCS->colorModelId() != LABAColorModelID && m_d->currentCS->colorModelId() != CMYKAColorModelID) { m_d->exposureSupported = true; } else { m_d->exposureSupported = false; } m_d->isRGBA = (m_d->currentCS->colorModelId() == RGBAColorModelID); const KoColorProfile *profile = m_d->currentCS->profile(); m_d->isLinear = (profile && profile->isLinear()); qDeleteAll(children()); m_d->widgetlist.clear(); // TODO: Layout only used for monochrome selector currently, but always present QLayout *layout = new QHBoxLayout; //recreate all the widgets. m_d->model = KisVisualColorSelector::Channel; if (m_d->currentCS->colorChannelCount() == 1) { KisVisualColorSelectorShape *bar; if (m_d->circular==false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20); bar->setMaximumWidth(width()*0.1); bar->setMaximumHeight(height()); } else { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, 0, 0,m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); layout->setMargin(0); } connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); layout->addWidget(bar); m_d->widgetlist.append(bar); } else if (m_d->currentCS->colorChannelCount() == 3) { KisVisualColorSelector::ColorModel modelS = KisVisualColorSelector::HSV; int channel1 = 0; int channel2 = 1; int channel3 = 2; switch(m_d->acs_config.subTypeParameter) { case KisColorSelectorConfiguration::H: channel1 = 0; break; case KisColorSelectorConfiguration::hsyS: case KisColorSelectorConfiguration::hsiS: case KisColorSelectorConfiguration::hslS: case KisColorSelectorConfiguration::hsvS: channel1 = 1; break; case KisColorSelectorConfiguration::V: case KisColorSelectorConfiguration::L: case KisColorSelectorConfiguration::I: case KisColorSelectorConfiguration::Y: channel1 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.subTypeParameter"); } switch(m_d->acs_config.mainTypeParameter) { case KisColorSelectorConfiguration::hsySH: modelS = KisVisualColorSelector::HSY; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsiSH: modelS = KisVisualColorSelector::HSI; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hslSH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::hsvSH: modelS = KisVisualColorSelector::HSV; channel2 = 0; channel3 = 1; break; case KisColorSelectorConfiguration::YH: modelS = KisVisualColorSelector::HSY; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::LH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::IH: modelS = KisVisualColorSelector::HSL; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::VH: modelS = KisVisualColorSelector::HSV; channel2 = 0; channel3 = 2; break; case KisColorSelectorConfiguration::SY: modelS = KisVisualColorSelector::HSY; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SI: modelS = KisVisualColorSelector::HSI; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SL: modelS = KisVisualColorSelector::HSL; channel2 = 1; channel3 = 2; break; case KisColorSelectorConfiguration::SV: case KisColorSelectorConfiguration::SV2: modelS = KisVisualColorSelector::HSV; channel2 = 1; channel3 = 2; break; default: Q_ASSERT_X(false, "", "Invalid acs_config.mainTypeParameter"); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { modelS = KisVisualColorSelector::HSV; //Triangle only really works in HSV mode. } m_d->model = modelS; KisVisualColorSelectorShape *bar; if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20,KisVisualEllipticalSelectorShape::border); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == false) { bar = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular == true) { bar = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::onedimensional, m_d->currentCS, channel1, channel1, m_d->displayRenderer, 20, KisVisualEllipticalSelectorShape::borderMirrored); } else { // Accessing bar below would crash since it's not initialized. // Hopefully this can never happen. warnUI << "Invalid subType, cannot initialize KisVisualColorSelectorShape"; Q_ASSERT_X(false, "", "Invalid subType, cannot initialize KisVisualColorSelectorShape"); return; } m_d->widgetlist.append(bar); KisVisualColorSelectorShape *block; if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { block = new KisVisualTriangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { block = new KisVisualRectangleSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } else { block = new KisVisualEllipticalSelectorShape(this, KisVisualColorSelectorShape::twodimensional, m_d->currentCS, channel2, channel3, m_d->displayRenderer); } connect(bar, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); } else if (m_d->currentCS->colorChannelCount() == 4) { KisVisualRectangleSelectorShape *block = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 0, 1); KisVisualRectangleSelectorShape *block2 = new KisVisualRectangleSelectorShape(this, KisVisualRectangleSelectorShape::twodimensional, m_d->currentCS, 2, 3); connect(block, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); connect(block2, SIGNAL(sigCursorMoved(QPointF)), SLOT(slotCursorMoved(QPointF))); m_d->widgetlist.append(block); m_d->widgetlist.append(block2); } this->setLayout(layout); // make sure we call "our" resize function KisVisualColorSelector::resizeEvent(0); // finally recalculate channel values and update widgets if (m_d->displayRenderer) { slotDisplayConfigurationChanged(); } m_d->channelValues = convertKoColorToShapeCoordinates(m_d->currentcolor); Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setChannelValues(m_d->channelValues, true); // if this widget is currently visible, new children are hidden by default shape->show(); } } void KisVisualColorSelector::setDisplayRenderer (const KoColorDisplayRendererInterface *displayRenderer) { m_d->displayRenderer = displayRenderer; if (m_d->widgetlist.size()>0) { Q_FOREACH (KisVisualColorSelectorShape *shape, m_d->widgetlist) { shape->setDisplayRenderer(displayRenderer); } } connect(m_d->displayRenderer, SIGNAL(displayConfigurationChanged()), SLOT(slotDisplayConfigurationChanged()), Qt::UniqueConnection); slotDisplayConfigurationChanged(); } void KisVisualColorSelector::slotCursorMoved(QPointF pos) { const KisVisualColorSelectorShape *shape = qobject_cast(sender()); Q_ASSERT(shape); QVector channels = shape->getChannels(); m_d->channelValues[channels.at(0)] = pos.x(); - if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) - { + if (shape->getDimensions() == KisVisualColorSelectorShape::twodimensional) { m_d->channelValues[channels.at(1)] = pos.y(); } KoColor newColor = convertShapeCoordsToKoColor(m_d->channelValues); - if (newColor != m_d->currentcolor) - { + if (newColor != m_d->currentcolor) { m_d->currentcolor = newColor; - - Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) { - if (widget != shape){ - widget->setChannelValues(m_d->channelValues, false); - } - } emit sigNewColor(m_d->currentcolor); } + Q_FOREACH (KisVisualColorSelectorShape *widget, m_d->widgetlist) { + if (widget != shape){ + widget->setChannelValues(m_d->channelValues, false); + } + } } void KisVisualColorSelector::resizeEvent(QResizeEvent *) { int sizeValue = qMin(width(), height()); int borderWidth = qMax(sizeValue*0.1, 20.0); QRect newrect(0,0, this->geometry().width(), this->geometry().height()); if (!m_d->currentCS) { slotsetColorSpace(m_d->currentcolor.colorSpace()); } if (m_d->currentCS->colorChannelCount()==3) { // set border width first, else the resized painting may have happened already, and we'd have to re-render m_d->widgetlist.at(0)->setBorderWidth(borderWidth); if (m_d->acs_config.subType == KisColorSelectorConfiguration::Ring) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==false) { m_d->widgetlist.at(0)->resize(borderWidth, sizeValue); } else if (m_d->acs_config.subType == KisColorSelectorConfiguration::Slider && m_d->circular==true) { m_d->widgetlist.at(0)->resize(sizeValue,sizeValue); } if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForTriangle(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Square) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForSquare(newrect)); } else if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Wheel) { m_d->widgetlist.at(1)->setGeometry(m_d->widgetlist.at(0)->getSpaceForCircle(newrect)); } } else if (m_d->currentCS->colorChannelCount() == 4) { int sizeBlock = qMin(width()/2 - 8, height()); m_d->widgetlist.at(0)->setGeometry(0, 0, sizeBlock, sizeBlock); m_d->widgetlist.at(1)->setGeometry(sizeBlock + 8, 0, sizeBlock, sizeBlock); } } diff --git a/libs/widgets/KoFontComboBox.h b/libs/widgets/KoFontComboBox.h deleted file mode 100644 index 5f7d607e1a..0000000000 --- a/libs/widgets/KoFontComboBox.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2014 Dmitry Kazakov - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ -#ifndef _KO_FONT_COMBO_BOX_H_ -#define _KO_FONT_COMBO_BOX_H_ - -#include -typedef QFontComboBox KoFontComboBox; - -#endif // _KO_FONT_COMBO_BOX_H_ diff --git a/libs/widgets/KoSliderCombo.cpp b/libs/widgets/KoSliderCombo.cpp index f3d78735b3..15493157b9 100644 --- a/libs/widgets/KoSliderCombo.cpp +++ b/libs/widgets/KoSliderCombo.cpp @@ -1,291 +1,291 @@ /* This file is part of the KDE project Copyright (c) 2007 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoSliderCombo.h" #include "KoSliderCombo_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KoSliderCombo::KoSliderCombo(QWidget *parent) : QComboBox(parent) ,d(new KoSliderComboPrivate()) { d->thePublic = this; d->minimum = 0.0; d->maximum = 100.0; d->decimals = 2; d->container = new KoSliderComboContainer(this); d->container->setAttribute(Qt::WA_WindowPropagation); QStyleOptionComboBox opt; opt.initFrom(this); // d->container->setFrameStyle(style()->styleHint(QStyle::SH_ComboBox_PopupFrameStyle, &opt, this)); d->slider = new QSlider(Qt::Horizontal); d->slider->setMinimum(0); d->slider->setMaximum(256); d->slider->setPageStep(10); d->slider->setValue(0); // When set to true, causes flicker on Qt 4.6. Any reason to keep it? d->firstShowOfSlider = false; //true; QHBoxLayout * l = new QHBoxLayout(); l->setMargin(2); l->setSpacing(2); l->addWidget(d->slider); d->container->setLayout(l); d->container->resize(200, 30); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setEditable(true); setEditText(QLocale().toString(0.0, d->decimals)); connect(d->slider, SIGNAL(valueChanged(int)), SLOT(sliderValueChanged(int))); connect(d->slider, SIGNAL(sliderReleased()), SLOT(sliderReleased())); connect(lineEdit(), SIGNAL(editingFinished()), SLOT(lineEditFinished())); } KoSliderCombo::~KoSliderCombo() { delete d; } QSize KoSliderCombo::sizeHint() const { return minimumSizeHint(); } QSize KoSliderCombo::minimumSizeHint() const { QSize sh; const QFontMetrics &fm = fontMetrics(); sh.setWidth(5 * fm.width(QLatin1Char('8'))); sh.setHeight(qMax(fm.lineSpacing(), 14) + 2); // add style and strut values QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; sh = style()->sizeFromContents(QStyle::CT_ComboBox, &opt, sh, this); - return sh.expandedTo(QApplication::globalStrut()); + return sh; } void KoSliderCombo::KoSliderComboPrivate::showPopup() { if(firstShowOfSlider) { container->show(); //show container a bit early so the slider can be layout'ed firstShowOfSlider = false; } QStyleOptionSlider opt; opt.initFrom(slider); opt.maximum=256; opt.sliderPosition = opt.sliderValue = slider->value(); int hdlPos = thePublic->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle).center().x(); QStyleOptionComboBox optThis; optThis.initFrom(thePublic); optThis.subControls = QStyle::SC_All; optThis.editable = true; int arrowPos = thePublic->style()->subControlRect(QStyle::CC_ComboBox, &optThis, QStyle::SC_ComboBoxArrow).center().x(); QSize popSize = container->size(); QRect popupRect(thePublic->mapToGlobal(QPoint(arrowPos - hdlPos - slider->x(), thePublic->size().height())), popSize); // Make sure the popup is not drawn outside the screen area QRect screenRect = QApplication::desktop()->availableGeometry(thePublic); if (popupRect.right() > screenRect.right()) popupRect.translate(screenRect.right() - popupRect.right(), 0); if (popupRect.left() < screenRect.left()) popupRect.translate(screenRect.left() - popupRect.left(), 0); if (popupRect.bottom() > screenRect.bottom()) popupRect.translate(0, -(thePublic->height() + container->height())); container->setGeometry(popupRect); container->raise(); container->show(); slider->setFocus(); } void KoSliderCombo::KoSliderComboPrivate::hidePopup() { container->hide(); } void KoSliderCombo::hideEvent(QHideEvent *) { d->hidePopup(); } void KoSliderCombo::changeEvent(QEvent *e) { switch (e->type()) { case QEvent::EnabledChange: if (!isEnabled()) d->hidePopup(); break; case QEvent::PaletteChange: d->container->setPalette(palette()); break; default: break; } QComboBox::changeEvent(e); } void KoSliderCombo::paintEvent(QPaintEvent *) { QStylePainter gc(this); gc.setPen(palette().color(QPalette::Text)); QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; gc.drawComplexControl(QStyle::CC_ComboBox, opt); gc.drawControl(QStyle::CE_ComboBoxLabel, opt); } void KoSliderCombo::mousePressEvent(QMouseEvent *e) { QStyleOptionComboBox opt; opt.initFrom(this); opt.subControls = QStyle::SC_All; opt.editable = true; QStyle::SubControl sc = style()->hitTestComplexControl(QStyle::CC_ComboBox, &opt, e->pos(), this); if (sc == QStyle::SC_ComboBoxArrow && !d->container->isVisible()) { d->showPopup(); } else QComboBox::mousePressEvent(e); } void KoSliderCombo::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Up) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5); else if (e->key() == Qt::Key_Down) setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5); else QComboBox::keyPressEvent(e); } void KoSliderCombo::wheelEvent(QWheelEvent *e) { if (e->delta() > 0) setValue(value() + d->slider->singleStep() * (maximum() - minimum()) / 256 + 0.5); else setValue(value() - d->slider->singleStep() * (maximum() - minimum()) / 256 - 0.5); } void KoSliderCombo::KoSliderComboPrivate::lineEditFinished() { qreal value = QLocale().toDouble(thePublic->currentText()); slider->blockSignals(true); slider->setValue(int((value - minimum) * 256 / (maximum - minimum) + 0.5)); slider->blockSignals(false); emit thePublic->valueChanged(value, true); } void KoSliderCombo::KoSliderComboPrivate::sliderValueChanged(int slidervalue) { thePublic->setEditText(QLocale().toString(minimum + (maximum - minimum)*slidervalue/256, decimals)); qreal value = QLocale().toDouble(thePublic->currentText()); emit thePublic->valueChanged(value, false); } void KoSliderCombo::KoSliderComboPrivate::sliderReleased() { qreal value = QLocale().toDouble(thePublic->currentText()); emit thePublic->valueChanged(value, true); } qreal KoSliderCombo::maximum() const { return d->maximum; } qreal KoSliderCombo::minimum() const { return d->minimum; } qreal KoSliderCombo::decimals() const { return d->decimals; } qreal KoSliderCombo::value() const { return QLocale().toDouble(currentText()); } void KoSliderCombo::setDecimals(int dec) { d->decimals = dec; if (dec == 0) lineEdit()->setValidator(new QIntValidator(this)); else lineEdit()->setValidator(new QDoubleValidator(this)); } void KoSliderCombo::setMinimum(qreal min) { d->minimum = min; } void KoSliderCombo::setMaximum(qreal max) { d->maximum = max; } void KoSliderCombo::setValue(qreal value) { if(value < d->minimum) value = d->minimum; if(value > d->maximum) value = d->maximum; setEditText(QLocale().toString(value, d->decimals)); d->slider->blockSignals(true); d->slider->setValue(int((value - d->minimum) * 256 / (d->maximum - d->minimum) + 0.5)); d->slider->blockSignals(false); emit valueChanged(value, true); } //have to include this because of Q_PRIVATE_SLOT #include diff --git a/libs/widgets/KoToolBox.cpp b/libs/widgets/KoToolBox.cpp index efeaa4090f..6c1d4896f3 100644 --- a/libs/widgets/KoToolBox.cpp +++ b/libs/widgets/KoToolBox.cpp @@ -1,342 +1,346 @@ /* * Copyright (c) 2005-2009 Thomas Zander * Copyright (c) 2009 Peter Simonsson * Copyright (c) 2010 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoToolBox_p.h" #include "KoToolBoxLayout_p.h" #include "KoToolBoxButton_p.h" +#include "kis_assert.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUTTON_MARGIN 10 static int buttonSize(int screen) { + KIS_ASSERT_RECOVER_RETURN_VALUE(screen < QGuiApplication::screens().size() && screen >= 0, 16); + QRect rc = QGuiApplication::screens().at(screen)->availableGeometry(); if (rc.width() <= 1024) { return 12; } else if (rc.width() <= 1377) { return 14; } else if (rc.width() <= 1920 ) { return 16; } else { return 22; } + } class KoToolBox::Private { public: Private() : layout(0) , buttonGroup(0) , floating(false) , contextSize(0) { } void addSection(Section *section, const QString &name); QList buttons; QMap sections; KoToolBoxLayout *layout; QButtonGroup *buttonGroup; QHash visibilityCodes; bool floating; QMap contextIconSizes; QMenu* contextSize; Qt::Orientation orientation; }; void KoToolBox::Private::addSection(Section *section, const QString &name) { section->setName(name); layout->addSection(section); sections.insert(name, section); } KoToolBox::KoToolBox() : d(new Private) { d->layout = new KoToolBoxLayout(this); // add defaults d->addSection(new Section(this), "main"); d->addSection(new Section(this), "dynamic"); d->buttonGroup = new QButtonGroup(this); setLayout(d->layout); Q_FOREACH (KoToolAction *toolAction, KoToolManager::instance()->toolActionList()) { addButton(toolAction); } // Update visibility of buttons setButtonsVisible(QList()); connect(KoToolManager::instance(), SIGNAL(changedTool(KoCanvasController*,int)), this, SLOT(setActiveTool(KoCanvasController*,int))); connect(KoToolManager::instance(), SIGNAL(currentLayerChanged(const KoCanvasController*,const KoShapeLayer*)), this, SLOT(setCurrentLayer(const KoCanvasController*,const KoShapeLayer*))); connect(KoToolManager::instance(), SIGNAL(toolCodesSelected(QList)), this, SLOT(setButtonsVisible(QList))); connect(KoToolManager::instance(), SIGNAL(addedTool(KoToolAction*,KoCanvasController*)), this, SLOT(toolAdded(KoToolAction*,KoCanvasController*))); } KoToolBox::~KoToolBox() { delete d; } void KoToolBox::addButton(KoToolAction *toolAction) { KoToolBoxButton *button = new KoToolBoxButton(toolAction, this); d->buttons << button; int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this)); KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); int iconSize = cfg.readEntry("iconSize", toolbuttonSize); button->setIconSize(QSize(iconSize, iconSize)); foreach (Section *section, d->sections.values()) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } QString sectionToBeAddedTo; const QString section = toolAction->section(); if (section.contains(qApp->applicationName())) { sectionToBeAddedTo = "main"; } else if (section.contains("main")) { sectionToBeAddedTo = "main"; } else if (section.contains("dynamic")) { sectionToBeAddedTo = "dynamic"; } else { sectionToBeAddedTo = section; } Section *sectionWidget = d->sections.value(sectionToBeAddedTo); if (sectionWidget == 0) { sectionWidget = new Section(this); d->addSection(sectionWidget, sectionToBeAddedTo); } sectionWidget->addButton(button, toolAction->priority()); d->buttonGroup->addButton(button, toolAction->buttonGroupId()); d->visibilityCodes.insert(button, toolAction->visibilityCode()); } void KoToolBox::setActiveTool(KoCanvasController *canvas, int id) { Q_UNUSED(canvas); QAbstractButton *button = d->buttonGroup->button(id); if (button) { button->setChecked(true); (qobject_cast(button))->setHighlightColor(); } else { warnWidgets << "KoToolBox::setActiveTool(" << id << "): no such button found"; } } void KoToolBox::setButtonsVisible(const QList &codes) { Q_FOREACH (QToolButton *button, d->visibilityCodes.keys()) { QString code = d->visibilityCodes.value(button); if (code.startsWith(QLatin1String("flake/"))) { continue; } if (code.endsWith( QLatin1String( "/always"))) { button->setVisible(true); button->setEnabled(true); } else if (code.isEmpty()) { button->setVisible(true); button->setEnabled( codes.count() != 0 ); } else { button->setVisible( codes.contains(code) ); } } layout()->invalidate(); update(); } void KoToolBox::setCurrentLayer(const KoCanvasController *canvas, const KoShapeLayer *layer) { Q_UNUSED(canvas); const bool enabled = layer == 0 || (layer->isShapeEditable() && layer->isVisible()); foreach (QToolButton *button, d->visibilityCodes.keys()) { if (d->visibilityCodes[button].endsWith( QLatin1String( "/always") ) ) { continue; } button->setEnabled(enabled); } } void KoToolBox::paintEvent(QPaintEvent *) { QPainter painter(this); const QList sections = d->sections.values(); QList::const_iterator iterator = sections.begin(); int halfSpacing = layout()->spacing(); if (halfSpacing > 0) { halfSpacing /= 2; } while(iterator != sections.end()) { Section *section = *iterator; QStyleOption styleoption; styleoption.palette = palette(); if (section->separators() & Section::SeparatorTop) { int y = section->y() - halfSpacing; styleoption.state = QStyle::State_None; styleoption.rect = QRect(section->x(), y-1, section->width(), 2); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter); } if (section->separators() & Section::SeparatorLeft) { int x = section->x() - halfSpacing; styleoption.state = QStyle::State_Horizontal; styleoption.rect = QRect(x-1, section->y(), 2, section->height()); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &styleoption, &painter); } ++iterator; } painter.end(); } void KoToolBox::setOrientation(Qt::Orientation orientation) { d->orientation = orientation; d->layout->setOrientation(orientation); QTimer::singleShot(0, this, SLOT(update())); Q_FOREACH (Section* section, d->sections) { section->setOrientation(orientation); } } void KoToolBox::setFloating(bool v) { d->floating = v; } void KoToolBox::toolAdded(KoToolAction *toolAction, KoCanvasController *canvas) { Q_UNUSED(canvas); addButton(toolAction); setButtonsVisible(QList()); } void KoToolBox::slotContextIconSize() { QAction* action = qobject_cast(sender()); if (action && d->contextIconSizes.contains(action)) { const int iconSize = d->contextIconSizes.value(action); KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); cfg.writeEntry("iconSize", iconSize); Q_FOREACH (QToolButton *button, d->buttons) { button->setIconSize(QSize(iconSize, iconSize)); } Q_FOREACH (Section *section, d->sections.values()) { section->setButtonSize(QSize(iconSize + BUTTON_MARGIN, iconSize + BUTTON_MARGIN)); } } } void KoToolBox::contextMenuEvent(QContextMenuEvent *event) { int toolbuttonSize = buttonSize(qApp->desktop()->screenNumber(this)); if (!d->contextSize) { d->contextSize = new QMenu(i18n("Icon Size"), this); d->contextIconSizes.insert(d->contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), this, SLOT(slotContextIconSize())), toolbuttonSize); QList sizes; sizes << 12 << 14 << 16 << 22 << 32 << 48 << 64; //<< 96 << 128 << 192 << 256; Q_FOREACH (int i, sizes) { d->contextIconSizes.insert(d->contextSize->addAction(i18n("%1x%2", i, i), this, SLOT(slotContextIconSize())), i); } QActionGroup *sizeGroup = new QActionGroup(d->contextSize); foreach (QAction *action, d->contextSize->actions()) { action->setActionGroup(sizeGroup); action->setCheckable(true); } } KConfigGroup cfg = KSharedConfig::openConfig()->group("KoToolBox"); toolbuttonSize = cfg.readEntry("iconSize", toolbuttonSize); QMapIterator< QAction*, int > it = d->contextIconSizes; while (it.hasNext()) { it.next(); if (it.value() == toolbuttonSize) { it.key()->setChecked(true); break; } } d->contextSize->exec(event->globalPos()); } KoToolBoxLayout *KoToolBox::toolBoxLayout() const { return d->layout; } #include "moc_KoToolBoxScrollArea_p.cpp" diff --git a/libs/widgets/kis_color_button.cpp b/libs/widgets/kis_color_button.cpp index 2e60eb5e47..49ba3729ce 100644 --- a/libs/widgets/kis_color_button.cpp +++ b/libs/widgets/kis_color_button.cpp @@ -1,387 +1,385 @@ /* This file is part of the KDE libraries Copyright (C) 1997 Martin Jones (mjones@kde.org) Copyright (C) 1999 Cristian Tibirna (ctibirna@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_button.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KisColorButton::KisColorButtonPrivate { public: KisColorButtonPrivate(KisColorButton *q); ~KisColorButtonPrivate() { if (dialogPtr) { dialogPtr.data()->close(); } } void _k_chooseColor(); void _k_colorChosen(); KisColorButton *q; KoColor m_defaultColor; bool m_bdefaultColor : 1; bool m_alphaChannel : 1; bool m_palette : 1; KoColor col; QPoint mPos; #ifndef Q_OS_MACOS QPointer dialogPtr; #else QPointer dialogPtr; #endif void initStyleOption(QStyleOptionButton *opt) const; }; ///////////////////////////////////////////////////////////////////// // Functions duplicated from KColorMimeData // Should be kept in sync void _k_populateMimeData(QMimeData *mimeData, const KoColor &color) { mimeData->setColorData(color.toQColor()); mimeData->setText(color.toQColor().name()); } bool _k_canDecode(const QMimeData *mimeData) { if (mimeData->hasColor()) { return true; } if (mimeData->hasText()) { const QString colorName = mimeData->text(); if ((colorName.length() >= 4) && (colorName[0] == QLatin1Char('#'))) { return true; } } return false; } QColor _k_fromMimeData(const QMimeData *mimeData) { if (mimeData->hasColor()) { return mimeData->colorData().value(); } if (_k_canDecode(mimeData)) { return QColor(mimeData->text()); } return QColor(); } QDrag *_k_createDrag(const KoColor &color, QObject *dragsource) { QDrag *drag = new QDrag(dragsource); QMimeData *mime = new QMimeData; _k_populateMimeData(mime, color); drag->setMimeData(mime); QPixmap colorpix(25, 20); colorpix.fill(color.toQColor()); QPainter p(&colorpix); p.setPen(Qt::black); p.drawRect(0, 0, 24, 19); p.end(); drag->setPixmap(colorpix); drag->setHotSpot(QPoint(-5, -7)); return drag; } ///////////////////////////////////////////////////////////////////// KisColorButton::KisColorButtonPrivate::KisColorButtonPrivate(KisColorButton *q) : q(q) { m_bdefaultColor = false; m_alphaChannel = false; m_palette = true; q->setAcceptDrops(true); connect(q, SIGNAL(clicked()), q, SLOT(_k_chooseColor())); } KisColorButton::KisColorButton(QWidget *parent) : QPushButton(parent) , d(new KisColorButtonPrivate(this)) { } KisColorButton::KisColorButton(const KoColor &c, QWidget *parent) : QPushButton(parent) , d(new KisColorButtonPrivate(this)) { d->col = c; } KisColorButton::KisColorButton(const KoColor &c, const KoColor &defaultColor, QWidget *parent) : QPushButton(parent) , d(new KisColorButtonPrivate(this)) { d->col = c; setDefaultColor(defaultColor); } KisColorButton::~KisColorButton() { delete d; } KoColor KisColorButton::color() const { return d->col; } void KisColorButton::setColor(const KoColor &c) { d->col = c; update(); emit changed(d->col); } void KisColorButton::setAlphaChannelEnabled(bool alpha) { d->m_alphaChannel = alpha; } bool KisColorButton::isAlphaChannelEnabled() const { return d->m_alphaChannel; } void KisColorButton::setPaletteViewEnabled(bool enable) { d->m_palette = enable; } bool KisColorButton::paletteViewEnabled() const { return d->m_palette; } KoColor KisColorButton::defaultColor() const { return d->m_defaultColor; } void KisColorButton::setDefaultColor(const KoColor &c) { d->m_bdefaultColor = true; d->m_defaultColor = c; } void KisColorButton::KisColorButtonPrivate::initStyleOption(QStyleOptionButton *opt) const { opt->initFrom(q); opt->state |= q->isDown() ? QStyle::State_Sunken : QStyle::State_Raised; opt->features = QStyleOptionButton::None; if (q->isDefault()) { opt->features |= QStyleOptionButton::DefaultButton; } opt->text.clear(); opt->icon = QIcon(); } void KisColorButton::paintEvent(QPaintEvent *) { QPainter painter(this); QStyle *style = QWidget::style(); //First, we need to draw the bevel. QStyleOptionButton butOpt; d->initStyleOption(&butOpt); style->drawControl(QStyle::CE_PushButtonBevel, &butOpt, &painter, this); //OK, now we can muck around with drawing out pretty little color box //First, sort out where it goes QRect labelRect = style->subElementRect(QStyle::SE_PushButtonContents, &butOpt, this); int shift = style->pixelMetric(QStyle::PM_ButtonMargin, &butOpt, this) / 2; labelRect.adjust(shift, shift, -shift, -shift); int x, y, w, h; labelRect.getRect(&x, &y, &w, &h); if (isChecked() || isDown()) { x += style->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &butOpt, this); y += style->pixelMetric(QStyle::PM_ButtonShiftVertical, &butOpt, this); } QColor fillCol = isEnabled() ? d->col.toQColor() : palette().color(backgroundRole()); qDrawShadePanel(&painter, x, y, w, h, palette(), true, 1, NULL); if (fillCol.isValid()) { const QRect rect(x + 1, y + 1, w - 2, h - 2); if (fillCol.alpha() < 255) { QPixmap chessboardPattern(16, 16); QPainter patternPainter(&chessboardPattern); patternPainter.fillRect(0, 0, 8, 8, Qt::black); patternPainter.fillRect(8, 8, 8, 8, Qt::black); patternPainter.fillRect(0, 8, 8, 8, Qt::white); patternPainter.fillRect(8, 0, 8, 8, Qt::white); patternPainter.end(); painter.fillRect(rect, QBrush(chessboardPattern)); } painter.fillRect(rect, fillCol); } if (hasFocus()) { QRect focusRect = style->subElementRect(QStyle::SE_PushButtonFocusRect, &butOpt, this); QStyleOptionFocusRect focusOpt; focusOpt.init(this); focusOpt.rect = focusRect; focusOpt.backgroundColor = palette().window().color(); style->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpt, &painter, this); } } QSize KisColorButton::sizeHint() const { QStyleOptionButton opt; d->initStyleOption(&opt); - return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this). - expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(40, 15), this); } QSize KisColorButton::minimumSizeHint() const { QStyleOptionButton opt; d->initStyleOption(&opt); - return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this). - expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_PushButton, &opt, QSize(3, 3), this); } void KisColorButton::dragEnterEvent(QDragEnterEvent *event) { event->setAccepted(_k_canDecode(event->mimeData()) && isEnabled()); } void KisColorButton::dropEvent(QDropEvent *event) { QColor c = _k_fromMimeData(event->mimeData()); if (c.isValid()) { KoColor col; col.fromQColor(c); setColor(col); } } void KisColorButton::keyPressEvent(QKeyEvent *e) { int key = e->key() | e->modifiers(); if (QKeySequence::keyBindings(QKeySequence::Copy).contains(key)) { QMimeData *mime = new QMimeData; _k_populateMimeData(mime, color()); QApplication::clipboard()->setMimeData(mime, QClipboard::Clipboard); } else if (QKeySequence::keyBindings(QKeySequence::Paste).contains(key)) { QColor color = _k_fromMimeData(QApplication::clipboard()->mimeData(QClipboard::Clipboard)); KoColor col; col.fromQColor(color); setColor(col); } else { QPushButton::keyPressEvent(e); } } void KisColorButton::mousePressEvent(QMouseEvent *e) { d->mPos = e->pos(); QPushButton::mousePressEvent(e); } void KisColorButton::mouseMoveEvent(QMouseEvent *e) { if ((e->buttons() & Qt::LeftButton) && (e->pos() - d->mPos).manhattanLength() > QApplication::startDragDistance()) { _k_createDrag(color(), this)->exec(); setDown(false); } } void KisColorButton::KisColorButtonPrivate::_k_chooseColor() { #ifndef Q_OS_MACOS KisDlgInternalColorSelector *dialog = dialogPtr.data(); #else QColorDialog *dialog = dialogPtr.data(); #endif if (dialog) { #ifndef Q_OS_MACOS dialog->setPreviousColor(q->color()); #else dialog->setCurrentColor(q->color().toQColor()); #endif dialog->show(); dialog->raise(); dialog->activateWindow(); return; } KisDlgInternalColorSelector::Config cfg; cfg.paletteBox = q->paletteViewEnabled(); #ifndef Q_OS_MACOS dialog = new KisDlgInternalColorSelector(q, q->color(), cfg, i18n("Choose a color")); #else dialog = new QColorDialog(q); dialog->setOption(QColorDialog::ShowAlphaChannel, m_alphaChannel); #endif dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, SIGNAL(accepted()), q, SLOT(_k_colorChosen())); dialogPtr = dialog; #ifndef Q_OS_MACOS dialog->setPreviousColor(q->color()); #else dialog->setCurrentColor(q->color().toQColor()); #endif dialog->show(); } void KisColorButton::KisColorButtonPrivate::_k_colorChosen() { #ifndef Q_OS_MACOS KisDlgInternalColorSelector *dialog = dialogPtr.data(); #else QColorDialog *dialog = dialogPtr.data(); #endif if (!dialog) { return; } #ifndef Q_OS_MACOS q->setColor(dialog->getCurrentColor()); #else KoColor c; c.fromQColor(dialog->currentColor()); q->setColor(c); #endif } #include "moc_kis_color_button.cpp" diff --git a/libs/widgets/kis_color_input.cpp b/libs/widgets/kis_color_input.cpp index 01b7b0dd74..8687176162 100644 --- a/libs/widgets/kis_color_input.cpp +++ b/libs/widgets/kis_color_input.cpp @@ -1,436 +1,439 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2011 Sven Langkamp * Copyright (c) 2015 Moritz Molch * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_color_input.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" KisColorInput::KisColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : QWidget(parent), m_channelInfo(channelInfo), m_color(color), m_displayRenderer(displayRenderer), m_usePercentage(usePercentage) { } void KisColorInput::init() { QHBoxLayout* m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0,0,0,0); m_layout->setSpacing(1); QLabel* m_label = new QLabel(i18n("%1:", m_channelInfo->name()), this); m_layout->addWidget(m_label); m_colorSlider = new KoColorSlider(Qt::Horizontal, this, m_displayRenderer); m_colorSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_layout->addWidget(m_colorSlider); QWidget* m_input = createInput(); m_colorSlider->setFixedHeight(m_input->sizeHint().height()); m_layout->addWidget(m_input); } KisIntegerColorInput::KisIntegerColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage) { init(); } void KisIntegerColorInput::setValue(int v) { quint8* data = m_color->data() + m_channelInfo->pos(); switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: *(reinterpret_cast(data)) = v; break; case KoChannelInfo::UINT16: *(reinterpret_cast(data)) = v; break; case KoChannelInfo::UINT32: *(reinterpret_cast(data)) = v; break; default: Q_ASSERT(false); } emit(updated()); } void KisIntegerColorInput::update() { KoColor min = *m_color; KoColor max = *m_color; quint8* data = m_color->data() + m_channelInfo->pos(); quint8* dataMin = min.data() + m_channelInfo->pos(); quint8* dataMax = max.data() + m_channelInfo->pos(); m_intNumInput->blockSignals(true); m_colorSlider->blockSignals(true); switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 255.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFF; break; case KoChannelInfo::UINT16: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 65535.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFFFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFFFF; break; case KoChannelInfo::UINT32: if (m_usePercentage) { m_intNumInput->setMaximum(100); m_intNumInput->setValue(round(*(reinterpret_cast(data))*1.0 / 4294967295.0 * 100.0)); } else { m_intNumInput->setMaximum(0xFFFF); m_intNumInput->setValue(*(reinterpret_cast(data))); } m_colorSlider->setValue(*(reinterpret_cast(data))); *(reinterpret_cast(dataMin)) = 0x0; *(reinterpret_cast(dataMax)) = 0xFFFFFFFF; break; default: Q_ASSERT(false); } m_colorSlider->setColors(min, max); m_intNumInput->blockSignals(false); m_colorSlider->blockSignals(false); } QWidget* KisIntegerColorInput::createInput() { m_intNumInput = new KisIntParseSpinBox(this); m_intNumInput->setMinimum(0); m_colorSlider->setMinimum(0); if (m_usePercentage) { m_intNumInput->setSuffix(i18n("%")); } else { m_intNumInput->setSuffix(""); } switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFF); } m_colorSlider->setMaximum(0xFF); break; case KoChannelInfo::UINT16: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFFFF); } m_colorSlider->setMaximum(0xFFFF); break; case KoChannelInfo::UINT32: if (m_usePercentage) { m_intNumInput->setMaximum(100); } else { m_intNumInput->setMaximum(0xFFFFFFFF); } m_colorSlider->setMaximum(0xFFFFFFFF); break; default: Q_ASSERT(false); } connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(onColorSliderChanged(int))); connect(m_intNumInput, SIGNAL(valueChanged(int)), this, SLOT(onNumInputChanged(int))); return m_intNumInput; } void KisIntegerColorInput::setPercentageWise(bool val) { m_usePercentage = val; if (m_usePercentage) { m_intNumInput->setSuffix(i18n("%")); } else { m_intNumInput->setSuffix(""); } } void KisIntegerColorInput::onColorSliderChanged(int val) { m_intNumInput->blockSignals(true); if (m_usePercentage) { switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: m_intNumInput->setValue(round((val*1.0) / 255.0 * 100.0)); break; case KoChannelInfo::UINT16: m_intNumInput->setValue(round((val*1.0) / 65535.0 * 100.0)); break; case KoChannelInfo::UINT32: m_intNumInput->setValue(round((val*1.0) / 4294967295.0 * 100.0)); break; default: Q_ASSERT(false); } } else { m_intNumInput->setValue(val); } m_intNumInput->blockSignals(false); setValue(val); } void KisIntegerColorInput::onNumInputChanged(int val) { m_colorSlider->blockSignals(true); if (m_usePercentage) { switch (m_channelInfo->channelValueType()) { case KoChannelInfo::UINT8: m_colorSlider->setValue((val*1.0)/100.0 * 255.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 255.0); break; case KoChannelInfo::UINT16: m_colorSlider->setValue((val*1.0)/100.0 * 65535.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 65535.0); break; case KoChannelInfo::UINT32: m_colorSlider->setValue((val*1.0)/100.0 * 4294967295.0); m_colorSlider->blockSignals(false); setValue((val*1.0)/100.0 * 4294967295.0); break; default: Q_ASSERT(false); } } else { m_colorSlider->setValue(val); m_colorSlider->blockSignals(false); setValue(val); } } KisFloatColorInput::KisFloatColorInput(QWidget* parent, const KoChannelInfo* channelInfo, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, channelInfo, color, displayRenderer, usePercentage) { init(); } void KisFloatColorInput::setValue(double v) { quint8* data = m_color->data() + m_channelInfo->pos(); switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: *(reinterpret_cast(data)) = v; break; #endif case KoChannelInfo::FLOAT32: *(reinterpret_cast(data)) = v; break; default: Q_ASSERT(false); } emit(updated()); } QWidget* KisFloatColorInput::createInput() { m_dblNumInput = new KisDoubleParseSpinBox(this); m_dblNumInput->setMinimum(0); m_dblNumInput->setMaximum(1.0); connect(m_colorSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderChanged(int))); connect(m_dblNumInput, SIGNAL(valueChanged(double)), this, SLOT(setValue(double))); m_dblNumInput->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_dblNumInput->setMinimumWidth(60); m_dblNumInput->setMaximumWidth(60); quint8* data = m_color->data() + m_channelInfo->pos(); qreal value = 1.0; switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: value = *(reinterpret_cast(data)); break; #endif case KoChannelInfo::FLOAT32: value = *(reinterpret_cast(data)); break; default: Q_ASSERT(false); } m_dblNumInput->setValue(value); return m_dblNumInput; } void KisFloatColorInput::sliderChanged(int i) { const qreal floatRange = m_maxValue - m_minValue; m_dblNumInput->setValue(m_minValue + (i / 255.0) * floatRange); } void KisFloatColorInput::update() { KoColor min = *m_color; KoColor max = *m_color; quint8* data = m_color->data() + m_channelInfo->pos(); quint8* dataMin = min.data() + m_channelInfo->pos(); quint8* dataMax = max.data() + m_channelInfo->pos(); qreal value = 1.0; m_minValue = m_displayRenderer->minVisibleFloatValue(m_channelInfo); m_maxValue = m_displayRenderer->maxVisibleFloatValue(m_channelInfo); + m_dblNumInput->blockSignals(true); m_colorSlider->blockSignals(true); switch (m_channelInfo->channelValueType()) { #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: value = *(reinterpret_cast(data)); m_minValue = qMin(value, m_minValue); m_maxValue = qMax(value, m_maxValue); *(reinterpret_cast(dataMin)) = m_minValue; *(reinterpret_cast(dataMax)) = m_maxValue; break; #endif case KoChannelInfo::FLOAT32: value = *(reinterpret_cast(data)); m_minValue = qMin(value, m_minValue); m_maxValue = qMax(value, m_maxValue); *(reinterpret_cast(dataMin)) = m_minValue; *(reinterpret_cast(dataMax)) = m_maxValue; break; default: Q_ASSERT(false); } m_dblNumInput->setMinimum(m_minValue); m_dblNumInput->setMaximum(m_maxValue); // ensure at least 3 significant digits are always shown int newPrecision = 2 + qMax(qreal(0.0), std::ceil(-std::log10(m_maxValue))); if (newPrecision != m_dblNumInput->decimals()) { m_dblNumInput->setDecimals(newPrecision); m_dblNumInput->updateGeometry(); } + m_dblNumInput->setValue(value); m_colorSlider->setColors(min, max); const qreal floatRange = m_maxValue - m_minValue; m_colorSlider->setValue((value - m_minValue) / floatRange * 255); + m_dblNumInput->blockSignals(false); m_colorSlider->blockSignals(false); } KisHexColorInput::KisHexColorInput(QWidget* parent, KoColor* color, KoColorDisplayRendererInterface *displayRenderer, bool usePercentage) : KisColorInput(parent, 0, color, displayRenderer, usePercentage) { QHBoxLayout* m_layout = new QHBoxLayout(this); m_layout->setContentsMargins(0,0,0,0); m_layout->setSpacing(1); QLabel* m_label = new QLabel(i18n("Color name:"), this); m_label->setMinimumWidth(50); m_layout->addWidget(m_label); QWidget* m_input = createInput(); m_input->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_layout->addWidget(m_input); } void KisHexColorInput::setValue() { QString valueString = m_hexInput->text(); valueString.remove(QChar('#')); QList channels = m_color->colorSpace()->channels(); channels = KoChannelInfo::displayOrderSorted(channels); Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->channelType() == KoChannelInfo::COLOR) { Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8); quint8* data = m_color->data() + channel->pos(); int value = valueString.left(2).toInt(0, 16); *(reinterpret_cast(data)) = value; valueString.remove(0, 2); } } emit(updated()); } void KisHexColorInput::update() { QString hexString("#"); QList channels = m_color->colorSpace()->channels(); channels = KoChannelInfo::displayOrderSorted(channels); Q_FOREACH (KoChannelInfo* channel, channels) { if (channel->channelType() == KoChannelInfo::COLOR) { Q_ASSERT(channel->channelValueType() == KoChannelInfo::UINT8); quint8* data = m_color->data() + channel->pos(); hexString.append(QString("%1").arg(*(reinterpret_cast(data)), 2, 16, QChar('0'))); } } m_hexInput->setText(hexString); } QWidget* KisHexColorInput::createInput() { m_hexInput = new QLineEdit(this); m_hexInput->setAlignment(Qt::AlignRight); int digits = 2*m_color->colorSpace()->colorChannelCount(); QString pattern = QString("#?[a-fA-F0-9]{%1,%2}").arg(digits).arg(digits); m_hexInput->setValidator(new QRegExpValidator(QRegExp(pattern), this)); connect(m_hexInput, SIGNAL(editingFinished()), this, SLOT(setValue())); return m_hexInput; } diff --git a/libs/widgets/kis_spinbox_color_selector.cpp b/libs/widgets/kis_spinbox_color_selector.cpp index 30d31035d9..9ca41e3080 100644 --- a/libs/widgets/kis_spinbox_color_selector.cpp +++ b/libs/widgets/kis_spinbox_color_selector.cpp @@ -1,273 +1,267 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_spinbox_color_selector.h" #include #include #include "kis_double_parse_spin_box.h" #include "kis_int_parse_spin_box.h" #include "kis_signal_compressor.h" #include #ifdef HAVE_OPENEXR #include #endif #include #include #include #include struct KisSpinboxColorSelector::Private { QList labels; QList spinBoxList; QList doubleSpinBoxList; KoColor color; const KoColorSpace *cs {0}; bool chooseAlpha {false}; QFormLayout *layout {0}; }; KisSpinboxColorSelector::KisSpinboxColorSelector(QWidget *parent) : QWidget(parent) , m_d(new Private) { this->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); m_d->layout = new QFormLayout(this); } KisSpinboxColorSelector::~KisSpinboxColorSelector() { } void KisSpinboxColorSelector::slotSetColor(KoColor color) { m_d->color = color; slotSetColorSpace(m_d->color.colorSpace()); updateSpinboxesWithNewValues(); } void KisSpinboxColorSelector::slotSetColorSpace(const KoColorSpace *cs) { if (cs == m_d->cs) { return; } m_d->cs = cs; //remake spinboxes delete m_d->layout; m_d->layout = new QFormLayout(this); Q_FOREACH(QObject *o, m_d->labels) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->spinBoxList) { o->deleteLater(); } Q_FOREACH(QObject *o, m_d->doubleSpinBoxList) { o->deleteLater(); } - Q_FOREACH(QObject *o, m_d->labels) { - o->deleteLater(); - } - m_d->labels.clear(); m_d->spinBoxList.clear(); m_d->doubleSpinBoxList.clear(); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); Q_FOREACH (KoChannelInfo* channel, channels) { QString inputLabel = channel->name(); QLabel *inlb = new QLabel(this); m_d->labels << inlb; inlb->setText(inputLabel); switch (channel->channelValueType()) { case KoChannelInfo::UINT8: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb, input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT16: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; case KoChannelInfo::UINT32: { KisIntParseSpinBox *input = new KisIntParseSpinBox(this); input->setMinimum(0); input->setMaximum(0xFFFFFFFF); m_d->spinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #ifdef HAVE_OPENEXR case KoChannelInfo::FLOAT16: { KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); input->setMinimum(0); input->setMaximum(KoColorSpaceMathsTraits::max); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; #endif case KoChannelInfo::FLOAT32: { KisDoubleParseSpinBox *input = new KisDoubleParseSpinBox(this); input->setMinimum(0); input->setMaximum(KoColorSpaceMathsTraits::max); input->setSingleStep(0.1); m_d->doubleSpinBoxList.append(input); m_d->layout->addRow(inlb,input); connect(input, SIGNAL(valueChanged(double)), this, SLOT(slotUpdateFromSpinBoxes())); if (channel->channelType() == KoChannelInfo::ALPHA && m_d->chooseAlpha == false) { inlb->setVisible(false); input->setVisible(false); input->blockSignals(true); } } break; default: Q_ASSERT(false); } } this->setLayout(m_d->layout); } void KisSpinboxColorSelector::createColorFromSpinboxValues() { - KoColor newColor; + KoColor newColor(m_d->cs); int channelcount = m_d->cs->channelCount(); - quint8 *data = new quint8[m_d->cs->pixelSize()]; QVector channelValues(channelcount); channelValues.fill(1.0); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); for (int i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType()==KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)){ int value = m_d->spinBoxList.at(i)->value(); channelValues[channelposition] = KoColorSpaceMaths::scaleToA(value); } else if (channels.at(i)->channelValueType()==KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)){ channelValues[channelposition] = KoColorSpaceMaths::scaleToA(m_d->spinBoxList.at(i)->value()); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { channelValues[channelposition] = m_d->doubleSpinBoxList.at(i)->value(); } } - m_d->cs->fromNormalisedChannelsValue(data, channelValues); - newColor.setColor(data, m_d->cs); + m_d->cs->fromNormalisedChannelsValue(newColor.data(), channelValues); newColor.setOpacity(m_d->color.opacityU8()); m_d->color = newColor; } void KisSpinboxColorSelector::slotUpdateFromSpinBoxes() { createColorFromSpinboxValues(); emit sigNewColor(m_d->color); } void KisSpinboxColorSelector::updateSpinboxesWithNewValues() { int channelcount = m_d->cs->channelCount(); QVector channelValues(channelcount); channelValues.fill(1.0); m_d->cs->normalisedChannelsValue(m_d->color.data(), channelValues); QList channels = KoChannelInfo::displayOrderSorted(m_d->cs->channels()); int i; /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(true); }*/ for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(true); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(true); } for (i = 0; i < (int)qAbs(m_d->cs->colorChannelCount()); i++) { int channelposition = KoChannelInfo::displayPositionToChannelIndex(i, m_d->cs->channels()); if (channels.at(i)->channelValueType() == KoChannelInfo::UINT8 && m_d->spinBoxList.at(i)) { int value = KoColorSpaceMaths::scaleToA(channelValues[channelposition]); m_d->spinBoxList.at(i)->setValue(value); } else if (channels.at(i)->channelValueType() == KoChannelInfo::UINT16 && m_d->spinBoxList.at(i)) { m_d->spinBoxList.at(i)->setValue(KoColorSpaceMaths::scaleToA(channelValues[channelposition])); } else if ((channels.at(i)->channelValueType()==KoChannelInfo::FLOAT16 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT32 || channels.at(i)->channelValueType()==KoChannelInfo::FLOAT64) && m_d->doubleSpinBoxList.at(i)) { m_d->doubleSpinBoxList.at(i)->setValue(channelValues[channelposition]); } } for (i=0; ispinBoxList.size(); i++) { m_d->spinBoxList.at(i)->blockSignals(false); } for (i=0; idoubleSpinBoxList.size(); i++) { m_d->doubleSpinBoxList.at(i)->blockSignals(false); } /*while (QLayoutItem *item = this->layout()->takeAt(0)) { item->widget()->blockSignals(false); }*/ } diff --git a/libs/widgetutils/KisSqueezedComboBox.cpp b/libs/widgetutils/KisSqueezedComboBox.cpp index 6e5d35baec..afd5de755d 100644 --- a/libs/widgetutils/KisSqueezedComboBox.cpp +++ b/libs/widgetutils/KisSqueezedComboBox.cpp @@ -1,179 +1,178 @@ /* ============================================================ * Author: Tom Albers * 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 "KisSqueezedComboBox.h" /** @file KisSqueezedComboBox.cpp */ // Qt includes. #include #include #include #include #include #include KisSqueezedComboBox::KisSqueezedComboBox(QWidget *parent, const char *name) : QComboBox(parent) { setObjectName(name); setMinimumWidth(100); m_timer = new QTimer(this); m_timer->setSingleShot(true); connect(m_timer, SIGNAL(timeout()), SLOT(slotTimeOut())); } KisSqueezedComboBox::~KisSqueezedComboBox() { delete m_timer; } bool KisSqueezedComboBox::contains(const QString& _text) const { if (_text.isEmpty()) return false; for (QMap::const_iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { if (it.value() == _text) { return true; } } return false; } qint32 KisSqueezedComboBox::findOriginalText(const QString& text) const { for (int i = 0; i < m_originalItems.size(); i++) { if(m_originalItems.value(i) == text) { return i; } } return -1; } QStringList KisSqueezedComboBox::originalTexts() const { return m_originalItems.values(); } void KisSqueezedComboBox::resetOriginalTexts(const QStringList &texts) { if (texts == m_originalItems.values()) return; clear(); m_originalItems.clear(); Q_FOREACH (const QString &item, texts) { addSqueezedItem(item); } } QSize KisSqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.boundingRect(QChar('x')).width() + 18; int maxH = qMax(fm.lineSpacing(), 14) + 2; QStyleOptionComboBox options; options.initFrom(this); - return style()->sizeFromContents(QStyle::CT_ComboBox, &options, - QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this); } void KisSqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem, this), userData); } void KisSqueezedComboBox::insertSqueezedItem(const QIcon &icon, const QString &newItem, int index, QVariant userData) { m_originalItems[index] = newItem; QComboBox::insertItem(index, icon, squeezeText(newItem, this), userData); } void KisSqueezedComboBox::addSqueezedItem(const QString& newItem, QVariant userData) { insertSqueezedItem(newItem, count(), userData); } void KisSqueezedComboBox::addSqueezedItem(const QIcon &icon, const QString &newItem, QVariant userData) { insertSqueezedItem(icon, newItem, count(), userData); } void KisSqueezedComboBox::setCurrent(const QString& itemText) { qint32 itemIndex = findOriginalText(itemText); if (itemIndex >= 0) { setCurrentIndex(itemIndex); } } void KisSqueezedComboBox::resizeEvent(QResizeEvent *) { m_timer->start(200); } void KisSqueezedComboBox::slotTimeOut() { for (QMap::iterator it = m_originalItems.begin() ; it != m_originalItems.end(); ++it) { setItemText(it.key(), squeezeText(it.value(), this)); } } QString KisSqueezedComboBox::squeezeText(const QString& original, const QWidget *widget) { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = widget->width() - 30; QFontMetrics fm(widget->fontMetrics()); // If we can fit the full text, return that. if (fm.boundingRect(original).width() < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize - fm.boundingRect("...").width(); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.boundingRect(original.right(i)).width() > widgetSize) { sqItem = QString("..." + original.right(--i)); break; } } return sqItem; } QString KisSqueezedComboBox::currentUnsqueezedText() { int curItem = currentIndex(); return m_originalItems[curItem]; } void KisSqueezedComboBox::removeSqueezedItem(int index) { removeItem(index); m_originalItems.remove(index); } diff --git a/libs/widgetutils/kis_double_parse_spin_box.cpp b/libs/widgetutils/kis_double_parse_spin_box.cpp index 50005053dd..47c4ead442 100644 --- a/libs/widgetutils/kis_double_parse_spin_box.cpp +++ b/libs/widgetutils/kis_double_parse_spin_box.cpp @@ -1,236 +1,237 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_double_parse_spin_box.h" #include "kis_num_parser.h" #include #include #include #include #include #include // for qIsNaN KisDoubleParseSpinBox::KisDoubleParseSpinBox(QWidget *parent) : QDoubleSpinBox(parent), boolLastValid(true), lastExprParsed(QStringLiteral("0.0")) { setAlignment(Qt::AlignRight); connect(this, SIGNAL(noMoreParsingError()), this, SLOT(clearErrorStyle())); //hack to let the clearError be called, even if the value changed method is the one from QDoubleSpinBox. connect(this, SIGNAL(valueChanged(double)), this, SLOT(clearError())); connect(this, SIGNAL(errorWhileParsing(QString)), this, SLOT(setErrorStyle())); oldValue = value(); warningIcon = new QLabel(this); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(16, 16)); } else { warningIcon->setText("!"); } warningIcon->setStyleSheet("background:transparent;"); warningIcon->move(1, 1); warningIcon->setVisible(false); isOldPaletteSaved = false; areOldMarginsSaved = false; } KisDoubleParseSpinBox::~KisDoubleParseSpinBox() { } double KisDoubleParseSpinBox::valueFromText(const QString & text) const { lastExprParsed = text; bool ok; double ret; if ( (suffix().isEmpty() || !text.endsWith(suffix())) && (prefix().isEmpty() || !text.startsWith(prefix())) ) { ret = KisNumericParser::parseSimpleMathExpr(text, &ok); } else { QString expr = text; if (text.endsWith(suffix())) { expr.remove(text.size()-suffix().size(), suffix().size()); } if(text.startsWith(prefix())){ expr.remove(0, prefix().size()); } lastExprParsed = expr; ret = KisNumericParser::parseSimpleMathExpr(expr, &ok); } if(qIsNaN(ret) || qIsInf(ret)){ ok = false; } if (!ok) { if (boolLastValid) { oldValue = value(); } boolLastValid = false; ret = oldValue; //in case of error set to minimum. } else { if (!boolLastValid) { oldValue = ret; } boolLastValid = true; } return ret; } QString KisDoubleParseSpinBox::textFromValue(double val) const { if (!boolLastValid) { emit errorWhileParsing(lastExprParsed); return lastExprParsed; } emit noMoreParsingError(); return QDoubleSpinBox::textFromValue(val); } QString KisDoubleParseSpinBox::veryCleanText() const { return cleanText(); } QValidator::State KisDoubleParseSpinBox::validate ( QString & input, int & pos ) const { Q_UNUSED(input); Q_UNUSED(pos); return QValidator::Acceptable; } void KisDoubleParseSpinBox::stepBy(int steps) { boolLastValid = true; //reset to valid state so we can use the up and down buttons. emit noMoreParsingError(); QDoubleSpinBox::stepBy(steps); } void KisDoubleParseSpinBox::setValue(double value) { - if(value == oldValue && hasFocus()){ //avoid to reset the button when it set the value of something that will recall this slot. + // Avoid to reset the button when it set the val of something that will recall this slot. + if(hasFocus() && QString::number( value, 'f', this->decimals()) == QString::number( oldValue, 'f', this->decimals())){ return; } QDoubleSpinBox::setValue(value); if (!hasFocus()) { clearError(); } } void KisDoubleParseSpinBox::setErrorStyle() { if (!boolLastValid) { //setStyleSheet(_oldStyleSheet + "Background: red; color: white; padding-left: 18px;"); if (!isOldPaletteSaved) { oldPalette = palette(); } isOldPaletteSaved = true; QPalette nP = oldPalette; nP.setColor(QPalette::Background, Qt::red); nP.setColor(QPalette::Base, Qt::red); nP.setColor(QPalette::Text, Qt::white); setPalette(nP); if (!areOldMarginsSaved) { oldMargins = lineEdit()->textMargins(); } areOldMarginsSaved = true; if (width() - height() >= 3*height()) { //if we have twice as much place as needed by the warning icon then display it. QMargins newMargins = oldMargins; newMargins.setLeft( newMargins.left() + height() - 4 ); lineEdit()->setTextMargins(newMargins); int h = warningIcon->height(); int hp = height()-2; if (h != hp) { warningIcon->resize(hp, hp); if (QFile(":/./16_light_warning.svg").exists()) { warningIcon->setPixmap(QIcon(":/./16_light_warning.svg").pixmap(hp-7, hp-7)); } } warningIcon->move(oldMargins.left()+4, 1); warningIcon->setVisible(true); } } } void KisDoubleParseSpinBox::clearErrorStyle() { if (boolLastValid) { warningIcon->setVisible(false); //setStyleSheet(QString()); setPalette(oldPalette); isOldPaletteSaved = false; lineEdit()->setTextMargins(oldMargins); areOldMarginsSaved = false; } } void KisDoubleParseSpinBox::clearError() { boolLastValid = true; emit noMoreParsingError(); oldValue = value(); clearErrorStyle(); } diff --git a/plugins/extensions/buginfo/dlg_buginfo.cpp b/plugins/extensions/buginfo/dlg_buginfo.cpp index 2a097360a2..1a65fa89c6 100644 --- a/plugins/extensions/buginfo/dlg_buginfo.cpp +++ b/plugins/extensions/buginfo/dlg_buginfo.cpp @@ -1,118 +1,126 @@ /* * 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 #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" #include 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; const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat); if (!kritarc.value("LogUsage", true).toBool() || !QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log").exists()) { // NOTE: This is intentionally not translated! // Krita version info info.append("Krita"); info.append("\n Version: ").append(KritaVersionWrapper::versionString(true)); info.append("\n\n"); info.append("Qt"); info.append("\n Version (compiled): ").append(QT_VERSION_STR); info.append("\n Version (loaded): ").append(qVersion()); info.append("\n\n"); // OS information info.append("OS Information"); info.append("\n Build ABI: ").append(QSysInfo::buildAbi()); info.append("\n Build CPU: ").append(QSysInfo::buildCpuArchitecture()); info.append("\n CPU: ").append(QSysInfo::currentCpuArchitecture()); info.append("\n Kernel Type: ").append(QSysInfo::kernelType()); info.append("\n Kernel Version: ").append(QSysInfo::kernelVersion()); info.append("\n Pretty Productname: ").append(QSysInfo::prettyProductName()); info.append("\n Product Type: ").append(QSysInfo::productType()); info.append("\n Product Version: ").append(QSysInfo::productVersion()); info.append("\n\n"); // OpenGL information info.append("\n").append(KisOpenGL::getDebugText()); info.append("\n\n"); // Hardware information info.append("Hardware Information"); info.append(QString("\n Memory: %1").arg(KisImageConfig(true).totalRAM() / 1024)).append(" Gb"); info.append(QString("\n Cores: %1").arg(QThread::idealThreadCount())); info.append("\n Swap: ").append(KisImageConfig(true).swapDir()); } else { - QFile f(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); - f.open(QFile::ReadOnly | QFile::Text); - info = QString::fromUtf8(f.readAll()); - f.close(); + + QFile sysinfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log"); + sysinfo.open(QFile::ReadOnly | QFile::Text); + info = QString::fromUtf8(sysinfo.readAll()); + sysinfo.close(); + + info += "\n\n"; + + QFile log(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log"); + log.open(QFile::ReadOnly | QFile::Text); + info += QString::fromUtf8(log.readAll()); + log.close(); } // calculate a default height for the widget int wheight = m_page->sizeHint().height(); m_page->txtBugInfo->setText(info); QFontMetrics fm = m_page->txtBugInfo->fontMetrics(); int target_height = fm.height() * info.split('\n').size() + wheight; QRect screen_rect = QGuiApplication::primaryScreen()->availableGeometry(); resize(m_page->size().width(), target_height > screen_rect.height() ? screen_rect.height() : target_height); 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/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui b/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui index b081e19102..53c0b931bc 100644 --- a/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui +++ b/plugins/flake/artistictextshape/ArtisticTextShapeConfigWidget.ui @@ -1,161 +1,156 @@ ArtisticTextShapeConfigWidget 0 0 259 73 0 0 0 0 0 0 - + 0 0 0 0 Qt::Vertical Qt::Vertical Qt::Horizontal 40 20 Qt::Vertical 20 40 KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
- - KoFontComboBox - QComboBox -
KoFontComboBox.h
-
diff --git a/plugins/flake/textshape/FontFamilyAction.cpp b/plugins/flake/textshape/FontFamilyAction.cpp index 09f424e073..58f267ceba 100644 --- a/plugins/flake/textshape/FontFamilyAction.cpp +++ b/plugins/flake/textshape/FontFamilyAction.cpp @@ -1,194 +1,194 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Reginald Stadlbauer (C) 1999 Simon Hausmann (C) 2000 Nicolas Hadacek (C) 2000 Kurt Granroth (C) 2000 Michael Koch (C) 2001 Holger Freyther (C) 2002 Ellis Whitehead (C) 2002 Joseph Wenninger (C) 2003 Andras Mantia (C) 2005-2006 Hamish Rodda (C) 2007 Clarence Dang (C) 2014 Dan Leinir Turthra Jensen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // This is a minorly modified version of the KFontAction class. It exists // entirely because there's a hang bug on windows at the moment. #include "FontFamilyAction.h" #include #include #include #include #include -#include +#include class KoFontFamilyAction::KoFontFamilyActionPrivate { public: KoFontFamilyActionPrivate(KoFontFamilyAction *parent) : q(parent) , settingFont(0) { } void _ko_slotFontChanged(const QFont &font) { - qDebug() << "KoFontComboBox - slotFontChanged(" + qDebug() << "QFontComboBox - slotFontChanged(" << font.family() << ") settingFont=" << settingFont; if (settingFont) { return; } q->setFont(font.family()); q->triggered(font.family()); qDebug() << "\tslotFontChanged done"; } KoFontFamilyAction *q; int settingFont; }; KoFontFamilyAction::KoFontFamilyAction(uint fontListCriteria, QObject *parent) : KSelectAction(parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, fontListCriteria); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::KoFontFamilyAction(QObject *parent) : KSelectAction(parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, 0); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::KoFontFamilyAction(const QString &text, QObject *parent) : KSelectAction(text, parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, 0); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::KoFontFamilyAction(const QIcon &icon, const QString &text, QObject *parent) : KSelectAction(icon, text, parent) , d(new KoFontFamilyActionPrivate(this)) { QStringList list; KFontChooser::getFontList(list, 0); KSelectAction::setItems(list); setEditable(true); } KoFontFamilyAction::~KoFontFamilyAction() { delete d; } QString KoFontFamilyAction::font() const { return currentText(); } QWidget *KoFontFamilyAction::createWidget(QWidget *parent) { qDebug() << "KoFontFamilyAction::createWidget()"; // silence unclear warning from original kfontaction.acpp // #ifdef __GNUC__ // #warning FIXME: items need to be converted // #endif // This is the visual element on the screen. This method overrides // the KSelectAction one, preventing KSelectAction from creating its // regular KComboBox. - KoFontComboBox *cb = new KoFontComboBox(parent); + QFontComboBox *cb = new QFontComboBox(parent); //cb->setFontList(items()); qDebug() << "\tset=" << font(); // Do this before connecting the signal so that nothing will fire. cb->setCurrentFont(QFont(font().toLower())); qDebug() << "\tspit back=" << cb->currentFont().family(); connect(cb, SIGNAL(currentFontChanged(QFont)), SLOT(_ko_slotFontChanged(QFont))); cb->setMinimumWidth(cb->sizeHint().width()); return cb; } /* * Maintenance note: Keep in sync with KFontComboBox::setCurrentFont() */ void KoFontFamilyAction::setFont(const QString &family) { qDebug() << "KoFontFamilyAction::setFont(" << family << ")"; // Suppress triggered(QString) signal and prevent recursive call to ourself. d->settingFont++; Q_FOREACH (QWidget *w, createdWidgets()) { - KoFontComboBox *cb = qobject_cast(w); + QFontComboBox *cb = qobject_cast(w); qDebug() << "\tw=" << w << "cb=" << cb; if (!cb) { continue; } cb->setCurrentFont(QFont(family.toLower())); qDebug() << "\t\tw spit back=" << cb->currentFont().family(); } d->settingFont--; qDebug() << "\tcalling setCurrentAction()"; QString lowerName = family.toLower(); if (setCurrentAction(lowerName, Qt::CaseInsensitive)) { return; } int i = lowerName.indexOf(" ["); if (i > -1) { lowerName = lowerName.left(i); i = 0; if (setCurrentAction(lowerName, Qt::CaseInsensitive)) { return; } } lowerName += " ["; if (setCurrentAction(lowerName, Qt::CaseInsensitive)) { return; } // TODO: Inconsistent state if KFontComboBox::setCurrentFont() succeeded // but setCurrentAction() did not and vice-versa. qDebug() << "Font not found " << family.toLower(); } // have to include this because of Q_PRIVATE_SLOT #include "moc_FontFamilyAction.cpp" diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 55e20d7b40..8f9618f226 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,407 +1,437 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } KraConverter::KraConverter(KisDocument *doc, QPointer updater) : m_doc(doc) , m_image(doc->savingImage()) , m_updater(updater) { } KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } +void fixCloneLayers(KisImageSP image, KisNodeSP root) +{ + KisNodeSP first = root->firstChild(); + KisNodeSP node = first; + while (!node.isNull()) { + if (node->inherits("KisCloneLayer")) { + KisCloneLayer* layer = dynamic_cast(node.data()); + if (layer && layer->copyFrom().isNull()) { + KisLayerSP reincarnation = layer->reincarnateAsPaintLayer(); + image->addNode(reincarnation, node->parent(), node->prevSibling()); + image->removeNode(node); + node = reincarnation; + } + } else if (node->childCount() > 0) { + fixCloneLayers(image, node); + } + node = node->nextSibling(); + } +} + KisImportExportErrorCode KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return ImportExportCodes::FileFormatIncorrect; } - bool success; + bool success = false; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc); if (res.isOk()) res = loadXML(doc, m_store); if (!res.isOk()) { return res; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return ImportExportCodes::FileFormatIncorrect; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; - if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) { + KisImportExportErrorCode resultHere = oldLoadAndParse(m_store, "documentinfo.xml", doc); + if (resultHere.isOk()) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } + fixCloneLayers(m_image, m_image->root()); + return success ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename) { setProgress(5); m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return ImportExportCodes::CannotCreateFile; } setProgress(20); m_kraSaver = new KisKraSaver(m_doc, filename); KisImportExportErrorCode resultCode = saveRootDocuments(m_store); if (!resultCode.isOk()) { return resultCode; } setProgress(40); bool result; result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } setProgress(60); result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } setProgress(70); result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); if (!result) { qWarning() << "saving palettes data failed"; } setProgress(80); if (!m_store->finalize()) { return ImportExportCodes::Failure; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return ImportExportCodes::Failure; } setProgress(90); return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return ImportExportCodes::NoAccessToWrite; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return ImportExportCodes::ErrorWhileWriting; } if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! bool success = dev.write(s.data(), s.size()); if (!success) { return ImportExportCodes::ErrorWhileWriting; } store->close(); } else { return ImportExportCodes::Failure; } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) KisImportExportErrorCode result = savePreview(store); (void)store->close(); if (!result.isOk()) { return result; } } else { return ImportExportCodes::Failure; } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); return ImportExportCodes::OK; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } KisImportExportErrorCode KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return ImportExportCodes::NoAccessToWrite; } bool ret = preview.save(&io, "PNG"); io.close(); return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting; } KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return ImportExportCodes::FileNotExist; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4", filename, errorLine, errorColumn, QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0))); return ImportExportCodes::FileFormatIncorrect; } dbgUI << "File" << filename << " loaded and parsed"; return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { errUI << "The format is not supported or the file is corrupted"; m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return ImportExportCodes::FileFormatIncorrect; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { errUI << "The file is too new for this version of Krita:" << syntaxVersion; m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return ImportExportCodes::FormatFeaturesUnsupported; } if (!root.hasChildNodes()) { errUI << "The file has no layers."; m_doc->setErrorMessage(i18n("The file has no layers.")); return ImportExportCodes::FileFormatIncorrect; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // reset the old image before loading the next one m_doc->setCurrentImage(0, false); for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { errUI << "Unknown error while opening the .kra file."; m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); errUI << m_kraLoader->errorMessages().join("\n"); } return ImportExportCodes::Failure; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); return ImportExportCodes::OK; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return ImportExportCodes::FileFormatIncorrect; } } } return ImportExportCodes::Failure; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); QString layerPathName = m_kraLoader->imageName(); if (!m_store->hasDirectory(layerPathName)) { // We might be hitting an encoding problem. Get the only folder in the toplevel Q_FOREACH (const QString &entry, m_store->directoryList()) { if (entry.contains("/layers/")) { layerPathName = entry.split("/layers/").first(); m_store->setSubstitution(m_kraLoader->imageName(), layerPathName); break; } } } m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_kraLoader->loadPalettes(store, m_doc); + if (!m_kraLoader->errorMessages().isEmpty()) { + m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); + return false; + } + m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; + return m_kraLoader->errorMessages().isEmpty(); } void KraConverter::cancel() { m_stop = true; } void KraConverter::setProgress(int progress) { if (m_updater) { m_updater->setProgress(progress); } } diff --git a/plugins/impex/libkra/kis_kra_load_visitor.cpp b/plugins/impex/libkra/kis_kra_load_visitor.cpp index c11dc50054..d495abd1ff 100644 --- a/plugins/impex/libkra/kis_kra_load_visitor.cpp +++ b/plugins/impex/libkra/kis_kra_load_visitor.cpp @@ -1,784 +1,789 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_kra_load_visitor.h" #include "kis_kra_tags.h" #include "flake/kis_shape_layer.h" #include "flake/KisReferenceImagesLayer.h" #include "KisReferenceImage.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // kritaimage #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_transform_mask_params_factory_registry.h" #include #include #include #include #include "kis_shape_selection.h" #include "kis_colorize_dom_utils.h" #include "kis_dom_utils.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_frames_interface.h" #include "kis_filter_registry.h" using namespace KRA; QString expandEncodedDirectory(const QString& _intern) { QString intern = _intern; QString result; int pos; while ((pos = intern.indexOf('/')) != -1) { if (QChar(intern.at(0)).isDigit()) result += "part"; result += intern.left(pos + 1); // copy numbers (or "pictures") + "/" intern = intern.mid(pos + 1); // remove the dir we just processed } if (!intern.isEmpty() && QChar(intern.at(0)).isDigit()) result += "part"; result += intern; return result; } KisKraLoadVisitor::KisKraLoadVisitor(KisImageSP image, KoStore *store, KoShapeControllerBase *shapeController, QMap &layerFilenames, QMap &keyframeFilenames, const QString & name, int syntaxVersion) : KisNodeVisitor() , m_image(image) , m_store(store) , m_external(false) , m_layerFilenames(layerFilenames) , m_keyframeFilenames(keyframeFilenames) , m_name(name) , m_shapeController(shapeController) { m_store->pushDirectory(); if (!m_store->enterDirectory(m_name)) { QStringList directories = m_store->directoryList(); dbgKrita << directories; if (directories.size() > 0) { dbgFile << "Could not locate the directory, maybe some encoding issue? Grab the first directory, that'll be the image one." << m_name << directories; m_name = directories.first(); } else { dbgFile << "Could not enter directory" << m_name << ", probably an old-style file with 'part' added."; m_name = expandEncodedDirectory(m_name); } } else { m_store->popDirectory(); } m_syntaxVersion = syntaxVersion; } void KisKraLoadVisitor::setExternalUri(const QString &uri) { m_external = true; m_uri = uri; } bool KisKraLoadVisitor::visit(KisExternalLayer * layer) { bool result = false; if (auto *referencesLayer = dynamic_cast(layer)) { Q_FOREACH(KoShape *shape, referencesLayer->shapes()) { auto *reference = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, false); while (!reference->loadImage(m_store)) { if (reference->embed()) { m_errorMessages << i18n("Could not load embedded reference image %1 ", reference->internalFile()); break; } else { QString msg = i18nc( "@info", "A reference image linked to an external file could not be loaded.\n\n" "Path: %1\n\n" "Do you want to select another location?", reference->filename()); int locateManually = QMessageBox::warning(0, i18nc("@title:window", "File not found"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); QString url; if (locateManually == QMessageBox::Yes) { KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); url = dialog.filename(); } if (url.isEmpty()) { break; } else { reference->setFilename(url); } } } } } else if (KisShapeLayer *shapeLayer = dynamic_cast(layer)) { if (!loadMetaData(layer)) { return false; } m_store->pushDirectory(); m_store->enterDirectory(getLocation(layer, DOT_SHAPE_LAYER)) ; result = shapeLayer->loadLayer(m_store); m_store->popDirectory(); } result = visitAll(layer) && result; return result; } bool KisKraLoadVisitor::visit(KisPaintLayer *layer) { loadNodeKeyframes(layer); if (!loadPaintDevice(layer->paintDevice(), getLocation(layer))) { return false; } if (!loadProfile(layer->paintDevice(), getLocation(layer, DOT_ICC))) { return false; } if (!loadMetaData(layer)) { return false; } if (m_syntaxVersion == 1) { // Check whether there is a file with a .mask extension in the // layer directory, if so, it's an old-style transparency mask // that should be converted. QString location = getLocation(layer, ".mask"); if (m_store->open(location)) { KisSelectionSP selection = KisSelectionSP(new KisSelection()); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (!pixelSelection->read(m_store->device())) { pixelSelection->disconnect(); } else { KisTransparencyMask* mask = new KisTransparencyMask(); mask->setSelection(selection); m_image->addNode(mask, layer, layer->firstChild()); } m_store->close(); } } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGroupLayer *layer) { if (*layer->colorSpace() != *m_image->colorSpace()) { layer->resetCache(m_image->colorSpace()); } if (!loadMetaData(layer)) { return false; } bool result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisAdjustmentLayer* layer) { loadNodeKeyframes(layer); // Adjustmentlayers are tricky: there's the 1.x style and the 2.x // style, which has selections with selection components bool result = true; if (m_syntaxVersion == 1) { KisSelectionSP selection = new KisSelection(); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); result = loadPaintDevice(pixelSelection, getLocation(layer, ".selection")); layer->setInternalSelection(selection); } else if (m_syntaxVersion == 2) { result = loadSelection(getLocation(layer), layer->internalSelection()); } else { // We use the default, empty selection } if (!loadMetaData(layer)) { return false; } loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); fixOldFilterConfigurations(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisGeneratorLayer *layer) { if (!loadMetaData(layer)) { return false; } bool result = true; loadNodeKeyframes(layer); result = loadSelection(getLocation(layer), layer->internalSelection()); // HACK ALERT: we set the same filter again to ensure the layer // is correctly updated result = loadFilterConfiguration(layer, getLocation(layer, DOT_FILTERCONFIG)); layer->setFilter(layer->filter()); result = visitAll(layer); return result; } bool KisKraLoadVisitor::visit(KisCloneLayer *layer) { if (!loadMetaData(layer)) { return false; } // the layer might have already been lazily initialized // from the mask loading code if (layer->copyFrom()) { return true; } KisNodeSP srcNode = layer->copyFromInfo().findNode(m_image->rootLayer()); - KisLayerSP srcLayer = qobject_cast(srcNode.data()); - Q_ASSERT(srcLayer); + if (!srcNode.isNull()) { + KisLayerSP srcLayer = qobject_cast(srcNode.data()); + Q_ASSERT(srcLayer); - layer->setCopyFrom(srcLayer); + layer->setCopyFrom(srcLayer); + } else { + m_warningMessages.append(i18nc("Loading a .kra file", "The file contains a clone layer that has an incorrect source node id. " + "This layer will be converted into a paint layer.")); + } // Clone layers have no data except for their masks bool result = visitAll(layer); return result; } void KisKraLoadVisitor::initSelectionForMask(KisMask *mask) { KisLayer *cloneLayer = dynamic_cast(mask->parent().data()); if (cloneLayer) { // the clone layers should be initialized out of order // and lazily, because their original() is still not // initialized cloneLayer->accept(*this); } KisLayer *parentLayer = qobject_cast(mask->parent().data()); // the KisKraLoader must have already set the parent for us Q_ASSERT(parentLayer); mask->initSelection(parentLayer); } bool KisKraLoadVisitor::visit(KisFilterMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); bool result = true; result = loadSelection(getLocation(mask), mask->selection()); result = loadFilterConfiguration(mask, getLocation(mask, DOT_FILTERCONFIG)); fixOldFilterConfigurations(mask->filter()); return result; } bool KisKraLoadVisitor::visit(KisTransformMask *mask) { QString location = getLocation(mask, DOT_TRANSFORMCONFIG); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement rootElement = doc.documentElement(); QDomElement main; if (!KisDomUtils::findOnlyElement(rootElement, "main", &main/*, &m_errorMessages*/)) { return false; } QString id = main.attribute("id", "not-valid"); if (id == "not-valid") { m_errorMessages << i18n("Could not load \"id\" of the transform mask"); return false; } QDomElement data; if (!KisDomUtils::findOnlyElement(rootElement, "data", &data, &m_errorMessages)) { return false; } KisTransformMaskParamsInterfaceSP params = KisTransformMaskParamsFactoryRegistry::instance()->createParams(id, data); if (!params) { m_errorMessages << i18n("Could not create transform mask params"); return false; } mask->setTransformParams(params); loadNodeKeyframes(mask); params->clearChangedFlag(); return true; } } return false; } bool KisKraLoadVisitor::visit(KisTransparencyMask *mask) { initSelectionForMask(mask); loadNodeKeyframes(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisSelectionMask *mask) { initSelectionForMask(mask); return loadSelection(getLocation(mask), mask->selection()); } bool KisKraLoadVisitor::visit(KisColorizeMask *mask) { m_store->pushDirectory(); QString location = getLocation(mask, DOT_COLORIZE_MASK); m_store->enterDirectory(location) ; QByteArray data; if (!m_store->extractFile("content.xml", data)) return false; QDomDocument doc; if (!doc.setContent(data)) return false; QVector strokes; if (!KisDomUtils::loadValue(doc.documentElement(), COLORIZE_KEYSTROKES_SECTION, &strokes, mask->colorSpace())) return false; int i = 0; Q_FOREACH (const KisLazyFillTools::KeyStroke &stroke, strokes) { const QString fileName = QString("%1_%2").arg(COLORIZE_KEYSTROKE).arg(i++); loadPaintDevice(stroke.dev, fileName); } mask->setKeyStrokesDirect(QList::fromVector(strokes)); loadPaintDevice(mask->coloringProjection(), COLORIZE_COLORING_DEVICE); mask->resetCache(); m_store->popDirectory(); return true; } QStringList KisKraLoadVisitor::errorMessages() const { return m_errorMessages; } QStringList KisKraLoadVisitor::warningMessages() const { return m_warningMessages; } struct SimpleDevicePolicy { bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->read(stream); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->setDefaultPixel(defaultPixel); } }; struct FramedDevicePolicy { FramedDevicePolicy(int frameId) : m_frameId(frameId) {} bool read(KisPaintDeviceSP dev, QIODevice *stream) { return dev->framesInterface()->readFrame(stream, m_frameId); } void setDefaultPixel(KisPaintDeviceSP dev, const KoColor &defaultPixel) const { return dev->framesInterface()->setFrameDefaultPixel(defaultPixel, m_frameId); } int m_frameId; }; bool KisKraLoadVisitor::loadPaintDevice(KisPaintDeviceSP device, const QString& location) { // Layer data KisPaintDeviceFramesInterface *frameInterface = device->framesInterface(); QList frames; if (frameInterface) { frames = device->framesInterface()->frames(); } if (!frameInterface || frames.count() <= 1) { return loadPaintDeviceFrame(device, location, SimpleDevicePolicy()); } else { KisRasterKeyframeChannel *keyframeChannel = device->keyframeChannel(); for (int i = 0; i < frames.count(); i++) { int id = frames[i]; if (keyframeChannel->frameFilename(id).isEmpty()) { m_warningMessages << i18n("Could not find keyframe pixel data for frame %1 in %2.", id, location); } else { Q_ASSERT(!keyframeChannel->frameFilename(id).isEmpty()); QString frameFilename = getLocation(keyframeChannel->frameFilename(id)); Q_ASSERT(!frameFilename.isEmpty()); if (!loadPaintDeviceFrame(device, frameFilename, FramedDevicePolicy(id))) { m_warningMessages << i18n("Could not load keyframe pixel data for frame %1 in %2.", id, location); } } } } return true; } template bool KisKraLoadVisitor::loadPaintDeviceFrame(KisPaintDeviceSP device, const QString &location, DevicePolicy policy) { { const int pixelSize = device->colorSpace()->pixelSize(); KoColor color(Qt::transparent, device->colorSpace()); if (m_store->open(location + ".defaultpixel")) { if (m_store->size() == pixelSize) { m_store->read((char*)color.data(), pixelSize); } m_store->close(); } policy.setDefaultPixel(device, color); } if (m_store->open(location)) { if (!policy.read(device, m_store->device())) { m_warningMessages << i18n("Could not read pixel data: %1.", location); device->disconnect(); m_store->close(); return true; } m_store->close(); } else { m_warningMessages << i18n("Could not load pixel data: %1.", location); return true; } return true; } bool KisKraLoadVisitor::loadProfile(KisPaintDeviceSP device, const QString& location) { if (m_store->hasFile(location)) { m_store->open(location); QByteArray data; data.resize(m_store->size()); dbgFile << "Data to load: " << m_store->size() << " from " << location << " with color space " << device->colorSpace()->id(); int read = m_store->read(data.data(), m_store->size()); dbgFile << "Profile size: " << data.size() << " " << m_store->atEnd() << " " << m_store->device()->bytesAvailable() << " " << read; m_store->close(); KoHashGenerator *hashGenerator = KoHashGeneratorProvider::instance()->getGenerator("MD5"); QByteArray hash = hashGenerator->generateHash(data); if (m_profileCache.contains(hash)) { if (device->setProfile(m_profileCache[hash], 0)) { return true; } } else { // Create a colorspace with the embedded profile const KoColorProfile *profile = KoColorSpaceRegistry::instance()->createColorProfile(device->colorSpace()->colorModelId().id(), device->colorSpace()->colorDepthId().id(), data); m_profileCache[hash] = profile; if (device->setProfile(profile, 0)) { return true; } } } m_warningMessages << i18n("Could not load profile: %1.", location); return true; } bool KisKraLoadVisitor::loadFilterConfiguration(KisNodeFilterInterface *nodeInterface, const QString& location) { KisFilterConfigurationSP kfc = nodeInterface->filter(); if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); if (!data.isEmpty()) { QDomDocument doc; doc.setContent(data); QDomElement e = doc.documentElement(); if (e.tagName() == "filterconfig") { kfc->fromLegacyXML(e); } else { kfc->fromXML(e); } loadDeprecatedFilter(kfc); return true; } } m_warningMessages << i18n("Could not filter configuration %1.", location); return true; } void KisKraLoadVisitor::fixOldFilterConfigurations(KisFilterConfigurationSP kfc) { KisFilterSP filter = KisFilterRegistry::instance()->value(kfc->name()); KIS_SAFE_ASSERT_RECOVER_RETURN(filter); if (!filter->configurationAllowedForMask(kfc)) { filter->fixLoadedFilterConfigurationForMasks(kfc); } KIS_SAFE_ASSERT_RECOVER_NOOP(filter->configurationAllowedForMask(kfc)); } bool KisKraLoadVisitor::loadMetaData(KisNode* node) { KisLayer* layer = qobject_cast(node); if (!layer) return true; KisMetaData::IOBackend* backend = KisMetaData::IOBackendRegistry::instance()->get("xmp"); if (!backend || !backend->supportLoading()) { if (backend) dbgFile << "Backend " << backend->id() << " does not support loading."; else dbgFile << "Could not load the XMP backend at all"; return true; } QString location = getLocation(node, QString(".") + backend->id() + DOT_METADATA); dbgFile << "going to load " << backend->id() << ", " << backend->name() << " from " << location; if (m_store->hasFile(location)) { QByteArray data; m_store->open(location); data = m_store->read(m_store->size()); m_store->close(); QBuffer buffer(&data); if (!backend->loadFrom(layer->metaData(), &buffer)) { m_warningMessages << i18n("Could not load metadata for layer %1.", layer->name()); } } return true; } bool KisKraLoadVisitor::loadSelection(const QString& location, KisSelectionSP dstSelection) { // by default the selection is expected to be fully transparent { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); KoColor transparent(Qt::transparent, pixelSelection->colorSpace()); pixelSelection->setDefaultPixel(transparent); } // Pixel selection bool result = true; QString pixelSelectionLocation = location + DOT_PIXEL_SELECTION; if (m_store->hasFile(pixelSelectionLocation)) { KisPixelSelectionSP pixelSelection = dstSelection->pixelSelection(); result = loadPaintDevice(pixelSelection, pixelSelectionLocation); if (!result) { m_warningMessages << i18n("Could not load raster selection %1.", location); } pixelSelection->invalidateOutlineCache(); } // Shape selection QString shapeSelectionLocation = location + DOT_SHAPE_SELECTION; if (m_store->hasFile(shapeSelectionLocation + "/content.svg") || m_store->hasFile(shapeSelectionLocation + "/content.xml")) { m_store->pushDirectory(); m_store->enterDirectory(shapeSelectionLocation) ; KisShapeSelection* shapeSelection = new KisShapeSelection(m_shapeController, m_image, dstSelection); dstSelection->setShapeSelection(shapeSelection); result = shapeSelection->loadSelection(m_store); m_store->popDirectory(); if (!result) { m_warningMessages << i18n("Could not load vector selection %1.", location); } } return true; } QString KisKraLoadVisitor::getLocation(KisNode* node, const QString& suffix) { return getLocation(m_layerFilenames[node], suffix); } QString KisKraLoadVisitor::getLocation(const QString &filename, const QString& suffix) { QString location = m_external ? QString() : m_uri; location += m_name + LAYER_PATH + filename + suffix; return location; } void KisKraLoadVisitor::loadNodeKeyframes(KisNode *node) { if (!m_keyframeFilenames.contains(node)) return; node->enableAnimation(); const QString &location = getLocation(m_keyframeFilenames[node]); if (!m_store->open(location)) { m_errorMessages << i18n("Could not load keyframes from %1.", location); return; } QString errorMsg; int errorLine; int errorColumn; QDomDocument dom; bool ok = dom.setContent(m_store->device(), &errorMsg, &errorLine, &errorColumn); m_store->close(); if (!ok) { m_errorMessages << i18n("parsing error in the keyframe file %1 at line %2, column %3\nError message: %4", location, errorLine, errorColumn, i18n(errorMsg.toUtf8())); return; } QDomElement root = dom.firstChildElement(); for (QDomElement child = root.firstChildElement(); !child.isNull(); child = child.nextSiblingElement()) { if (child.nodeName().toUpper() == "CHANNEL") { QString id = child.attribute("name"); KisKeyframeChannel *channel = node->getKeyframeChannel(id, true); if (!channel) { m_warningMessages << i18n("unknown keyframe channel type: %1 in %2", id, location); continue; } channel->loadXML(child); } } } void KisKraLoadVisitor::loadDeprecatedFilter(KisFilterConfigurationSP cfg) { if (cfg->getString("legacy") == "left edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "right edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "yGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "top edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xGrowth"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } else if (cfg->getString("legacy") == "bottom edge detections") { cfg->setProperty("horizRadius", 1); cfg->setProperty("vertRadius", 1); cfg->setProperty("type", "prewitt"); cfg->setProperty("output", "xFall"); cfg->setProperty("lockAspect", true); cfg->setProperty("transparency", false); } } diff --git a/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp b/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp index 9fd6fc0d07..052f9c555f 100644 --- a/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp +++ b/plugins/impex/ora/kis_open_raster_stack_save_visitor.cpp @@ -1,172 +1,178 @@ /* * Copyright (c) 2006-2007,2009 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_open_raster_stack_save_visitor.h" #include #include #include #include #include "kis_adjustment_layer.h" #include "filter/kis_filter.h" #include "filter/kis_filter_configuration.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include #include "kis_open_raster_save_context.h" #include #include struct KisOpenRasterStackSaveVisitor::Private { Private() {} KisOpenRasterSaveContext* saveContext; QDomDocument layerStack; QDomElement currentElement; vKisNodeSP activeNodes; }; KisOpenRasterStackSaveVisitor::KisOpenRasterStackSaveVisitor(KisOpenRasterSaveContext* saveContext, vKisNodeSP activeNodes) : d(new Private) { d->saveContext = saveContext; d->activeNodes = activeNodes; } KisOpenRasterStackSaveVisitor::~KisOpenRasterStackSaveVisitor() { delete d; } void KisOpenRasterStackSaveVisitor::saveLayerInfo(QDomElement& elt, KisLayer* layer) { elt.setAttribute("name", layer->name()); elt.setAttribute("opacity", QString().setNum(layer->opacity() / 255.0)); elt.setAttribute("visibility", layer->visible() ? "visible" : "hidden"); + elt.setAttribute("x", QString().setNum(layer->x())); + elt.setAttribute("y", QString().setNum(layer->y())); if (layer->userLocked()) { elt.setAttribute("edit-locked", "true"); } if (d->activeNodes.contains(layer)) { elt.setAttribute("selected", "true"); } QString compop = layer->compositeOpId(); if (layer->compositeOpId() == COMPOSITE_CLEAR) compop = "svg:clear"; else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over"; else if (layer->compositeOpId() == COMPOSITE_ERASE) compop = "svg:dst-out"; else if (layer->alphaChannelDisabled()) compop = "svg:src-atop"; else if (layer->compositeOpId() == COMPOSITE_DESTINATION_ATOP) compop = "svg:dst-atop"; else if (layer->compositeOpId() == COMPOSITE_DESTINATION_IN) compop = "svg:dst-in"; else if (layer->compositeOpId() == COMPOSITE_ADD) compop = "svg:plus"; else if (layer->compositeOpId() == COMPOSITE_MULT) compop = "svg:multiply"; else if (layer->compositeOpId() == COMPOSITE_SCREEN) compop = "svg:screen"; else if (layer->compositeOpId() == COMPOSITE_OVERLAY) compop = "svg:overlay"; else if (layer->compositeOpId() == COMPOSITE_DARKEN) compop = "svg:darken"; else if (layer->compositeOpId() == COMPOSITE_LIGHTEN) compop = "svg:lighten"; else if (layer->compositeOpId() == COMPOSITE_DODGE) compop = "svg:color-dodge"; else if (layer->compositeOpId() == COMPOSITE_BURN) compop = "svg:color-burn"; else if (layer->compositeOpId() == COMPOSITE_HARD_LIGHT) compop = "svg:hard-light"; else if (layer->compositeOpId() == COMPOSITE_SOFT_LIGHT_SVG) compop = "svg:soft-light"; else if (layer->compositeOpId() == COMPOSITE_DIFF) compop = "svg:difference"; else if (layer->compositeOpId() == COMPOSITE_COLOR) compop = "svg:color"; else if (layer->compositeOpId() == COMPOSITE_LUMINIZE) compop = "svg:luminosity"; else if (layer->compositeOpId() == COMPOSITE_HUE) compop = "svg:hue"; else if (layer->compositeOpId() == COMPOSITE_SATURATION) compop = "svg:saturation"; //else if (layer->compositeOpId() == COMPOSITE_EXCLUSION) compop = "svg:exclusion"; else compop = "krita:" + layer->compositeOpId(); elt.setAttribute("composite-op", compop); } bool KisOpenRasterStackSaveVisitor::visit(KisPaintLayer *layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::visit(KisGeneratorLayer* layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::visit(KisGroupLayer *layer) { QDomElement previousElt = d->currentElement; QDomElement elt = d->layerStack.createElement("stack"); d->currentElement = elt; saveLayerInfo(elt, layer); QString isolate = "isolate"; if (layer->passThroughMode()) { isolate = "auto"; } elt.setAttribute("isolation", isolate); visitAll(layer); if (!previousElt.isNull()) { previousElt.insertBefore(elt, QDomNode()); d->currentElement = previousElt; } else { QDomElement imageElt = d->layerStack.createElement("image"); int width = layer->image()->width(); int height = layer->image()->height(); int xRes = (int)(qRound(layer->image()->xRes() * 72)); int yRes = (int)(qRound(layer->image()->yRes() * 72)); imageElt.setAttribute("version", "0.0.1"); imageElt.setAttribute("w", width); imageElt.setAttribute("h", height); imageElt.setAttribute("xres", xRes); imageElt.setAttribute("yres", yRes); imageElt.appendChild(elt); d->layerStack.insertBefore(imageElt, QDomNode()); d->currentElement = QDomElement(); d->saveContext->saveStack(d->layerStack); } return true; } bool KisOpenRasterStackSaveVisitor::visit(KisAdjustmentLayer *layer) { QDomElement elt = d->layerStack.createElement("filter"); saveLayerInfo(elt, layer); elt.setAttribute("type", "applications:krita:" + layer->filter()->name()); return true; } bool KisOpenRasterStackSaveVisitor::visit(KisCloneLayer *layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::visit(KisExternalLayer * layer) { return saveLayer(layer); } bool KisOpenRasterStackSaveVisitor::saveLayer(KisLayer *layer) { - QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), layer->image()->bounds(), layer->image()->xRes(), layer->image()->yRes()); + + // here we adjust the bounds to encompass the entire area of the layer with color data by adding the current offsets + QRect adjustedBounds = layer->image()->bounds(); + adjustedBounds.adjust(layer->x(), layer->y(), layer->x(), layer->y()); + QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), adjustedBounds, layer->image()->xRes(), layer->image()->yRes()); QDomElement elt = d->layerStack.createElement("layer"); saveLayerInfo(elt, layer); elt.setAttribute("src", filename); d->currentElement.insertBefore(elt, QDomNode()); return true; } diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp index 36b94686aa..b89263c04d 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/rsliderspinbox.cpp @@ -1,773 +1,772 @@ /** =========================================================== * @file * * This file is a part of digiKam project * https://www.digikam.org * * @date 2014-11-30 * @brief Save space slider widget * * @author Copyright (C) 2014 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2010 by Justin Noel * justin at ics dot com * @author Copyright (C) 2010 by Cyrille Berger * cberger at cberger dot net * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "rsliderspinbox.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include namespace KDcrawIface { class RAbstractSliderSpinBoxPrivate { public: RAbstractSliderSpinBoxPrivate() { edit = 0; validator = 0; dummySpinBox = 0; upButtonDown = false; downButtonDown = false; shiftMode = false; factor = 1.0; fastSliderStep = 5; slowFactor = 0.1; shiftPercent = 0.0; exponentRatio = 0.0; value = 0; maximum = 100; minimum = 0; singleStep = 1; } QLineEdit* edit; QDoubleValidator* validator; bool upButtonDown; bool downButtonDown; int factor; int fastSliderStep; double slowFactor; double shiftPercent; bool shiftMode; QString suffix; double exponentRatio; int value; int maximum; int minimum; int singleStep; QSpinBox* dummySpinBox; }; RAbstractSliderSpinBox::RAbstractSliderSpinBox(QWidget* const parent, RAbstractSliderSpinBoxPrivate* const q) : QWidget(parent), d_ptr(q) { Q_D(RAbstractSliderSpinBox); d->edit = new QLineEdit(this); d->edit->setFrame(false); d->edit->setAlignment(Qt::AlignCenter); d->edit->hide(); d->edit->installEventFilter(this); // Make edit transparent d->edit->setAutoFillBackground(false); QPalette pal = d->edit->palette(); pal.setColor(QPalette::Base, Qt::transparent); d->edit->setPalette(pal); connect(d->edit, SIGNAL(editingFinished()), this, SLOT(editLostFocus())); d->validator = new QDoubleValidator(d->edit); d->edit->setValidator(d->validator); setExponentRatio(1.0); // Set sane defaults setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); // dummy needed to fix a bug in the polyester theme d->dummySpinBox = new QSpinBox(this); d->dummySpinBox->hide(); } RAbstractSliderSpinBox::~RAbstractSliderSpinBox() { Q_D(RAbstractSliderSpinBox); delete d; } void RAbstractSliderSpinBox::showEdit() { Q_D(RAbstractSliderSpinBox); if (d->edit->isVisible()) return; d->edit->setGeometry(editRect(spinBoxOptions())); d->edit->setText(valueString()); d->edit->selectAll(); d->edit->show(); d->edit->setFocus(Qt::OtherFocusReason); update(); } void RAbstractSliderSpinBox::hideEdit() { Q_D(RAbstractSliderSpinBox); d->edit->hide(); update(); } void RAbstractSliderSpinBox::paintEvent(QPaintEvent* e) { Q_D(RAbstractSliderSpinBox); Q_UNUSED(e) QPainter painter(this); // Create options to draw spin box parts QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Draw "SpinBox".Clip off the area of the lineEdit to avoid double borders being drawn painter.save(); painter.setClipping(true); QRect eraseRect(QPoint(rect().x(), rect().y()), QPoint(editRect(spinOpts).right(), rect().bottom())); painter.setClipRegion(QRegion(rect()).subtracted(eraseRect)); style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox); painter.setClipping(false); painter.restore(); // Create options to draw progress bar parts QStyleOptionProgressBar progressOpts = progressBarOptions(); // Draw "ProgressBar" in SpinBox style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0); // Draw focus if necessary if (hasFocus() && d->edit->hasFocus()) { QStyleOptionFocusRect focusOpts; focusOpts.initFrom(this); focusOpts.rect = progressOpts.rect; focusOpts.backgroundColor = palette().color(QPalette::Window); style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this); } } void RAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e) { Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Depress buttons or highlight slider. Also used to emulate mouse grab. if (e->buttons() & Qt::LeftButton) { if (upButtonRect(spinOpts).contains(e->pos())) { d->upButtonDown = true; } else if (downButtonRect(spinOpts).contains(e->pos())) { d->downButtonDown = true; } } else if (e->buttons() & Qt::RightButton) { showEdit(); } update(); } void RAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e) { Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Step up/down for buttons. Emulating mouse grab too. if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown) { setInternalValue(d->value + d->singleStep); } else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown) { setInternalValue(d->value - d->singleStep); } else if (editRect(spinOpts).contains(e->pos()) && !(d->edit->isVisible()) && !(d->upButtonDown || d->downButtonDown)) { // Snap to percentage for progress area setInternalValue(valueForX(e->pos().x(),e->modifiers())); } d->upButtonDown = false; d->downButtonDown = false; update(); } void RAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e) { Q_D(RAbstractSliderSpinBox); if( e->modifiers() & Qt::ShiftModifier ) { if( !d->shiftMode ) { d->shiftPercent = pow(double(d->value - d->minimum)/double(d->maximum - d->minimum), 1/double(d->exponentRatio)); d->shiftMode = true; } } else { d->shiftMode = false; } // Respect emulated mouse grab. if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown)) { setInternalValue(valueForX(e->pos().x(),e->modifiers())); update(); } } void RAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e) { Q_D(RAbstractSliderSpinBox); switch (e->key()) { case Qt::Key_Up: case Qt::Key_Right: setInternalValue(d->value + d->singleStep); break; case Qt::Key_Down: case Qt::Key_Left: setInternalValue(d->value - d->singleStep); break; case Qt::Key_Shift: d->shiftPercent = pow( double(d->value - d->minimum)/double(d->maximum - d->minimum), 1/double(d->exponentRatio) ); d->shiftMode = true; break; case Qt::Key_Enter: // Line edit isn't "accepting" key strokes... case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_AltGr: case Qt::Key_Super_L: case Qt::Key_Super_R: break; default: showEdit(); d->edit->event(e); break; } } void RAbstractSliderSpinBox::wheelEvent(QWheelEvent *e) { Q_D(RAbstractSliderSpinBox); int step = d->fastSliderStep; if( e->modifiers() & Qt::ShiftModifier ) { step = d->singleStep; } if ( e->delta() > 0) { setInternalValue(d->value + step); } else { setInternalValue(d->value - step); } update(); e->accept(); } bool RAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e) { Q_D(RAbstractSliderSpinBox); if (recv == static_cast(d->edit) && e->type() == QEvent::KeyRelease) { QKeyEvent* const keyEvent = static_cast(e); switch (keyEvent->key()) { case Qt::Key_Enter: case Qt::Key_Return: setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor); hideEdit(); return true; case Qt::Key_Escape: hideEdit(); return true; default: break; } } return false; } QSize RAbstractSliderSpinBox::sizeHint() const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QFontMetrics fm(font()); // We need at least 50 pixels or things start to look bad int w = qMax(fm.width(QString::number(d->maximum)), 50); QSize hint(w, d->edit->sizeHint().height() + 3); // Getting the size of the buttons is a pain as the calcs require a rect // that is "big enough". We run the calc twice to get the "smallest" buttons // This code was inspired by QAbstractSpinBox. QSize extra(35, 6); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); spinOpts.rect.setSize(hint + extra); extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts, QStyle::SC_SpinBoxEditField, this).size(); hint += extra; spinOpts.rect = rect(); - return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint, 0) - .expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint, 0); } QSize RAbstractSliderSpinBox::minimumSizeHint() const { return sizeHint(); } QStyleOptionSpinBox RAbstractSliderSpinBox::spinBoxOptions() const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox opts; opts.initFrom(this); opts.frame = false; opts.buttonSymbols = QAbstractSpinBox::UpDownArrows; opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown; // Disable non-logical buttons if (d->value == d->minimum) { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled; } else if (d->value == d->maximum) { opts.stepEnabled = QAbstractSpinBox::StepDownEnabled; } else { opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled; } // Deal with depressed buttons if (d->upButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxUp; } else if (d->downButtonDown) { opts.activeSubControls = QStyle::SC_SpinBoxDown; } else { opts.activeSubControls = 0; } return opts; } QStyleOptionProgressBar RAbstractSliderSpinBox::progressBarOptions() const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); // Create opts for drawing the progress portion QStyleOptionProgressBar progressOpts; progressOpts.initFrom(this); progressOpts.maximum = d->maximum; progressOpts.minimum = d->minimum; double minDbl = d->minimum; double dValues = (d->maximum - minDbl); progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl; progressOpts.text = valueString() + d->suffix; progressOpts.textAlignment = Qt::AlignCenter; progressOpts.textVisible = !(d->edit->isVisible()); // Change opts rect to be only the ComboBox's text area progressOpts.rect = editRect(spinOpts); return progressOpts; } QRect RAbstractSliderSpinBox::editRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxEditField); } QRect RAbstractSliderSpinBox::progressRect(const QStyleOptionProgressBar& progressBarOptions) const { return style()->subElementRect(QStyle::SE_ProgressBarGroove, &progressBarOptions); } QRect RAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxUp); } QRect RAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const { return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions, QStyle::SC_SpinBoxDown); } int RAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const { const Q_D(RAbstractSliderSpinBox); QStyleOptionSpinBox spinOpts = spinBoxOptions(); QStyleOptionProgressBar progressOpts = progressBarOptions(); // Adjust for magic number in style code (margins) QRect correctedProgRect = progressRect(progressOpts).adjusted(2, 2, -2, -2); // Compute the distance of the progress bar, in pixel double leftDbl = correctedProgRect.left(); double xDbl = x - leftDbl; // Compute the ration of the progress bar used, linearly (ignoring the exponent) double rightDbl = correctedProgRect.right(); double minDbl = d->minimum; double maxDbl = d->maximum; double dValues = (maxDbl - minDbl); double percent = (xDbl / (rightDbl - leftDbl)); // If SHIFT is pressed, movement should be slowed. if ( modifiers & Qt::ShiftModifier ) { percent = d->shiftPercent + (percent - d->shiftPercent) * d->slowFactor; } // Final value double realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl); // If key CTRL is pressed, round to the closest step. if ( modifiers & Qt::ControlModifier ) { double fstep = d->fastSliderStep; if( modifiers & Qt::ShiftModifier ) { fstep *= d->slowFactor; } realvalue = floor((realvalue + fstep / 2) / fstep) * fstep; } // Return the value return int(realvalue); } void RAbstractSliderSpinBox::setSuffix(const QString& suffix) { Q_D(RAbstractSliderSpinBox); d->suffix = suffix; } void RAbstractSliderSpinBox::setExponentRatio(double dbl) { Q_D(RAbstractSliderSpinBox); Q_ASSERT(dbl > 0); d->exponentRatio = dbl; } void RAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event) { event->accept(); } void RAbstractSliderSpinBox::editLostFocus() { // only hide on focus lost, if editing is finished that will be handled in eventFilter Q_D(RAbstractSliderSpinBox); if (!d->edit->hasFocus()) { hideEdit(); } } // --------------------------------------------------------------------------------------------- class RSliderSpinBoxPrivate : public RAbstractSliderSpinBoxPrivate { }; RSliderSpinBox::RSliderSpinBox(QWidget* const parent) : RAbstractSliderSpinBox(parent, new RSliderSpinBoxPrivate) { setRange(0,99); } RSliderSpinBox::~RSliderSpinBox() { } void RSliderSpinBox::setRange(int minimum, int maximum) { Q_D(RSliderSpinBox); d->minimum = minimum; d->maximum = maximum; d->fastSliderStep = (maximum-minimum+1)/20; d->validator->setRange(minimum, maximum, 0); update(); } int RSliderSpinBox::minimum() const { const Q_D(RSliderSpinBox); return d->minimum; } void RSliderSpinBox::setMinimum(int minimum) { Q_D(RSliderSpinBox); setRange(minimum, d->maximum); } int RSliderSpinBox::maximum() const { const Q_D(RSliderSpinBox); return d->maximum; } void RSliderSpinBox::setMaximum(int maximum) { Q_D(RSliderSpinBox); setRange(d->minimum, maximum); } int RSliderSpinBox::fastSliderStep() const { const Q_D(RSliderSpinBox); return d->fastSliderStep; } void RSliderSpinBox::setFastSliderStep(int step) { Q_D(RSliderSpinBox); d->fastSliderStep = step; } int RSliderSpinBox::value() const { const Q_D(RSliderSpinBox); return d->value; } void RSliderSpinBox::setValue(int value) { setInternalValue(value); update(); } QString RSliderSpinBox::valueString() const { const Q_D(RSliderSpinBox); return QLocale::system().toString(d->value); } void RSliderSpinBox::setSingleStep(int value) { Q_D(RSliderSpinBox); d->singleStep = value; } void RSliderSpinBox::setPageStep(int value) { Q_UNUSED(value); } void RSliderSpinBox::setInternalValue(int _value) { Q_D(RAbstractSliderSpinBox); d->value = qBound(d->minimum, _value, d->maximum); emit(valueChanged(value())); } // --------------------------------------------------------------------------------------------- class RDoubleSliderSpinBoxPrivate : public RAbstractSliderSpinBoxPrivate { }; RDoubleSliderSpinBox::RDoubleSliderSpinBox(QWidget* const parent) : RAbstractSliderSpinBox(parent, new RDoubleSliderSpinBoxPrivate) { } RDoubleSliderSpinBox::~RDoubleSliderSpinBox() { } void RDoubleSliderSpinBox::setRange(double minimum, double maximum, int decimals) { Q_D(RDoubleSliderSpinBox); d->factor = pow(10.0, decimals); d->minimum = minimum * d->factor; d->maximum = maximum * d->factor; // This code auto-compute a new step when pressing control. // A flag defaulting to "do not change the fast step" should be added, but it implies changing every call if (maximum - minimum >= 2.0 || decimals <= 0) { //Quick step on integers d->fastSliderStep = int(pow(10.0, decimals)); } else if(decimals == 1) { d->fastSliderStep = (maximum-minimum)*d->factor/10; } else { d->fastSliderStep = (maximum-minimum)*d->factor/20; } d->validator->setRange(minimum, maximum, decimals); update(); setValue(value()); } double RDoubleSliderSpinBox::minimum() const { const Q_D(RAbstractSliderSpinBox); return d->minimum / d->factor; } void RDoubleSliderSpinBox::setMinimum(double minimum) { Q_D(RAbstractSliderSpinBox); setRange(minimum, d->maximum); } double RDoubleSliderSpinBox::maximum() const { const Q_D(RAbstractSliderSpinBox); return d->maximum / d->factor; } void RDoubleSliderSpinBox::setMaximum(double maximum) { Q_D(RAbstractSliderSpinBox); setRange(d->minimum, maximum); } double RDoubleSliderSpinBox::fastSliderStep() const { const Q_D(RAbstractSliderSpinBox); return d->fastSliderStep; } void RDoubleSliderSpinBox::setFastSliderStep(double step) { Q_D(RAbstractSliderSpinBox); d->fastSliderStep = step * d->factor; } double RDoubleSliderSpinBox::value() const { const Q_D(RAbstractSliderSpinBox); return (double)d->value / d->factor; } void RDoubleSliderSpinBox::setValue(double value) { Q_D(RAbstractSliderSpinBox); setInternalValue(d->value = qRound(value * d->factor)); update(); } void RDoubleSliderSpinBox::setSingleStep(double value) { Q_D(RAbstractSliderSpinBox); d->singleStep = value * d->factor; } QString RDoubleSliderSpinBox::valueString() const { const Q_D(RAbstractSliderSpinBox); return QLocale::system().toString((double)d->value / d->factor, 'f', d->validator->decimals()); } void RDoubleSliderSpinBox::setInternalValue(int val) { Q_D(RAbstractSliderSpinBox); d->value = qBound(d->minimum, val, d->maximum); emit(valueChanged(value())); } } // namespace KDcrawIface diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp index d9df006a8e..ffa40dee49 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/squeezedcombobox.cpp @@ -1,196 +1,195 @@ /** =========================================================== * @file * * This file is a part of digiKam project * https://www.digikam.org * * @date 2008-08-21 * @brief a combo box with a width not depending of text * content size * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2008 by Andi Clemens * andi dot clemens at googlemail dot com * @author Copyright (C) 2005 by Tom Albers * tomalbers at kde dot nl * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "squeezedcombobox.h" // Qt includes #include #include #include #include #include namespace KDcrawIface { class Q_DECL_HIDDEN SqueezedComboBox::Private { public: Private() { timer = 0; } QMap originalItems; QTimer* timer; }; SqueezedComboBox::SqueezedComboBox(QWidget* const parent, const char* name) : QComboBox(parent), d(new Private) { setObjectName(name); setMinimumWidth(100); d->timer = new QTimer(this); d->timer->setSingleShot(true); connect(d->timer, &QTimer::timeout, this, &SqueezedComboBox::slotTimeOut); connect(this, static_cast(&SqueezedComboBox::activated), this, &SqueezedComboBox::slotUpdateToolTip); } SqueezedComboBox::~SqueezedComboBox() { d->originalItems.clear(); delete d->timer; delete d; } bool SqueezedComboBox::contains(const QString& text) const { if (text.isEmpty()) return false; for (QMap::const_iterator it = d->originalItems.constBegin() ; it != d->originalItems.constEnd(); ++it) { if (it.value() == text) return true; } return false; } QSize SqueezedComboBox::sizeHint() const { ensurePolished(); QFontMetrics fm = fontMetrics(); int maxW = count() ? 18 : 7 * fm.width(QChar('x')) + 18; int maxH = qMax( fm.lineSpacing(), 14 ) + 2; QStyleOptionComboBox options; options.initFrom(this); - return style()->sizeFromContents(QStyle::CT_ComboBox, &options, - QSize(maxW, maxH), this).expandedTo(QApplication::globalStrut()); + return style()->sizeFromContents(QStyle::CT_ComboBox, &options, QSize(maxW, maxH), this); } void SqueezedComboBox::insertSqueezedItem(const QString& newItem, int index, const QVariant& userData) { d->originalItems[index] = newItem; QComboBox::insertItem(index, squeezeText(newItem), userData); // if this is the first item, set the tooltip. if (index == 0) slotUpdateToolTip(0); } void SqueezedComboBox::insertSqueezedList(const QStringList& newItems, int index) { for(QStringList::const_iterator it = newItems.constBegin() ; it != newItems.constEnd() ; ++it) { insertSqueezedItem(*it, index); index++; } } void SqueezedComboBox::addSqueezedItem(const QString& newItem, const QVariant& userData) { insertSqueezedItem(newItem, count(), userData); } void SqueezedComboBox::setCurrent(const QString& itemText) { QString squeezedText = squeezeText(itemText); qint32 itemIndex = findText(squeezedText); if (itemIndex >= 0) setCurrentIndex(itemIndex); } void SqueezedComboBox::resizeEvent(QResizeEvent *) { d->timer->start(200); } void SqueezedComboBox::slotTimeOut() { for (QMap::iterator it = d->originalItems.begin() ; it != d->originalItems.end(); ++it) { setItemText( it.key(), squeezeText( it.value() ) ); } } QString SqueezedComboBox::squeezeText(const QString& original) const { // not the complete widgetSize is usable. Need to compensate for that. int widgetSize = width()-30; QFontMetrics fm( fontMetrics() ); // If we can fit the full text, return that. if (fm.width(original) < widgetSize) return(original); // We need to squeeze. QString sqItem = original; // prevent empty return value; widgetSize = widgetSize-fm.width("..."); for (int i = 0 ; i != original.length(); ++i) { if ((int)fm.width(original.right(i)) > widgetSize) { sqItem = QString(original.left(i) + "..."); break; } } return sqItem; } void SqueezedComboBox::slotUpdateToolTip(int index) { setToolTip(d->originalItems[index]); } QString SqueezedComboBox::currentUnsqueezedText() const { int curItem = currentIndex(); return d->originalItems[curItem]; } QString SqueezedComboBox::item(int index) const { return d->originalItems[index]; } } // namespace KDcrawIface diff --git a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop index 544eda162d..fb4a784a97 100644 --- a/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop +++ b/plugins/python/assignprofiledialog/kritapykrita_assignprofiledialog.desktop @@ -1,57 +1,59 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=assignprofiledialog X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Assign Profile to Image Name[ar]=إسناد اللاحات إلى الصور Name[ca]=Assigna un perfil a una imatge Name[ca@valencia]=Assigna un perfil a una imatge Name[cs]=Přiřadit obrázku profil Name[el]=Αντιστοίχιση προφίλ σε εικόνα Name[en_GB]=Assign Profile to Image Name[es]=Asignar perfil a imagen +Name[et]=Pildile profiili omistamine Name[eu]=Esleitu profila irudiari Name[fi]=Liitä kuvaan profiili Name[fr]=Attribuer un profil à l'image Name[gl]=Asignar un perfil á imaxe Name[is]=Úthluta litasniði á myndina Name[it]=Assegna profilo a immagine Name[ko]=이미지에 프로필 할당 Name[nl]=Profiel aan afbeelding toewijzen Name[nn]=Tildel profil til bilete Name[pl]=Przypisz profil do obrazu Name[pt]=Atribuir um Perfil à Imagem Name[pt_BR]=Atribuir perfil a imagem Name[sv]=Tilldela profil till bild Name[tr]=Görüntüye Profil Ata Name[uk]=Призначити профіль до зображення Name[x-test]=xxAssign Profile to Imagexx Name[zh_CN]=为图像指定特性文件 Name[zh_TW]=指定設定檔到圖像 Comment=Assign a profile to an image without converting it. Comment[ar]=أسنِد لاحة إلى صورة دون تحويلها. Comment[ca]=Assigna un perfil a una imatge sense convertir-la. Comment[ca@valencia]=Assigna un perfil a una imatge sense convertir-la. Comment[el]=Αντιστοιχίζει ένα προφίλ σε μια εικόνα χωρίς μετατροπή. Comment[en_GB]=Assign a profile to an image without converting it. Comment[es]=Asignar un perfil a una imagen sin convertirla. +Comment[et]=Pildile profiili omistamine ilma seda teisendamata. Comment[eu]=Esleitu profil bat irudi bati hura bihurtu gabe. Comment[fi]=Liitä kuvaan profiili muuntamatta kuvaa Comment[fr]=Attribuer un profil à une image sans la convertir. Comment[gl]=Asignar un perfil a unha imaxe sen convertela. Comment[is]=Úthluta litasniði á myndina án þess að umbreyta henni. Comment[it]=Assegna un profilo a un'immagine senza convertirla. Comment[ko]=프로필을 변환하지 않고 이미지에 할당합니다. Comment[nl]=Een profiel aan een afbeelding toewijzen zonder het te converteren. Comment[nn]=Tildel fargeprofil til eit bilete utan å konvertera det til profilen Comment[pl]=Przypisz profil do obrazu bez jego przekształcania. Comment[pt]=Atribui um perfil à imagem sem a converter. Comment[pt_BR]=Atribui um perfil para uma imagem sem convertê-la. Comment[sv]=Tilldela en profil till en bild utan att konvertera den. Comment[tr]=Bir görüntüye, görüntüyü değiştirmeden bir profil ata. Comment[uk]=Призначити профіль до зображення без його перетворення. Comment[x-test]=xxAssign a profile to an image without converting it.xx Comment[zh_CN]=仅为图像指定特性文件,不转换其色彩空间 Comment[zh_TW]=將設定檔指定給圖像,而不進行轉換。 diff --git a/plugins/python/colorspace/kritapykrita_colorspace.desktop b/plugins/python/colorspace/kritapykrita_colorspace.desktop index 11c1c3415c..229e5afa66 100644 --- a/plugins/python/colorspace/kritapykrita_colorspace.desktop +++ b/plugins/python/colorspace/kritapykrita_colorspace.desktop @@ -1,59 +1,61 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=colorspace X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Color Space Name[ar]=الفضاء اللوني Name[ca]=Espai de color Name[ca@valencia]=Espai de color Name[cs]=Barevný prostor Name[de]=Farbraum Name[el]=Χρωματικός χώρος Name[en_GB]=Colour Space Name[es]=Espacio de color +Name[et]=Värviruum Name[eu]=Kolore-espazioa Name[fi]=Väriavaruus Name[fr]=Espace colorimétrique Name[gl]=Espazo de cores Name[is]=Litrýmd Name[it]=Spazio dei colori Name[ko]=색상 공간 Name[nl]=Kleurruimte Name[nn]=Fargerom Name[pl]=Przestrzeń barw Name[pt]=Espaço de Cores Name[pt_BR]=Espaço de cores Name[sk]=Farebný priestor Name[sv]=Färgrymd Name[tr]=Renk Aralığı Name[uk]=Простір кольорів Name[x-test]=xxColor Spacexx Name[zh_CN]=色彩空间 Name[zh_TW]=色彩空間 Comment=Plugin to change color space to selected documents Comment[ar]=ملحقة لتغيير الفضاء اللوني في المستندات المحددة Comment[ca]=Un connector per a canviar l'espai de color dels documents seleccionats Comment[ca@valencia]=Un connector per a canviar l'espai de color dels documents seleccionats Comment[cs]=Modul pro změnu rozsahu barvy pro vybrané dokumenty Comment[el]=Πρόσθετο αλλαγής χρωματικού χώρου σε επιλεγμένα έγγραφα Comment[en_GB]=Plugin to change colour space to selected documents Comment[es]=Complemento para cambiar el espacio de color de los documentos seleccionados +Comment[et]=Plugin valitud dokumentide värviruumi muutmiseks Comment[eu]=Hautatutako dokumentuei kolore-espazioa aldatzeko plugina Comment[fi]=Liitännäinen valittujen tiedostojen väriavaruuden muuttamiseksi Comment[fr]=Module externe pour l'espace de couleurs des documents sélectionnés Comment[gl]=Complemento para cambiar o espazo de cores dos documentos seleccionados. Comment[it]=Estensione per cambiare lo spazio dei colori ai documenti selezionati Comment[ko]=선택한 문서로 색상 공간을 변경하는 플러그인 Comment[nl]=Plug-in om kleurruimte in geselecteerde documenten te wijzigen Comment[nn]=Programtillegg for å byta fargerom på utvalde dokument Comment[pl]=Wtyczka do zmiany przestrzeni barw wybranych dokumentów Comment[pt]='Plugin' para mudar o espaço de cores do documento seleccionado Comment[pt_BR]=Plugin para alterar o espaço de cores em documentos selecionados Comment[sv]=Insticksprogram för att ändra färgrymd för valda dokument Comment[tr]=Seçili belgede renk aralığını değiştirmek için eklenti Comment[uk]=Додаток для зміни простору кольорів у позначених документах Comment[x-test]=xxPlugin to change color space to selected documentsxx Comment[zh_CN]=用于更改选定文档色彩空间的插件 Comment[zh_TW]=用於變更色彩空間為選定檔案的外掛程式 diff --git a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop index f72ea33d5a..4184aee956 100644 --- a/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop +++ b/plugins/python/comics_project_management_tools/kritapykrita_comics_project_management_tools.desktop @@ -1,58 +1,60 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=comics_project_management_tools X-Krita-Manual=README.html X-Python-2-Compatible=false Name=Comics Project Management Tools Name[ar]=أدوات إدارة المشاريع الهزليّة Name[ca]=Eines per a la gestió dels projectes de còmics Name[ca@valencia]=Eines per a la gestió dels projectes de còmics Name[cs]=Nástroje pro správu projektů komixů Name[el]=Εργαλεία διαχείρισης έργων ιστοριών σε εικόνες Name[en_GB]=Comics Project Management Tools Name[es]=Herramientas de gestión de proyectos de cómics +Name[et]=Koomiksite projektihalduse tööriistad Name[eu]=Komikien proiektuak kudeatzeko tresnak Name[fi]=Sarjakuvaprojektien hallintatyökalut Name[fr]=Outils de gestion d'un projet de bande dessinée Name[gl]=Ferramentas de xestión de proxectos de cómics Name[is]=Verkefnisstjórn teiknimyndasögu Name[it]=Strumenti per la gestione dei progetti di fumetti Name[ko]=만화 프로젝트 관리 도구 Name[nl]=Hulpmiddelen voor projectbeheer van strips Name[nn]=Prosjekthandsaming for teikneseriar Name[pl]=Narzędzia do zarządzania projektami komiksów Name[pt]=Ferramentas de Gestão de Projectos de Banda Desenhada Name[pt_BR]=Ferramentas de gerenciamento de projeto de quadrinhos Name[sv]=Projekthanteringsverktyg för tecknade serier Name[tr]=Çizgi Roman Projesi Yönetimi Araçları Name[uk]=Інструменти для керування проєктами коміксів Name[x-test]=xxComics Project Management Toolsxx Name[zh_CN]=漫画项目管理工具 Name[zh_TW]=漫畫專案管理工具 Comment=Tools for managing comics. Comment[ar]=أدوات لإدارة الهزليّات. Comment[ca]=Eines per a gestionar els còmics. Comment[ca@valencia]=Eines per a gestionar els còmics. Comment[cs]=Nástroje pro správu komixů. Comment[el]=Εργαλεία για τη διαχείριση ιστοριών σε εικόνες. Comment[en_GB]=Tools for managing comics. Comment[es]=Herramientas para gestionar cómics. +Comment[et]=Koomiksite haldamise tööriistad. Comment[eu]=Komikiak kudeatzeko tresnak. Comment[fi]=Sarjakuvien hallintatyökalut. Comment[fr]=Outils pour gérer les bandes dessinées. Comment[gl]=Ferramentas para xestionar cómics. Comment[is]=Verkfæri til að stýra gerð teiknimyndasögu. Comment[it]=Strumenti per la gestione dei fumetti. Comment[ko]=만화 관리 도구입니다. Comment[nl]=Hulpmiddelen voor beheer van strips. Comment[nn]=Verktøy for handsaming av teikneseriar Comment[pl]=Narzędzie do zarządzania komiksami. Comment[pt]=Ferramentas para gerir bandas desenhadas. Comment[pt_BR]=Ferramentas para gerenciar quadrinhos. Comment[sv]=Verktyg för att hantera tecknade serier. Comment[tr]=Çizgi romanları yönetmek için araçlar. Comment[uk]=Інструменти для керування коміксами Comment[x-test]=xxTools for managing comics.xx Comment[zh_CN]=用于管理漫画的工具。 Comment[zh_TW]=管理漫畫的工具。 diff --git a/plugins/python/documenttools/kritapykrita_documenttools.desktop b/plugins/python/documenttools/kritapykrita_documenttools.desktop index c34c388ee2..650d03a0f3 100644 --- a/plugins/python/documenttools/kritapykrita_documenttools.desktop +++ b/plugins/python/documenttools/kritapykrita_documenttools.desktop @@ -1,56 +1,58 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=documenttools X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Document Tools Name[ar]=أدوات المستندات Name[ca]=Eines de document Name[ca@valencia]=Eines de document Name[cs]=Dokumentové nástroje Name[el]=Εργαλεία για έγγραφα Name[en_GB]=Document Tools Name[es]=Herramientas de documentos +Name[et]=Dokumenditööriistad Name[eu]=Dokumentuen tresnak Name[fi]=Tiedostotyökalut Name[fr]=Outil Document Name[gl]=Ferramentas de documentos Name[it]=Strumenti per i documenti Name[ko]=문서 도구 Name[nl]=Documenthulpmiddelen Name[nn]=Dokumentverktøy Name[pl]=Narzędzia dokumentu Name[pt]=Ferramentas de Documentos Name[pt_BR]=Ferramentas de documento Name[sv]=Dokumentverktyg Name[tr]=Belge Araçları Name[uk]=Засоби документа Name[x-test]=xxDocument Toolsxx Name[zh_CN]=文档工具 Name[zh_TW]=檔案工具 Comment=Plugin to manipulate properties of selected documents Comment[ar]=ملحقة لتعديل خصائص المستندات المحددة Comment[ca]=Un connector per a manipular les propietats dels documents seleccionats Comment[ca@valencia]=Un connector per a manipular les propietats dels documents seleccionats Comment[cs]=Modul pro správu vlastností vybraných dokumentů Comment[el]=Πρόσθετο χειρισμού ιδιοτήτων σε επιλεγμένα έγγραφα Comment[en_GB]=Plugin to manipulate properties of selected documents Comment[es]=Complemento para manipular las propiedades de los documentos seleccionados +Comment[et]=Plugin valitud dokumentide omaduste käitlemiseks Comment[eu]=Hautatutako dokumentuen propietateak manipulatzeko plugina Comment[fi]=Liitännäinen valittujen tiedostojen ominaisuuksien käsittelemiseksi Comment[fr]=Module externe de gestion des propriétés des documents sélectionnés Comment[gl]=Complemento para manipular as propiedades dos documentos seleccionados. Comment[it]=Estensione per manipolare le proprietà dei documenti selezionati Comment[ko]=선택한 문서의 속성을 변경하는 플러그인 Comment[nl]=Plug-in om eigenschappen van geselecteerde documenten te manipuleren Comment[nn]=Programtillegg for å endra eigenskapar på utvalde dokument Comment[pl]=Wtyczka do zmiany właściwości wybranych dokumentów Comment[pt]='Plugin' para manipular as propriedades dos documentos seleccionados Comment[pt_BR]=Plugin para manipular as propriedades de documentos selecionados Comment[sv]=Insticksprogram för att ändra egenskaper för valda dokument Comment[tr]=Seçili belgelerin özelliklerini değiştirmek için eklenti Comment[uk]=Додаток для керування властивостями позначених документів Comment[x-test]=xxPlugin to manipulate properties of selected documentsxx Comment[zh_CN]=用于编辑选定文档属性的插件 Comment[zh_TW]=用於修改所選檔案屬性的外掛程式 diff --git a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop index 2469aeb41d..63e1511745 100644 --- a/plugins/python/exportlayers/kritapykrita_exportlayers.desktop +++ b/plugins/python/exportlayers/kritapykrita_exportlayers.desktop @@ -1,59 +1,61 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=exportlayers X-Krita-Manual=Manual.html X-Python-2-Compatible=true Name=Export Layers Name[ar]=تصدير الطبقات Name[ca]=Exportació de capes Name[ca@valencia]=Exportació de capes Name[cs]=Exportovat vrstvy Name[de]=Ebenen exportieren Name[el]=Εξαγωγή επιπέδων Name[en_GB]=Export Layers Name[es]=Exportar capas +Name[et]=Kihtide eksport Name[eu]=Esportatu geruzak Name[fi]=Vie tasoja Name[fr]=Exporter des calques Name[gl]=Exportar as capas Name[is]=Flytja út lög Name[it]=Esporta livelli Name[ko]=레이어 내보내기 Name[nl]=Lagen exporteren Name[nn]=Eksporter lag Name[pl]=Eksportuj warstwy Name[pt]=Exportar as Camadas Name[pt_BR]=Exportar camadas Name[sv]=Exportera lager Name[tr]=Katmanları Dışa Aktar Name[uk]=Експортувати шари Name[x-test]=xxExport Layersxx Name[zh_CN]=导出图层 Name[zh_TW]=匯出圖層 Comment=Plugin to export layers from a document Comment[ar]=ملحقة لتصدير الطبقات من مستند Comment[ca]=Un connector per exportar capes d'un document Comment[ca@valencia]=Un connector per exportar capes d'un document Comment[cs]=Modul pro export vrstev z dokumentu Comment[de]=Modul zum Exportieren von Ebenen aus einem Dokument Comment[el]=Πρόσθετο εξαγωγής επιπέδων από έγγραφο Comment[en_GB]=Plugin to export layers from a document Comment[es]=Complemento para exportar las capas de un documento +Comment[et]=Plugin dokumendi kihtide eksportimiseks Comment[eu]=Dokumentu batetik geruzak esportatzeko plugina Comment[fi]=Liitännäisen tiedoston tasojen viemiseksi Comment[fr]=Module externe d'export de calques d'un document Comment[gl]=Complemento para exportar as capas dun documento. Comment[it]=Estensione per esportare i livelli da un documento Comment[ko]=문서에서 레이어를 내보내는 플러그인 Comment[nl]=Plug-in om lagen uit een document te exporteren Comment[nn]=Programtillegg for eksportering av biletlag i dokument Comment[pl]=Wtyczka do eksportowania warstw z dokumentu Comment[pt]='Plugin' para exportar as camadas de um documento Comment[pt_BR]=Plugin para exportar as camadas de um documento Comment[sv]=Insticksprogram för att exportera lager från ett dokument Comment[tr]=Belgenin katmanlarını dışa aktarmak için eklenti Comment[uk]=Додаток для експортування шарів з документа Comment[x-test]=xxPlugin to export layers from a documentxx Comment[zh_CN]=用于从文档导出图层的插件 Comment[zh_TW]=用於從檔案匯出圖層的外掛程式 diff --git a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop index 41a19bb39f..3be984b34b 100644 --- a/plugins/python/filtermanager/kritapykrita_filtermanager.desktop +++ b/plugins/python/filtermanager/kritapykrita_filtermanager.desktop @@ -1,58 +1,60 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=filtermanager X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Filter Manager Name[ar]=مدير المرشّحات Name[ca]=Gestor de filtres Name[ca@valencia]=Gestor de filtres Name[cs]=Správce filtrů Name[de]=Filterverwaltung Name[el]=Διαχειριστής φίλτρων Name[en_GB]=Filter Manager Name[es]=Gestor de filtros +Name[et]=Filtrihaldur Name[eu]=Iragazki-kudeatzailea Name[fi]=Suodatinhallinta Name[fr]=Gestionnaire de fichiers Name[gl]=Xestor de filtros Name[it]=Gestore dei filtri Name[ko]=필터 관리자 Name[nl]=Beheerder van filters Name[nn]=Filterhandsamar Name[pl]=Zarządzanie filtrami Name[pt]=Gestor de Filtros Name[pt_BR]=Gerenciador de filtros Name[sv]=Filterhantering Name[tr]=Süzgeç Yöneticisi Name[uk]=Керування фільтрами Name[x-test]=xxFilter Managerxx Name[zh_CN]=滤镜管理器 Name[zh_TW]=濾鏡管理器 Comment=Plugin to filters management Comment[ar]=ملحقة إدارة المرشّحات Comment[ca]=Un connector per a gestionar filtres Comment[ca@valencia]=Un connector per a gestionar filtres Comment[cs]=Modul pro správu filtrů Comment[de]=Modul zum Verwalten von Filtern Comment[el]=Πρόσθετο για τη διαχείριση φίλτρων Comment[en_GB]=Plugin to filters management Comment[es]=Complemento para la gestión de filtros +Comment[et]=Plugin filtrite haldamiseks Comment[eu]=Iragazkiak kudeatzeko plugina Comment[fi]=Liitännäinen suodatinten hallintaan Comment[fr]=Module externe de gestion des filtres Comment[gl]=Complemento para a xestión de filtros. Comment[it]=Estensione per la gestione dei filtri Comment[ko]=필터 관리에 대한 플러그인 Comment[nl]=Plug-in voor beheer van filters Comment[nn]=Programtillegg for filterhandsaming Comment[pl]=Wtyczka do zarządzania filtrami Comment[pt]='Plugin' para a gestão de filtros Comment[pt_BR]=Plugin para gerenciamento de filtros Comment[sv]=Insticksprogram för filterhantering Comment[tr]=Süzgeç yönetimi için eklenti Comment[uk]=Додаток для керування фільтрами Comment[x-test]=xxPlugin to filters managementxx Comment[zh_CN]=用于管理滤镜的插件。 Comment[zh_TW]=用於濾鏡管理的外掛程式 diff --git a/plugins/python/hello/kritapykrita_hello.desktop b/plugins/python/hello/kritapykrita_hello.desktop index 1ec86ba1df..c4a0c7ceae 100644 --- a/plugins/python/hello/kritapykrita_hello.desktop +++ b/plugins/python/hello/kritapykrita_hello.desktop @@ -1,61 +1,63 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=hello X-Krita-Manual=Manual.html X-Python-2-Compatible=true Name=Hello World Name[ar]=مرحبا يا عالم Name[ca]=Hola món Name[ca@valencia]=Hola món Name[cs]=Hello World Name[de]=Hallo Welt Name[el]=Hello World Name[en_GB]=Hello World Name[es]=Hola mundo +Name[et]=Tere, maailm Name[eu]=Kaixo mundua Name[fi]=Hei maailma Name[fr]=Bonjour tout le monde Name[gl]=Ola mundo Name[is]=Halló Heimur Name[it]=Ciao mondo Name[ko]=전 세계 여러분 안녕하세요 Name[nl]=Hallo wereld Name[nn]=Hei, verda Name[pl]=Witaj świecie Name[pt]=Olá Mundo Name[pt_BR]=Olá mundo Name[sk]=Ahoj svet Name[sv]=Hello World Name[tg]=Салом ҷаҳон Name[tr]=Merhaba Dünya Name[uk]=Привіт, світе Name[x-test]=xxHello Worldxx Name[zh_CN]=Hello World Name[zh_TW]=你好,世界 Comment=Basic plugin to test PyKrita Comment[ar]=ملحقة أساسية لاختبار PyKrita Comment[ca]=Connector bàsic per a provar el PyKrita Comment[ca@valencia]=Connector bàsic per a provar el PyKrita Comment[cs]=Základní modul pro testování PyKrita Comment[de]=Basismodul zum Testen von PyKrita Comment[el]=Βασικό πρόσθετο δοκιμής PyKrita Comment[en_GB]=Basic plugin to test PyKrita Comment[es]=Complemento básico para probar PyKrita +Comment[et]=Baasplugin PyKrita testimiseks Comment[eu]=PyKrita probatzeko plugina Comment[fi]=Perusliitännäinen PyKritan kokeilemiseksi Comment[fr]=Module externe élémentaire pour tester PyKrita Comment[gl]=Complemento básico para probar PyKrita. Comment[it]=Estensione di base per provare PyKrita Comment[ko]=PyKrita 테스트용 기본 플러그인 Comment[nl]=Basisplug-in om PyKrita te testen Comment[nn]=Enkelt programtillegg for testing av PyKrita Comment[pl]=Podstawowa wtyczka do wypróbowania PyKrity Comment[pt]='Plugin' básico para testar o PyKrita Comment[pt_BR]=Plugin básico para testar o PyKrita Comment[sv]=Enkelt insticksprogram för att utprova PyKrita Comment[tr]=PyKrita'yı test etmek için temel eklenti Comment[uk]=Базовий додаток для тестування PyKrita Comment[x-test]=xxBasic plugin to test PyKritaxx Comment[zh_CN]=用于测试 PyKrita 的简易插件 Comment[zh_TW]=測試 PyKrita 的基本外掛程式 diff --git a/plugins/python/highpass/kritapykrita_highpass.desktop b/plugins/python/highpass/kritapykrita_highpass.desktop index e40f7724c3..bb6471a1cc 100644 --- a/plugins/python/highpass/kritapykrita_highpass.desktop +++ b/plugins/python/highpass/kritapykrita_highpass.desktop @@ -1,53 +1,55 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=highpass X-Python-2-Compatible=false Name=Highpass Filter Name[ca]=Filtre passaalt Name[ca@valencia]=Filtre passaalt Name[cs]=Filtr s horní propustí Name[de]=Hochpassfilter Name[el]=Υψιπερατό φίλτρο Name[en_GB]=Highpass Filter Name[es]=Filtro paso alto +Name[et]=Kõrgpääsfilter Name[eu]=Goi-igaropeneko iragazkia Name[fr]=Filtre passe-haut Name[gl]=Filtro de paso alto Name[it]=Filtro di accentuazione passaggio Name[ko]=하이패스 필터 Name[nl]=Hoogdoorlaatfilter Name[nn]=Høgpass-filter Name[pl]=Filtr górnoprzepustowy Name[pt]=Filtro Passa-Alto Name[pt_BR]=Filtro passa alta Name[sv]=Högpassfilter Name[tr]=Yüksek Geçirgen Süzgeç Name[uk]=Високочастотний фільтр Name[x-test]=xxHighpass Filterxx Name[zh_CN]=高通滤镜 Name[zh_TW]=高通濾鏡 Comment=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[ca]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385 Comment[ca@valencia]=Filtre passaalt, basat en el http://registry.gimp.org/node/7385 Comment[cs]=Filtr s horní propustí založený na http://registry.gimp.org/node/7385 Comment[de]=Hochpassfilter, Grundlage ist http://registry.gimp.org/node/7385 Comment[el]=Υψιπερατό φίλτρο, με βάση το http://registry.gimp.org/node/7385 Comment[en_GB]=Highpass Filter, based on http://registry.gimp.org/node/7385 Comment[es]=Filtro paso alto, basado en http://registry.gimp.org/node/7385 +Comment[et]=Kõrgpääsfilter, mille aluseks on http://registry.gimp.org/node/7385 Comment[eu]=Goi-igaropeneko iragazkia, honetan oinarritua http://registry.gimp.org/node/7385 Comment[fr]=Filtre passe-haut, fondé sur http://registry.gimp.org/node/7385 Comment[gl]=Filtro de paso alto, baseado en http://registry.gimp.org/node/7385. Comment[it]=Filtro di accentuazione passaggio, basato su http://registry.gimp.org/node/7385 Comment[ko]=http://registry.gimp.org/node/7385에 기반한 하이패스 필터 Comment[nl]=Hoogdoorlaatfilter, gebaseerd op http://registry.gimp.org/node/7385 Comment[nn]=Høgpass-filter, basert på http://registry.gimp.org/node/7385 Comment[pl]=Filtr górnoprzepustowy, oparty na http://registry.gimp.org/node/7385 Comment[pt]=Filtro passa-alto, baseado em http://registry.gimp.org/node/7385 Comment[pt_BR]=Filtro passa alta, baseado em http://registry.gimp.org/node/7385 Comment[sv]=Högpassfilter, baserat på http://registry.gimp.org/node/7385 Comment[tr]=http://registry.gimp.org/node/7385 tabanlı Yüksek Geçirgen Süzgeç Comment[uk]=Високочастотний фільтр, засновано на on http://registry.gimp.org/node/7385 Comment[x-test]=xxHighpass Filter, based on http://registry.gimp.org/node/7385xx Comment[zh_CN]=高通滤镜,基于 http://registry.gimp.org/node/7385 Comment[zh_TW]=高通濾鏡,基於 http://registry.gimp.org/node/7385 diff --git a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop index 6a13606e33..aeddfef036 100644 --- a/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop +++ b/plugins/python/krita_script_starter/kritapykrita_krita_script_starter.desktop @@ -1,45 +1,47 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=krita_script_starter X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Krita Script Starter Name[ca]=Iniciador de scripts del Krita Name[ca@valencia]=Iniciador de scripts del Krita Name[cs]=Spouštěč skriptů Krita Name[en_GB]=Krita Script Starter Name[es]=Iniciador de guiones de Krita +Name[et]=Krita skriptialustaja Name[eu]=Krita-ren script abiarazlea Name[gl]=Iniciador de scripts de Krita Name[it]=Iniziatore di script per Krita Name[ko]=Krita 스크립트 시작 도구 Name[nl]=Script-starter van Krita Name[nn]=Krita skriptbyggjar Name[pl]=Starter skryptów Krity Name[pt]=Inicialização do Programa do Krita Name[sv]=Krita skriptstart Name[tr]=Krita Betik Başlatıcı Name[uk]=Створення скрипту Krita Name[x-test]=xxKrita Script Starterxx Name[zh_CN]=Krita 空脚本生成器 Name[zh_TW]=Krita 指令啟動器 Comment=Create the metadata and file structure for a new Krita script Comment[ca]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[ca@valencia]=Crea les metadades i l'estructura de fitxers d'un script nou del Krita Comment[en_GB]=Create the metadata and file structure for a new Krita script Comment[es]=Crear los metadatos y la estructura de archivos para un nuevo guion de Krita +Comment[et]=Uue Krita skripti metaandmete ja failistruktuuri loomine Comment[eu]=Sortu Krita-script berri baterako meta-datuak eta fitxategi egitura Comment[gl]=Crear os metadatos e a estrutura de ficheiros para un novo script de Krita. Comment[it]=Crea i metadati e la struttura dei file per un nuovo script di Krita Comment[ko]=새 Krita 스크립트에 대한 메타데이터 및 파일 구조 생성 Comment[nl]=Maak de metagegevens en bestandsstructuur voor een nieuw Krita-script Comment[nn]=Generer metadata og filstruktur for nye Krita-skript Comment[pl]=Utwórz metadane i strukturę plików dla nowego skryptu Krity Comment[pt]=Cria os meta-dados e a estrutura de ficheiros para um novo programa do Krita Comment[sv]=Skapa metadata och filstruktur för ett nytt Krita-skript Comment[tr]=Yeni Krita betiği için üstveri ve dosya yapısı oluştur Comment[uk]=Створення метаданих і структури файлів для нового скрипту Krita Comment[x-test]=xxCreate the metadata and file structure for a new Krita scriptxx Comment[zh_CN]=为新的 Krita 脚本创建元数据及文件结构 Comment[zh_TW]=為新的 Krita 指令稿建立中繼資料和檔案建構體 diff --git a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop index 750d85fd0b..4821911758 100644 --- a/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop +++ b/plugins/python/lastdocumentsdocker/kritapykrita_lastdocumentsdocker.desktop @@ -1,52 +1,54 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=lastdocumentsdocker X-Python-2-Compatible=false X-Krita-Manual=Manual.html Name=Last Documents Docker Name[ar]=رصيف بآخر المستندات Name[ca]=Acoblador Darrers documents Name[ca@valencia]=Acoblador Darrers documents Name[el]=Προσάρτηση τελευταίων εγγράφοων Name[en_GB]=Last Documents Docker Name[es]=Panel de últimos documentos +Name[et]=Viimaste dokumentide dokk Name[eu]=Azken dokumentuen panela Name[fi]=Viimeisimpien tiedostojen telakka Name[fr]=Récemment ouverts Name[gl]=Doca dos últimos documentos Name[it]=Area di aggancio Ultimi documenti Name[ko]=마지막 문서 도킹 패널 Name[nl]=Laatste documenten verankering Name[nn]=Dokk for nyleg opna dokument Name[pl]=Dok ostatnich dokumentów Name[pt]=Área dos Últimos Documentos Name[sv]=Dockningsfönster för senaste dokument Name[tr]=Son Belgeler Doku Name[uk]=Бічна панель останніх документів Name[x-test]=xxLast Documents Dockerxx Name[zh_CN]=最近文档工具面板 Name[zh_TW]=「最後檔案」面板 Comment=A Python-based docker for show thumbnails to last ten documents Comment[ar]=رصيف بِ‍«پيثون» لعرض مصغّرات آخر ١٠ مستندات مفتوحة Comment[ca]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents Comment[ca@valencia]=Un acoblador basat en Python per a mostrar miniatures dels darrers deu documents Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την εμφάνιση επισκοπήσεων των δέκα τελευταίων εγγράφων Comment[en_GB]=A Python-based docker for show thumbnails to last ten documents Comment[es]=Un panel basado en Python para mostrar miniaturas de los últimos diez documentos +Comment[et]=Pythoni-põhine dokk viimase kümne dokumendi pisipildi näitamiseks Comment[eu]=Azken hamar dokumentuen koadro-txikiak erakusteko Python-oinarridun panel bat Comment[fi]=Python-pohjainen telakka viimeisimpien kymmenen tiedoston pienoiskuvien näyttämiseen Comment[fr]=Panneau en Python pour afficher les vignettes des dix derniers documents Comment[gl]=Unha doca baseada en Python para mostrar as miniaturas dos últimos dez documentos. Comment[it]=Un'area di aggancio basata su Python per mostrare miniature degli ultimi dieci documenti. Comment[ko]=10개의 문서를 표시할 수 있는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter om miniaturen te tonen naar de laatste tien documenten. Comment[nn]=Python-basert dokk for vising av miniatyrbilete av dei siste ti dokumenta Comment[pl]=Dok oparty na pythonie do wyświetlania miniatur ostatnich dziesięciu dokumentów Comment[pt]=Uma área acoplável, feita em Python, para mostrar as miniaturas dos últimos dez documentos Comment[sv]=Ett Python-baserat dockningsfönster för att visa miniatyrbilder för de tio senaste dokumenten Comment[tr]=Son on belgenin küçük resmini göstermek için Python-tabanlı bir dok Comment[uk]=Бічна панель на основі Python для показу мініатюр останніх десяти документів Comment[x-test]=xxA Python-based docker for show thumbnails to last ten documentsxx Comment[zh_CN]=这是一个基于 Python 的工具面板插件,它可以显示最近十个文档的缩略图 Comment[zh_TW]=基於 Python 的面板,用於顯示最後 10 個檔案縮圖 diff --git a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop index 2603892208..4a6cc34a54 100644 --- a/plugins/python/palette_docker/kritapykrita_palette_docker.desktop +++ b/plugins/python/palette_docker/kritapykrita_palette_docker.desktop @@ -1,55 +1,57 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=palette_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Palette docker Name[ar]=رصيف اللوحات Name[ca]=Acoblador Paleta Name[ca@valencia]=Acoblador Paleta Name[cs]=Dok palet Name[de]=Paletten-Docker Name[el]=Προσάρτηση παλέτας Name[en_GB]=Palette docker Name[es]=Panel de paleta +Name[et]=Paletidokk Name[eu]=Paleta-panela Name[fi]=Palettitelakka Name[fr]=Panneau de palette Name[gl]=Doca de paleta Name[is]=Tengikví fyrir litaspjald Name[it]=Area di aggancio della tavolozza Name[ko]=팔레트 도킹 패널 Name[nl]=Vastzetter van palet Name[nn]=Palettdokk Name[pl]=Dok palety Name[pt]=Área acoplável da paleta Name[sv]=Dockningsfönster för palett Name[tr]=Palet doku Name[uk]=Панель палітри Name[x-test]=xxPalette dockerxx Name[zh_CN]=调色板工具面板 Name[zh_TW]=「調色盤」面板 Comment=A Python-based docker to edit color palettes. Comment[ar]=رصيف بِ‍«پيثون» لتحرير لوحات الألوان. Comment[ca]=Un acoblador basat en Python per editar paletes de colors. Comment[ca@valencia]=Un acoblador basat en Python per editar paletes de colors. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για την επεξεργασία παλετών χρώματος. Comment[en_GB]=A Python-based docker to edit colour palettes. Comment[es]=Un panel basado en Python para editar paletas de colores. +Comment[et]=Pythoni-põhine dokk värvipalettide muutmiseks. Comment[eu]=Kolore-paletak editatzeko Python-oinarridun paleta bat. Comment[fi]=Python-pohjainen telakka väripalettien muokkaamiseen. Comment[fr]=Panneau en Python pour éditer les palettes de couleurs. Comment[gl]=Unha doca baseada en Python para editar paletas de cores. Comment[it]=Un'area di aggancio per modificare le tavolozze di colori basata su Python. Comment[ko]=색상 팔레트를 편집할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde vastzetter om kleurpaletten te bewerken. Comment[nn]=Python-basert dokk for redigering av fargepalettar Comment[pl]=Dok oparty na pythonie do edytowania palet barw. Comment[pt]=Uma área acoplável, feita em Python, para editar paletas de cores. Comment[sv]=Ett Python-baserat dockningsfönster för att redigera färgpaletter. Comment[tr]=Renk paletlerini düzenlemek için Python-tabanlı bir dok. Comment[uk]=Бічна панель для редагування палітр кольорів на основі Python. Comment[x-test]=xxA Python-based docker to edit color palettes.xx Comment[zh_CN]=基于 Python 的调色板编辑器工具面板 Comment[zh_TW]=基於 Python 的面板,用於編輯調色盤。 diff --git a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop index 6dc82d0dda..845ed5c50b 100644 --- a/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop +++ b/plugins/python/plugin_importer/kritapykrita_plugin_importer.desktop @@ -1,44 +1,46 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=plugin_importer X-Python-2-Compatible=false X-Krita-Manual=manual.html Name=Python Plugin Importer Name[ca]=Importador de connectors en Python Name[ca@valencia]=Importador de connectors en Python Name[cs]=Importér modulů pro Python Name[en_GB]=Python Plugin Importer Name[es]=Importador de complementos de Python +Name[et]=Pythoni plugina importija Name[gl]=Importador de complementos de Python Name[it]=Importatore estensioni Python Name[ko]=Python 플러그인 가져오기 도구 Name[nl]=Importeur van Plugin voor Python Name[nn]=Importering av Python-tillegg Name[pl]=Import wtyczek Pythona Name[pt]=Importador de 'Plugins' do Python Name[sv]=Python-insticksimport Name[tr]=Python Eklenti İçe Aktarıcı Name[uk]=Засіб імпортування додатків Python Name[x-test]=xxPython Plugin Importerxx Name[zh_CN]=Python 插件导入器 Name[zh_TW]=Python 外掛程式匯入工具 Comment=Imports Python plugins from zip files. Comment[ca]=Importa connectors en Python a partir de fitxers «zip». Comment[ca@valencia]=Importa connectors en Python a partir de fitxers «zip». Comment[cs]=Importuje moduly Pythonu ze souborů zip. Comment[en_GB]=Imports Python plugins from zip files. Comment[es]=Importa complementos de Python desde archivos zip. +Comment[et]=Pythoni pluginate import zip-failidest. Comment[gl]=Importa complementos de Python de ficheiros zip. Comment[it]=Importa le estensioni Python dai file compressi. Comment[ko]=ZIP 파일에서 Python 플러그인을 가져옵니다. Comment[nl]=Importeert Python-plug-ins uit zip-bestanden. Comment[nn]=Importerer Python-baserte programtillegg frå .zip-filer Comment[pl]=Importuj wtyczki Pythona z plików zip. Comment[pt]=Importa os 'plugins' em Python a partir de ficheiros Zip. Comment[sv]=Importerar Python-insticksprogram från zip-filer. Comment[tr]=Python eklentilerini zip dosyasından içe aktarır. Comment[uk]=Імпортує додатки Python з файлів zip. Comment[x-test]=xxImports Python plugins from zip files.xx Comment[zh_CN]=从 zip 文件导入 Python 插件。 Comment[zh_TW]=匯入 Zip 檔案中的 Python 外掛程式。 diff --git a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop index 86cce72c36..1602d4d0b7 100644 --- a/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop +++ b/plugins/python/quick_settings_docker/kritapykrita_quick_settings_docker.desktop @@ -1,53 +1,55 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=quick_settings_docker X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Quick Settings Docker Name[ar]=رصيف بإعدادات سريعة Name[ca]=Acoblador Arranjament ràpid Name[ca@valencia]=Acoblador Arranjament ràpid Name[cs]=Dok pro rychlé nastavení Name[el]=Προσάρτηση γρήγορων ρυθμίσεων Name[en_GB]=Quick Settings Docker Name[es]=Panel de ajustes rápidos +Name[et]=Kiirseadistuste dokk Name[eu]=Ezarpen azkarren panela Name[fi]=Pika-asetustelakka Name[fr]=Réglages rapides Name[gl]=Doca de configuración rápida Name[it]=Area di aggancio delle impostazioni rapide Name[ko]=빠른 설정 도킹 패널 Name[nl]=Verankering voor snelle instellingen Name[nn]=Snøgginnstillingar-dokk Name[pl]=Dok szybkich ustawień Name[pt]=Área de Configuração Rápida Name[sv]=Dockningspanel med snabbinställningar Name[tr]=Hızlı Ayarlar Doku Name[uk]=Панель швидких параметрів Name[x-test]=xxQuick Settings Dockerxx Name[zh_CN]=快速设置工具面板 Name[zh_TW]=「快速設定」面板 Comment=A Python-based docker for quickly changing brush size and opacity. Comment[ar]=رصيف بِ‍«پيثون» لتغيير حجم الفرشاة وشفافيّتها بسرعة. Comment[ca]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[ca@valencia]=Un acoblador basat en Python per a canviar ràpidament la mida i l'opacitat del pinzell. Comment[el]=Ένα εργαλείο προσάρτησης σε Python για γρήγορη αλλαγή του μεγέθους και της αδιαφάνειας του πινέλου. Comment[en_GB]=A Python-based docker for quickly changing brush size and opacity. Comment[es]=Un panel basado en Python para cambiar rápidamente el tamaño y la opacidad del pincel. +Comment[et]=Pythoni-põhine dokk pintsli suuruse ja läbipaistmatuse kiireks muutmiseks. Comment[eu]=Isipu-neurria eta -opakotasuna aldatzeko Python-oinarridun panel bat. Comment[fi]=Python-pohjainen telakka siveltimen koon ja läpikuultavuuden nopean muuttamiseen. Comment[fr]=Panneau en python pour modifier rapidement la taille et l'opacité des brosses. Comment[gl]=Unha doca baseada en Python para cambiar rapidamente a opacidade e o tamaño dos pinceis. Comment[it]=Un'area di aggancio basata su Python per cambiare rapidamente la dimensione del pennello e l'opacità. Comment[ko]=브러시 크기와 불투명도를 빠르게 변경할 수 있는 Python 기반 도킹 패널입니다. Comment[nl]=Een op Python gebaseerde docker voor snel wijzigen van penseelgrootte en dekking. Comment[nn]=Python-basert dokk for kjapp endring av penselstorleik/-tettleik Comment[pl]=Dok oparty na Pythonie do szybkiej zmiany rozmiaru i nieprzezroczystości pędzla. Comment[pt]=Uma área acoplável em Python para mudar rapidamente o tamanho e opacidade do pincel. Comment[sv]=En Python-baserad dockningspanel för att snabbt ändra penselstorlek och ogenomskinlighet. Comment[tr]=Fırça boyutunu ve matlığını hızlıca değiştirmek için Python-tabanlı bir dok. Comment[uk]=Панель на основі мови програмування Python для швидкої зміни розміру та непрозорості пензля. Comment[x-test]=xxA Python-based docker for quickly changing brush size and opacity.xx Comment[zh_CN]=这是一个基于 Python 的工具面板,用于快速更改笔刷尺寸和透明度。 Comment[zh_TW]=基於 Python 的面板,用於快速變更筆刷尺寸和不透明度。 diff --git a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop index b37503456d..496b6a400b 100644 --- a/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop +++ b/plugins/python/scriptdocker/kritapykrita_scriptdocker.desktop @@ -1,50 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scriptdocker X-Python-2-Compatible=false Name=Script Docker Name[ar]=رصيف سكربتات Name[ca]=Acoblador Script Name[ca@valencia]=Acoblador Script Name[cs]=Dok skriptu Name[el]=Προσάρτηση σεναρίων Name[en_GB]=Script Docker Name[es]=Panel de guiones +Name[et]=Skriptidokk Name[eu]=Script-panela Name[fi]=Skriptitelakka Name[fr]=Panneau de script Name[gl]=Doca de scripts Name[it]=Area di aggancio degli script Name[ko]=스크립트 도킹 패널 Name[nl]=Verankering van scripts Name[nn]=Skriptdokk Name[pl]=Dok skryptów Name[pt]=Área de Programas Name[sv]=Dockningsfönster för skript Name[tr]=Betik Doku Name[uk]=Бічна панель скриптів Name[x-test]=xxScript Dockerxx Name[zh_CN]=脚本工具面板 Name[zh_TW]=「指令稿」面板 Comment=A Python-based docker for create actions and point to Python scripts Comment[ar]=رصيف بِ‍«پيثون» لإنشاء الإجراءات والإشارة إلى سكربتات «پيثون» Comment[ca]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python Comment[ca@valencia]=Un acoblador basat en Python per a crear accions i apuntar a scripts en Python Comment[el]=Ένα εργαλείο προσάρτησης σε Python για τη δημιουργία ενεργειών και τη δεικτοδότηση σεναρίων σε Python Comment[en_GB]=A Python-based docker for create actions and point to Python scripts Comment[es]=Un panel basado en Python para crear y apuntar a guiones de Python +Comment[et]=Pythoni-põhine dokk toimingute loomiseks ja viitamiseks Pythoni skriptidele Comment[eu]=Python-oinarridun panel bat. Comment[gl]=Unha doca escrita en Python para crear accións e apuntar a scripts escritos en Python. Comment[it]=Un'area di aggancio basata su Python per creare azioni e scegliere script Python Comment[ko]=작업 생성 및 Python 스크립트를 가리키는 Python 기반 도킹 패널 Comment[nl]=Een op Python gebaseerde vastzetter voor aanmaakacties en wijzen naar Python-scripts Comment[nn]=Python-basert dokk for å laga handlingar og peika til Python-skript Comment[pl]=Dok oparty na pythonie do tworzenia działań i punktów dla skryptów pythona Comment[pt]=Uma área acoplável, feita em Python, para criar acções e apontar para programas em Python Comment[sv]=Ett Python-baserat dockningsfönster för att skapa åtgärder och peka ut Python-skript Comment[tr]=Eylemler oluşturmak ve Python betiklerine yönlendirmek için Python-tabanlı bir dok Comment[uk]=Бічна панель для створення дій і керування скриптами на основі Python. Comment[x-test]=xxA Python-based docker for create actions and point to Python scriptsxx Comment[zh_CN]=这是一个基于 Python 的工具面板,用于创建操作,并将他们指向 Python 脚本。 Comment[zh_TW]=基於 Python 的面板,用於建立動作並指向 Python 指令稿 diff --git a/plugins/python/scripter/kritapykrita_scripter.desktop b/plugins/python/scripter/kritapykrita_scripter.desktop index e79dde80b6..e648f51e56 100644 --- a/plugins/python/scripter/kritapykrita_scripter.desktop +++ b/plugins/python/scripter/kritapykrita_scripter.desktop @@ -1,49 +1,51 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=scripter X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Scripter Name[ca]=Scripter Name[ca@valencia]=Scripter Name[de]=Scripter Name[el]=Σενάρια Name[en_GB]=Scripter Name[es]=Guionador +Name[et]=Skriptija Name[eu]=Script egilea Name[fr]=Scripter Name[gl]=Executor de scripts Name[it]=Scripter Name[ko]=스크립트 도구 Name[nl]=Scriptmaker Name[nn]=Skriptkøyrer Name[pl]=Skrypter Name[pt]=Programador Name[sv]=Skriptgenerator Name[tr]=Betik yazarı Name[uk]=Скриптер Name[x-test]=xxScripterxx Name[zh_CN]=脚本工具 Name[zh_TW]=指令稿編寫者 Comment=Plugin to execute ad-hoc Python code Comment[ca]=Connector per executar codi «ad hoc» en Python Comment[ca@valencia]=Connector per executar codi «ad hoc» en Python Comment[el]=Πρόσθετο για την εκτέλεση συγκεκριμένου κώδικα Python Comment[en_GB]=Plugin to execute ad-hoc Python code Comment[es]=Complemento para ejecutar código Python a medida +Comment[et]=Plugin kohapeal loodud Pythoni koodi täitmiseks Comment[eu]=Berariaz egindako Python kodea exekutatzeko plugina Comment[fi]=Liitännäinen satunnaisen Python-koodin suorittamiseksi Comment[gl]=Complemento para executar código de Python escrito no momento. Comment[it]=Estensione per eseguire ad-hoc codice Python Comment[ko]=즉석에서 Python 코드를 실행하는 플러그인 Comment[nl]=Plug-in om ad-hoc Python code uit te voeren Comment[nn]=Programtillegg for køyring av ad hoc Python-kode Comment[pl]=Wtyczka do wykonywania kodu Pythona ad-hoc Comment[pt]='Plugin' para executar código em Python arbitrário Comment[sv]=Insticksprogram för att köra godtycklig Python-kod Comment[tr]=Geçici Python kodu çalıştırmak için eklenti Comment[uk]=Додаток для виконання апріорного коду Python Comment[x-test]=xxPlugin to execute ad-hoc Python codexx Comment[zh_CN]=用于执行当场编写的 Python 代码的插件 Comment[zh_TW]=外掛程式,用於執行特定 Python 程式碼 diff --git a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop index 1b463b43a6..aa23405c62 100644 --- a/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop +++ b/plugins/python/selectionsbagdocker/kritapykrita_selectionsbagdocker.desktop @@ -1,50 +1,52 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=selectionsbagdocker X-Python-2-Compatible=false Name=Selections Bag Docker Name[ca]=Acoblador Bossa de seleccions Name[ca@valencia]=Acoblador Bossa de seleccions Name[el]=Προσάρτηση σάκου επιλογών Name[en_GB]=Selections Bag Docker Name[es]=Panel de selecciones +Name[et]=Valikukarbi dokk Name[eu]=Hautapen-zakua panela Name[fr]=Outils de sélection Name[gl]=Doca de bolsa das seleccións Name[it]=Area di raccolta selezioni Name[ko]=선택 가방 도킹 패널 Name[nl]=Docker van zak met selecties Name[nn]=Utvalssamlingsdokk Name[pl]=Dok worka zaznaczeń Name[pt]=Área de Selecções Name[sv]=Dockningspanel med markeringspåse Name[tr]=Seçim Çantası Doku Name[uk]=Бічна панель позначеного Name[x-test]=xxSelections Bag Dockerxx Name[zh_CN]=选区列表工具面板 Name[zh_TW]=「選取範圍收藏」面板 Comment=A docker that allow to store a list of selections Comment[ar]=رصيف يتيح تخزين قائمة تحديدات Comment[ca]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[ca@valencia]=Un acoblador que permet emmagatzemar una llista de seleccions Comment[cs]=Dok umožňující uložit seznam výběrů Comment[el]=Ένα εργαλείο προσάρτησης που επιτρέπει την αποθήκευση μιας λίστας επιλογών Comment[en_GB]=A docker that allow to store a list of selections Comment[es]=Un panel que permite guardar una lista de selecciones +Comment[et]=Dokk valikute salvestamiseks Comment[eu]=Hautapen zerrenda bat biltegiratzen uzten duen panel bat Comment[fi]=Telakka, joka sallii tallentaa valintaluettelon Comment[fr]=Panneau permettant de conserver une liste de sélections Comment[gl]=Unha doca que permite almacenar unha lista de seleccións. Comment[it]=Un'area di aggancio che consente di memorizzare un elenco di selezioni Comment[ko]=선택 목록을 저장할 수 있는 도킹 패널 Comment[nl]=Een docker die een lijst met selecties kan opslaan Comment[nn]=Dokk for lagring av ei liste med utval Comment[pl]=Dok, który umożliwia przechowywanie listy zaznaczeń Comment[pt]=Uma área acoplável que permite guardar uma lista de selecções Comment[sv]=En dockningspanel som gör det möjligt att lagra en lista över markeringar Comment[tr]=Seçimlerin bir listesini saklamayı sağlayan bir dok Comment[uk]=Бічна панель, на якій можна зберігати список позначеного Comment[x-test]=xxA docker that allow to store a list of selectionsxx Comment[zh_CN]=用于保存一组选区的工具面板 Comment[zh_TW]=允許儲存選取範圍列表的面板 diff --git a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop index bb8dd0a6bd..e638386384 100644 --- a/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop +++ b/plugins/python/tenbrushes/kritapykrita_tenbrushes.desktop @@ -1,52 +1,54 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenbrushes X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Brushes Name[ar]=عشرُ فُرش Name[ca]=Deu pinzells Name[ca@valencia]=Deu pinzells Name[cs]=Deset štětců Name[el]=Δέκα πινέλα Name[en_GB]=Ten Brushes Name[es]=Diez pinceles +Name[et]=Kümme pintslit Name[eu]=Hamar isipu Name[fi]=Kymmenen sivellintä Name[fr]=Raccourcis des préréglages de brosses Name[gl]=Dez pinceis Name[is]=Tíu penslar Name[it]=Dieci pennelli Name[ko]=10개의 브러시 Name[nl]=Tien penselen Name[nn]=Ti penslar Name[pl]=Dziesięć pędzli Name[pt]=Dez Pincéis Name[sv]=Tio penslar Name[tr]=On Fırça Name[uk]=Десять пензлів Name[x-test]=xxTen Brushesxx Name[zh_CN]=常用笔刷快捷键 Name[zh_TW]=10 個筆刷 Comment=Assign a preset to ctrl-1 to ctrl-0 Comment[ca]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[ca@valencia]=Assigna una predefinició des de Ctrl-1 a Ctrl-0 Comment[el]=Αντιστοίχιση προκαθορισμένου από ctrl-1 στο ctrl-0 Comment[en_GB]=Assign a preset to ctrl-1 to ctrl-0 Comment[es]=Asignar una preselección a Ctrl-1 hasta Ctrl-0 +Comment[et]=Valmisvaliku omistamine Ctrl+1 kuni Ctrl+0 Comment[eu]=Aurrezarpena ezarri ktrl-1'etik ktrl-0'ra arte Comment[fi]=Kytke esiasetukset Ctrl-1:stä Ctrl-0:aan Comment[gl]=Asigne unha predefinición do Ctrl+1 ao Ctrl+0. Comment[it]=Assegna una preimpostazione per ctrl-1 a ctrl-0 Comment[ko]=Ctrl+1부터 Ctrl+0까지 프리셋 할당 Comment[nl]=Een voorinstelling toekennen aan Ctrl-1 tot Ctrl-0 Comment[nn]=Byt til ferdigpenslar med «Ctrl + 1» til «Ctrl + 0» Comment[pl]=Przypisz nastawę do ctrl-1 lub ctrl-0 Comment[pt]=Atribuir uma predefinição de Ctrl-1 a Ctrl-0 Comment[sv]=Tilldela en förinställning för Ctrl+1 till Ctrl+0 Comment[tr]= ctrl-1 ve ctrl-0 için ayar atama Comment[uk]=Прив’язування наборів налаштувань до скорочень від ctrl-1 до ctrl-0 Comment[x-test]=xxAssign a preset to ctrl-1 to ctrl-0xx Comment[zh_CN]=将预设分配给由 Ctrl+1 到 Ctrl+0 的快捷键 Comment[zh_TW]=將預設指定給 ctrl-1 至 ctrl-0 diff --git a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop index 16a93d1691..6d3e124d88 100644 --- a/plugins/python/tenscripts/kritapykrita_tenscripts.desktop +++ b/plugins/python/tenscripts/kritapykrita_tenscripts.desktop @@ -1,51 +1,53 @@ [Desktop Entry] Type=Service ServiceTypes=Krita/PythonPlugin X-KDE-Library=tenscripts X-Python-2-Compatible=true X-Krita-Manual=Manual.html Name=Ten Scripts Name[ar]=عشرُ سكربتات Name[ca]=Deu scripts Name[ca@valencia]=Deu scripts Name[en_GB]=Ten Scripts Name[es]=Diez guiones +Name[et]=Kümme skripti Name[eu]=Hamar script Name[fi]=Kymmenen skriptiä Name[fr]=Raccourcis des scripts Name[gl]=Dez scripts Name[is]=Tíu skriftur Name[it]=Dieci script Name[ko]=10개의 스크립트 Name[nl]=Tien scripts Name[nn]=Ti skript Name[pl]=Skrypty Ten Name[pt]=Dez Programas Name[sv]=Tio skript Name[tr]=On Betik Name[uk]=Десять скриптів Name[x-test]=xxTen Scriptsxx Name[zh_CN]=常用脚本快捷键 Name[zh_TW]=10 個指令稿 Comment=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[ar]=ملحقة بِ‍«پيثون» لإنشاء ١٠ إجراءات وإسنادها إلى سكربتات «پيثون» Comment[ca]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[ca@valencia]=Un connector basat en Python per a crear deu accions i assignar-les a scripts en Python Comment[en_GB]=A Python-based plugin for creating ten actions and assign them to Python scripts Comment[es]=Un complemento basado en Python para crear diez acciones y asignarlas a guiones de Python +Comment[et]=Pythoni-põhine plugin kümne toimingu loomiseks ja nende omistamiseks Pythoni skriptidele Comment[eu]=Hamar ekintza sortu eta haiek Python-scriptei esleitzeko Python-oinarridun plugin bat Comment[fi]=Python-pohjainen liitännäinen kymmenen toiminnon luomiseksi kytkemiseksi Python-skripteihin Comment[fr]=Module externe basé sur Python pour créer dix actions et les assigner à des scripts Python Comment[gl]=Un complemento escrito en Python para crear dez accións e asignalas a scripts escritos en Python. Comment[it]=Un'estensione basata su Python per creare dieci azioni e assegnarle a script Python Comment[ko]=10개의 작업을 생성하고 이를 Python 스크립트에 할당하는 Python 기반 플러그인 Comment[nl]=Een op Python gebaseerde plug-in voor aanmaken van tien acties en ze dan toewijzen aan Python-scripts Comment[nn]=Python-basert tillegg som legg til ti handlingar du kan tildela til Python-skript Comment[pl]=Wtyczka oparta na Pythonie do tworzenia działań i przypisywanie ich skryptom Pythona Comment[pt]=Um 'plugin' feito em Python para criar dez acções e atribuí-las a programas em Python Comment[sv]=Ett Python-baserat insticksprogram för att skapa tio åtgärder och tilldela dem till Python-skript Comment[tr]=On eylem oluşturmak ve Python betiklerine atamak için Python tabanlı bir eklenti Comment[uk]=Скрипт на основі Python для створення десяти дій і прив'язування до них скриптів Python Comment[x-test]=xxA Python-based plugin for creating ten actions and assign them to Python scriptsxx Comment[zh_CN]=这是一个基于 Python 编写的插件,它可以创建十种操作,并将它们指定到特定 Python 脚本。 Comment[zh_TW]=基於 Python 的外掛程式,用於建立 10 個動作並且將它們指定給 Python 指令稿 diff --git a/plugins/tools/basictools/kis_tool_multihand.cpp b/plugins/tools/basictools/kis_tool_multihand.cpp index dae752884f..48aba81fe6 100644 --- a/plugins/tools/basictools/kis_tool_multihand.cpp +++ b/plugins/tools/basictools/kis_tool_multihand.cpp @@ -1,605 +1,607 @@ /* * Copyright (c) 2011 Lukáš Tvrdý * Copyright (c) 2011 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_multihand.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_tool_multihand_helper.h" static const int MAXIMUM_BRUSHES = 50; #include #ifdef Q_OS_WIN // quoting DRAND48(3) man-page: // These functions are declared obsolete by SVID 3, // which states that rand(3) should be used instead. #define drand48() (static_cast(qrand()) / static_cast(RAND_MAX)) #endif KisToolMultihand::KisToolMultihand(KoCanvasBase *canvas) : KisToolBrush(canvas), m_transformMode(SYMMETRY), m_angle(0), m_handsCount(6), m_mirrorVertically(false), m_mirrorHorizontally(false), m_showAxes(false), m_translateRadius(100), m_setupAxesFlag(false), m_addSubbrushesMode(false) , customUI(0) { m_helper = new KisToolMultihandHelper(paintingInformationBuilder(), kundo2_i18n("Multibrush Stroke")); resetHelper(m_helper); if (image()) { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); } } KisToolMultihand::~KisToolMultihand() { } void KisToolMultihand::beginPrimaryAction(KoPointerEvent *event) { if(m_setupAxesFlag) { setMode(KisTool::OTHER); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else if (m_addSubbrushesMode){ QPointF newPoint = convertToPixelCoord(event->point); m_subbrOriginalLocations << newPoint; requestUpdateOutline(event->point, 0); updateCanvas(); } else { initTransformations(); KisToolFreehand::beginPrimaryAction(event); } } void KisToolMultihand::continuePrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } else { requestUpdateOutline(event->point, 0); KisToolFreehand::continuePrimaryAction(event); } } void KisToolMultihand::endPrimaryAction(KoPointerEvent *event) { if(mode() == KisTool::OTHER) { setMode(KisTool::HOVER_MODE); requestUpdateOutline(event->point, 0); finishAxesSetup(); } else { KisToolFreehand::endPrimaryAction(event); } } void KisToolMultihand::beginAlternateAction(KoPointerEvent* event, AlternateAction action) { if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { KisToolBrush::beginAlternateAction(event, action); return; } setMode(KisTool::OTHER_1); m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } void KisToolMultihand::continueAlternateAction(KoPointerEvent* event, AlternateAction action) { if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { KisToolBrush::continueAlternateAction(event, action); return; } if (mode() == KisTool::OTHER_1) { m_axesPoint = convertToPixelCoord(event->point); requestUpdateOutline(event->point, 0); updateCanvas(); } } void KisToolMultihand::endAlternateAction(KoPointerEvent* event, AlternateAction action) { if (action != ChangeSize || m_transformMode != COPYTRANSLATE || !m_addSubbrushesMode) { KisToolBrush::endAlternateAction(event, action); return; } if (mode() == KisTool::OTHER_1) { setMode(KisTool::HOVER_MODE); } } void KisToolMultihand::mouseMoveEvent(KoPointerEvent* event) { if (mode() == HOVER_MODE) { m_lastToolPos=convertToPixelCoord(event->point); } KisToolBrush::mouseMoveEvent(event); } void KisToolMultihand::paint(QPainter& gc, const KoViewConverter &converter) { QPainterPath path; if (m_showAxes) { int axisLength = currentImage()->height() + currentImage()->width(); // add division guide lines if using multiple brushes if ((m_handsCount > 1 && m_transformMode == SYMMETRY) || (m_handsCount > 1 && m_transformMode == SNOWFLAKE) ) { int axesCount; if (m_transformMode == SYMMETRY){ axesCount = m_handsCount; } else { axesCount = m_handsCount*2; } qreal axesAngle = 360.0 / float(axesCount); float currentAngle = 0.0; float startingInsetLength = 20; // don't start each line at the origin so we can see better when all points converge // draw lines radiating from the origin for( int i=0; i < axesCount; i++) { currentAngle = i*axesAngle; // convert angles to radians since cos and sin need that currentAngle = currentAngle * 0.017453 + m_angle; // m_angle is current rotation set on UI QPoint startingSpot = QPoint(m_axesPoint.x()+ (sin(currentAngle)*startingInsetLength), m_axesPoint.y()- (cos(currentAngle))*startingInsetLength ); path.moveTo(startingSpot.x(), startingSpot.y()); QPointF symmetryLinePoint(m_axesPoint.x()+ (sin(currentAngle)*axisLength), m_axesPoint.y()- (cos(currentAngle))*axisLength ); path.lineTo(symmetryLinePoint); } } else if(m_transformMode == MIRROR) { if (m_mirrorHorizontally) { path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2)); } if(m_mirrorVertically) { path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle)); } } else if (m_transformMode == COPYTRANSLATE) { int ellipsePreviewSize = 10; // draw ellipse at origin to emphasize this is a drawing point path.addEllipse(m_axesPoint.x()-(ellipsePreviewSize), m_axesPoint.y()-(ellipsePreviewSize), ellipsePreviewSize*2, ellipsePreviewSize*2); for (QPointF dPos : m_subbrOriginalLocations) { path.addEllipse(dPos, ellipsePreviewSize, ellipsePreviewSize); // Show subbrush reference locations while in add mode } // draw the horiz/vertical line for axis origin path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle)); path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2)); } else { // draw the horiz/vertical line for axis origin path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle), m_axesPoint.y()-axisLength*sin(m_angle)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle), m_axesPoint.y()+axisLength*sin(m_angle)); path.moveTo(m_axesPoint.x()-axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()-axisLength*sin(m_angle+M_PI_2)); path.lineTo(m_axesPoint.x()+axisLength*cos(m_angle+M_PI_2), m_axesPoint.y()+axisLength*sin(m_angle+M_PI_2)); } } else { // not showing axis if (m_transformMode == COPYTRANSLATE) { for (QPointF dPos : m_subbrOriginalLocations) { // Show subbrush reference locations while in add mode if (m_addSubbrushesMode) { path.addEllipse(dPos, 10, 10); } } } } KisToolFreehand::paint(gc, converter); // origin point preview line/s gc.save(); QPen outlinePen; outlinePen.setColor(QColor(100,100,100,150)); outlinePen.setStyle(Qt::PenStyle::SolidLine); gc.setPen(outlinePen); paintToolOutline(&gc, pixelToView(path)); gc.restore(); // fill in a dot for the origin if showing axis if (m_showAxes) { // draw a dot at the origin point to help with precisly moving QPainterPath dotPath; int dotRadius = 4; dotPath.moveTo(m_axesPoint.x(), m_axesPoint.y()); dotPath.addEllipse(m_axesPoint.x()- dotRadius*0.25, m_axesPoint.y()- dotRadius*0.25, dotRadius, dotRadius); // last 2 parameters are dot's size QBrush fillBrush; fillBrush.setColor(QColor(255, 255, 255, 255)); fillBrush.setStyle(Qt::SolidPattern); gc.fillPath(pixelToView(dotPath), fillBrush); // add slight offset circle for contrast to help show it on dotPath = QPainterPath(); // resets path dotPath.addEllipse(m_axesPoint.x() - dotRadius*0.75, m_axesPoint.y()- dotRadius*0.75, dotRadius, dotRadius); // last 2 parameters are dot's size fillBrush.setColor(QColor(120, 120, 120, 255)); gc.fillPath(pixelToView(dotPath), fillBrush); } } void KisToolMultihand::initTransformations() { QVector transformations; QTransform m; if(m_transformMode == SYMMETRY) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount; for(int i = 0; i < m_handsCount; i++) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep; } } else if(m_transformMode == MIRROR) { transformations << m; if (m_mirrorHorizontally) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically) { m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } if (m_mirrorVertically && m_mirrorHorizontally){ m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.scale(-1,-1); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if(m_transformMode == SNOWFLAKE) { qreal angle = 0; qreal angleStep = (2 * M_PI) / m_handsCount/4; for(int i = 0; i < m_handsCount*4; i++) { if ((i%2)==1) { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.scale(-1,1); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } else { m.translate(m_axesPoint.x(), m_axesPoint.y()); m.rotateRadians(m_angle-angleStep); m.rotateRadians(angle); m.rotateRadians(-m_angle+angleStep); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); angle += angleStep*2; } } } else if(m_transformMode == TRANSLATE) { /** * TODO: currently, the seed is the same for all the * strokes */ for (int i = 0; i < m_handsCount; i++){ qreal angle = drand48() * M_PI * 2; qreal length = drand48(); // convert the Polar coordinates to Cartesian coordinates qreal nx = (m_translateRadius * cos(angle) * length); qreal ny = (m_translateRadius * sin(angle) * length); m.translate(m_axesPoint.x(),m_axesPoint.y()); m.rotateRadians(m_angle); m.translate(nx,ny); m.rotateRadians(-m_angle); m.translate(-m_axesPoint.x(), -m_axesPoint.y()); transformations << m; m.reset(); } } else if (m_transformMode == COPYTRANSLATE) { transformations << m; for (QPointF dPos : m_subbrOriginalLocations) { QPointF resPos = dPos-m_axesPoint; // Calculate the difference between subbrush reference position and "origin" reference m.translate(resPos.x(), resPos.y()); transformations << m; m.reset(); } } m_helper->setupTransformations(transformations); } QWidget* KisToolMultihand::createOptionWidget() { QWidget *widget = KisToolBrush::createOptionWidget(); customUI = new KisToolMultiHandConfigWidget(); // brush smoothing option. //customUI->layout()->addWidget(widget); customUI->smoothingOptionsLayout->addWidget(widget); // setup common parameters that all of the modes will see connect(customUI->showAxesCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetAxesVisible(bool))); customUI->showAxesCheckbox->setChecked((bool)m_configGroup.readEntry("showAxes", false)); connect(image(), SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(resetAxes())); customUI->moveOriginButton->setCheckable(true); connect(customUI->moveOriginButton, SIGNAL(clicked(bool)),this, SLOT(activateAxesPointModeSetup())); connect(customUI->resetOriginButton, SIGNAL(released()), this, SLOT(resetAxes())); customUI->multihandTypeCombobox->addItem(i18n("Symmetry"),int(SYMMETRY)); // axis mode customUI->multihandTypeCombobox->addItem(i18n("Mirror"),int(MIRROR)); customUI->multihandTypeCombobox->addItem(i18n("Translate"),int(TRANSLATE)); customUI->multihandTypeCombobox->addItem(i18n("Snowflake"),int(SNOWFLAKE)); customUI->multihandTypeCombobox->addItem(i18n("Copy Translate"),int(COPYTRANSLATE)); connect(customUI->multihandTypeCombobox,SIGNAL(currentIndexChanged(int)),this, SLOT(slotSetTransformMode(int))); customUI->multihandTypeCombobox->setCurrentIndex(m_configGroup.readEntry("transformMode", 0)); slotSetTransformMode(customUI->multihandTypeCombobox->currentIndex()); customUI->axisRotationSpinbox->setSuffix(QChar(Qt::Key_degree)); // origin rotation customUI->axisRotationSpinbox->setSingleStep(0.5); customUI->axisRotationSpinbox->setRange(0.0, 90.0, 1); customUI->axisRotationSpinbox->setValue(m_configGroup.readEntry("axesAngle", 0.0)); connect( customUI->axisRotationSpinbox, SIGNAL(valueChanged(qreal)),this, SLOT(slotSetAxesAngle(qreal))); // symmetry mode options customUI->brushCountSpinBox->setRange(1, MAXIMUM_BRUSHES); connect(customUI->brushCountSpinBox, SIGNAL(valueChanged(int)),this, SLOT(slotSetHandsCount(int))); customUI->brushCountSpinBox->setValue(m_configGroup.readEntry("handsCount", 4)); // mirror mode specific options connect(customUI->horizontalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorHorizontally(bool))); customUI->horizontalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorHorizontally", false)); connect(customUI->verticalCheckbox, SIGNAL(toggled(bool)), this, SLOT(slotSetMirrorVertically(bool))); customUI->verticalCheckbox->setChecked((bool)m_configGroup.readEntry("mirrorVertically", false)); // translate mode options customUI->translationRadiusSpinbox->setRange(0, 200); customUI->translationRadiusSpinbox->setSuffix(i18n(" px")); customUI->translationRadiusSpinbox->setValue(m_configGroup.readEntry("translateRadius", 0)); connect(customUI->translationRadiusSpinbox,SIGNAL(valueChanged(int)),this,SLOT(slotSetTranslateRadius(int))); // Copy translate mode options and actions connect(customUI->addSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotAddSubbrushesMode); connect(customUI->removeSubbrushButton, &QPushButton::clicked, this, &KisToolMultihand::slotRemoveAllSubbrushes); // snowflake re-uses the existing options, so there is no special parameters for that... return static_cast(customUI); // keeping it in the native class until the end allows us to access the UI components } void KisToolMultihand::activateAxesPointModeSetup() { if (customUI->moveOriginButton->isChecked()){ m_setupAxesFlag = true; useCursor(KisCursor::crossCursor()); updateCanvas(); } else { finishAxesSetup(); } } void KisToolMultihand::resetAxes() { m_axesPoint = QPointF(0.5 * image()->width(), 0.5 * image()->height()); finishAxesSetup(); } void KisToolMultihand::finishAxesSetup() { m_setupAxesFlag = false; customUI->moveOriginButton->setChecked(false); resetCursorStyle(); updateCanvas(); } void KisToolMultihand::updateCanvas() { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); Q_ASSERT(kisCanvas); kisCanvas->updateCanvas(); } void KisToolMultihand::slotSetHandsCount(int count) { m_handsCount = count; m_configGroup.writeEntry("handsCount", count); updateCanvas(); } void KisToolMultihand::slotSetAxesAngle(qreal angle) { //negative so axes rotates counter clockwise m_angle = -angle*M_PI/180; updateCanvas(); m_configGroup.writeEntry("axesAngle", angle); } void KisToolMultihand::slotSetTransformMode(int index) { m_transformMode = enumTransforModes(customUI->multihandTypeCombobox->itemData(index).toInt()); m_configGroup.writeEntry("transformMode", index); // hide all of the UI elements by default customUI->horizontalCheckbox->setVisible(false); customUI->verticalCheckbox->setVisible(false); customUI->translationRadiusSpinbox->setVisible(false); customUI->radiusLabel->setVisible(false); customUI->brushCountSpinBox->setVisible(false); customUI->brushesLabel->setVisible(false); customUI->subbrushLabel->setVisible(false); customUI->addSubbrushButton->setVisible(false); customUI->removeSubbrushButton->setVisible(false); + m_addSubbrushesMode = 0; // turn on what we need if (index == MIRROR) { customUI->horizontalCheckbox->setVisible(true); customUI->verticalCheckbox->setVisible(true); } else if (index == TRANSLATE) { customUI->translationRadiusSpinbox->setVisible(true); customUI->radiusLabel->setVisible(true); customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } else if (index == SYMMETRY || index == SNOWFLAKE || index == TRANSLATE ) { customUI->brushCountSpinBox->setVisible(true); customUI->brushesLabel->setVisible(true); } else if (index == COPYTRANSLATE) { customUI->subbrushLabel->setVisible(true); customUI->addSubbrushButton->setVisible(true); + customUI->addSubbrushButton->setChecked(false); customUI->removeSubbrushButton->setVisible(true); } } void KisToolMultihand::slotSetAxesVisible(bool vis) { m_showAxes = vis; updateCanvas(); m_configGroup.writeEntry("showAxes", vis); } void KisToolMultihand::slotSetMirrorVertically(bool mirror) { m_mirrorVertically = mirror; updateCanvas(); m_configGroup.writeEntry("mirrorVertically", mirror); } void KisToolMultihand::slotSetMirrorHorizontally(bool mirror) { m_mirrorHorizontally = mirror; updateCanvas(); m_configGroup.writeEntry("mirrorHorizontally", mirror); } void KisToolMultihand::slotSetTranslateRadius(int radius) { m_translateRadius = radius; m_configGroup.writeEntry("translateRadius", radius); } void KisToolMultihand::slotAddSubbrushesMode(bool checked) { m_addSubbrushesMode = checked; updateCanvas(); } void KisToolMultihand::slotRemoveAllSubbrushes() { m_subbrOriginalLocations.clear(); updateCanvas(); } diff --git a/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp b/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp index 7cc8ed4d0b..b789d9094a 100644 --- a/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp +++ b/plugins/tools/svgtexttool/kis_font_family_combo_box.cpp @@ -1,306 +1,306 @@ /* This file is part of the KDE project * * Copyright 2017 Wolthera van Hövell tot Westerflier * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_font_family_combo_box.h" #include #include #include #include #include #include #include #include #include #include #include PinnedFontsSeparator::PinnedFontsSeparator(QAbstractItemDelegate *_default, QWidget *parent) : QStyledItemDelegate(parent), m_separatorIndex(0), m_separatorAdded(false), m_defaultDelegate(_default) { } void PinnedFontsSeparator::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.row() == m_separatorIndex && m_separatorAdded) { QRect viewRect = option.rect; painter->setPen(Qt::gray); painter->drawLine((viewRect.topLeft() + viewRect.bottomLeft()) / 2 + QPoint(5, 0), (viewRect.topRight() + viewRect.bottomRight()) / 2 - QPoint(5, 0)); } else { m_defaultDelegate->paint(painter, option, index); } } QSize PinnedFontsSeparator::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { return QStyledItemDelegate::sizeHint(option, index) * 1.25; } void PinnedFontsSeparator::setSeparatorIndex(int index) { m_separatorIndex = index; } void PinnedFontsSeparator::setSeparatorAdded() { m_separatorAdded = true; } KisFontFamilyComboBox::KisFontFamilyComboBox(QWidget *parent) : QComboBox(parent), m_initilized(false), m_initializeFromConfig(false) { setEditable(true); completer()->setCompletionMode(QCompleter::InlineCompletion); completer()->setCaseSensitivity(Qt::CaseInsensitive); // The following are all helper fonts for LaTeX that no one but LaTeX would use // but because many people use LaTeX, they do show up on quite a few systems. m_blacklistedFonts << "bbold10" << "cmbsy10" << "cmmib10" << "cmss10" << "cmex10" << "cmmi10" << "cmr10" << "cmsy10" << "eufb10" << "eufm10" << "eurb10" << "eurm10" << "esint10" << "eufm10" << "eusb10" << "eusm10" << "lasy10" << "lasyb10" << "msam10" << "msbm10" << "rsfs10" << "stmary10" << "wasy10" << "wasyb10"; refillComboBox(); - QFontComboBox *temp = new QFontComboBox(); + QFontComboBox *temp = new QFontComboBox(this); m_fontSeparator = new PinnedFontsSeparator(temp->itemDelegate(), this); temp->setEnabled(true); temp->hide(); m_separatorIndex = 0; m_pinnedFonts = KisConfig(true).readList("PinnedFonts", QStringList{}); } void KisFontFamilyComboBox::refillComboBox(QVector writingSystems) { QFontDatabase fonts = QFontDatabase(); int maxWidth = 0; this->clear(); QStringList duplicateFonts; QStringList filteredFonts; if (writingSystems.isEmpty()) { writingSystems.append(QFontDatabase::Any); } for (int i = 0; i < writingSystems.size(); i++) { Q_FOREACH (QString family, fonts.families(writingSystems.at(i))) { // if it's a private family it shouldn't be added. bool addFont = !fonts.isPrivateFamily(family); if (addFont && filteredFonts.contains(family)) { addFont = false; } if (addFont && duplicateFonts.contains(family)) { addFont = false; } if (addFont && m_blacklistedFonts.contains(family)) { addFont = false; } if (addFont && !fonts.isSmoothlyScalable(family)) { addFont = false; } if (addFont) { // now, check for all possible familyname+style name combinations, so we can avoid those. Q_FOREACH (const QString style, fonts.styles(family)) { duplicateFonts.append(family + " " + style); duplicateFonts.append(family + "_" + style); } filteredFonts.append(family); int width = 1.5 * view()->fontMetrics() .width(family + " " + fonts.writingSystemSample(QFontDatabase::Any)); if (width > maxWidth) { maxWidth = width; } } } } this->addItems(filteredFonts); if (this->count() > this->maxVisibleItems()) { maxWidth += view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent); } view()->setMinimumWidth(maxWidth); } // KisFontFamilyComboBox::refillComboBox void KisFontFamilyComboBox::setTopFont(const QString &family) { if (family.isEmpty() || !m_initilized || m_pinnedFonts.contains(family)) { return; } if (m_pinnedFonts.count() > 4) { this->removeItem(4); m_pinnedFonts.pop_back(); m_separatorIndex--; } if (m_pinnedFonts.isEmpty()) { this->insertSeparator(0); m_fontSeparator->setSeparatorAdded(); } m_pinnedFonts.push_front(family); this->insertItem(0, family); m_separatorIndex++; m_fontSeparator->setSeparatorIndex(m_separatorIndex); KisConfig(false).writeList("PinnedFonts", m_pinnedFonts); } void KisFontFamilyComboBox::setInitialized() { if(m_initilized) return; m_initilized = true; for(int i=m_pinnedFonts.count()-1; i>=0; i--){ this->insertItem(0, m_pinnedFonts[i]); m_separatorIndex++; } if(m_pinnedFonts.count() > 0){ this->insertSeparator(m_separatorIndex); m_fontSeparator->setSeparatorIndex(m_separatorIndex); m_fontSeparator->setSeparatorAdded(); } this->setItemDelegate(m_fontSeparator); } KisFontComboBoxes::KisFontComboBoxes(QWidget *parent) : QWidget(parent) { QHBoxLayout *layout = new QHBoxLayout(); this->setLayout(layout); m_family = new KisFontFamilyComboBox(); m_family->setMinimumWidth(100); m_family->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); layout->addWidget(m_family); m_styles = new QComboBox(); layout->addWidget(m_styles); fontFamilyChanged(); m_family->setToolTip(i18n("Font Family")); m_styles->setToolTip(i18n("Font Style")); connect(m_family, SIGNAL(currentTextChanged(QString)), this, SLOT(fontFamilyChanged())); connect(m_family, SIGNAL(currentTextChanged(QString)), this, SLOT(fontChange())); connect(m_styles, SIGNAL(activated(int)), this, SLOT(fontChange())); } void KisFontComboBoxes::setCurrentFont(QFont font) { setCurrentFamily(font.family()); setCurrentStyle(QFontDatabase().styleString(font)); } void KisFontComboBoxes::setCurrentFamily(const QString family) { m_family->setCurrentText(family); fontFamilyChanged(); } void KisFontComboBoxes::setCurrentStyle(QString style) { QString properStyle = QString(); for (int i = 0; i < m_styles->count(); i++) { QString item = m_styles->itemText(i); if (item == style) { properStyle = style; } else if (item.contains(style, Qt::CaseInsensitive)) { properStyle = item; } else if (item.contains("regular", Qt::CaseInsensitive)) { properStyle = item; } } m_styles->setCurrentText(properStyle); } QString KisFontComboBoxes::currentFamily() const { return m_family->currentText(); } QString KisFontComboBoxes::currentStyle() const { return m_styles->currentText(); } QFont KisFontComboBoxes::currentFont(int pointSize) const { return QFontDatabase().font(m_family->currentText(), m_styles->currentText(), pointSize); } void KisFontComboBoxes::refillComboBox(QVector writingSystems) { KisFontFamilyComboBox *cmb = qobject_cast(m_family); cmb->refillComboBox(writingSystems); } void KisFontComboBoxes::fontFamilyChanged() { QString currentText = m_styles->currentText(); QFontDatabase fonts; const QString family = m_family->currentText(); int maxWidth = 0; m_styles->clear(); QStringList styles; KisFontFamilyComboBox *cmb = qobject_cast(m_family); cmb->setTopFont(family); if (fonts.styles(family).isEmpty()) { styles.append("Normal"); } Q_FOREACH (const QString style, fonts.styles(family)) { int b = fonts.weight(family, style); int bindex = 0; for (int i = 0; i < styles.size(); i++) { if (b > fonts.weight(family, styles.at(i))) { bindex = i; } } if (!styles.contains(style)) { styles.insert(bindex, style); maxWidth = qMax(m_styles->view()->fontMetrics().width(style + " "), maxWidth); } } m_styles->addItems(styles); if (m_styles->count() > m_styles->maxVisibleItems()) { maxWidth += m_styles->view()->style()->pixelMetric(QStyle::PixelMetric::PM_ScrollBarExtent); } m_styles->view()->setMinimumWidth(maxWidth); if (styles.contains(currentText)) { m_styles->setCurrentText(currentText); } } // KisFontComboBoxes::fontFamilyChanged void KisFontComboBoxes::fontChange() { emit fontChanged(currentFont(10).toString()); } void KisFontComboBoxes::setInitialized() { KisFontFamilyComboBox *cmb = qobject_cast(m_family); cmb->setInitialized(); } diff --git a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp index a92e804783..815c96e414 100644 --- a/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp +++ b/plugins/tools/tool_lazybrush/kis_tool_lazy_brush.cpp @@ -1,350 +1,359 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_lazy_brush.h" #include #include #include #include #include #include #include #include "kis_canvas2.h" #include "kis_cursor.h" #include "kis_config.h" #include "kundo2magicstring.h" #include "KoProperties.h" #include "kis_node_manager.h" #include "kis_layer_properties_icons.h" #include "kis_canvas_resource_provider.h" #include "kis_tool_lazy_brush_options_widget.h" #include "lazybrush/kis_colorize_mask.h" #include "kis_signal_auto_connection.h" struct KisToolLazyBrush::Private { bool activateMaskMode = false; bool oldShowKeyStrokesValue = false; bool oldShowColoringValue = false; KisNodeWSP manuallyActivatedNode; KisSignalAutoConnectionsStore toolConnections; }; KisToolLazyBrush::KisToolLazyBrush(KoCanvasBase * canvas) : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.png", 5, 5), kundo2_i18n("Colorize Mask Key Stroke")), m_d(new Private) { setObjectName("tool_lazybrush"); } KisToolLazyBrush::~KisToolLazyBrush() { } void KisToolLazyBrush::tryDisableKeyStrokesOnMask() { // upgrade to strong pointer KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; if (manuallyActivatedNode) { KisLayerPropertiesIcons::setNodeProperty(manuallyActivatedNode, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image()); manuallyActivatedNode = 0; } m_d->manuallyActivatedNode = 0; } void KisToolLazyBrush::activate(ToolActivation activation, const QSet &shapes) { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); m_d->toolConnections.addUniqueConnection( kiscanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigNodeChanged(KisNodeSP)), this, SLOT(slotCurrentNodeChanged(KisNodeSP))); KisColorizeMask *mask = qobject_cast(currentNode().data()); if (mask) { mask->regeneratePrefilteredDeviceIfNeeded(); } KisToolFreehand::activate(activation, shapes); } void KisToolLazyBrush::deactivate() { KisToolFreehand::deactivate(); tryDisableKeyStrokesOnMask(); m_d->toolConnections.clear(); } void KisToolLazyBrush::slotCurrentNodeChanged(KisNodeSP node) { // upgrade to strong pointer KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; if (node != manuallyActivatedNode) { tryDisableKeyStrokesOnMask(); KisColorizeMask *mask = qobject_cast(node.data()); if (mask) { mask->regeneratePrefilteredDeviceIfNeeded(); } } } void KisToolLazyBrush::resetCursorStyle() { - KisToolFreehand::resetCursorStyle(); + // If there's no mask yet, we show the hand cursor + if (!colorizeMaskActive() && canCreateColorizeMask()) { + useCursor(KisCursor::handCursor()); + m_d->activateMaskMode = true; + setOutlineEnabled(false); + } + else { + KisToolFreehand::resetCursorStyle(); + } } bool KisToolLazyBrush::colorizeMaskActive() const { KisNodeSP node = currentNode(); return node && node->inherits("KisColorizeMask"); } bool KisToolLazyBrush::canCreateColorizeMask() const { KisNodeSP node = currentNode(); return node && node->inherits("KisLayer"); } bool KisToolLazyBrush::shouldActivateKeyStrokes() const { KisNodeSP node = currentNode(); return node && node->inherits("KisColorizeMask") && !KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); } void KisToolLazyBrush::tryCreateColorizeMask() { KisNodeSP node = currentNode(); if (!node) return; KoProperties properties; properties.setProperty("visible", true); properties.setProperty("locked", false); QList masks = node->childNodes(QStringList("KisColorizeMask"), properties); if (!masks.isEmpty()) { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->nodeManager()->slotNonUiActivatedNode(masks.first()); } else { KisCanvas2 * kiscanvas = static_cast(canvas()); KisViewManager* viewManager = kiscanvas->viewManager(); viewManager->nodeManager()->createNode("KisColorizeMask"); } } void KisToolLazyBrush::activatePrimaryAction() { KisToolFreehand::activatePrimaryAction(); - if (shouldActivateKeyStrokes() || - (!colorizeMaskActive() && canCreateColorizeMask())) { + qDebug() << "1"; + if (!colorizeMaskActive() && canCreateColorizeMask()) { + qDebug() << "2"; useCursor(KisCursor::handCursor()); m_d->activateMaskMode = true; setOutlineEnabled(false); } } void KisToolLazyBrush::deactivatePrimaryAction() { if (m_d->activateMaskMode) { m_d->activateMaskMode = false; setOutlineEnabled(true); resetCursorStyle(); } KisToolFreehand::deactivatePrimaryAction(); } void KisToolLazyBrush::beginPrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) { if (!colorizeMaskActive() && canCreateColorizeMask()) { tryCreateColorizeMask(); } else if (shouldActivateKeyStrokes()) { // upgrade to strong pointer KisNodeSP manuallyActivatedNode = m_d->manuallyActivatedNode; KisNodeSP node = currentNode(); KIS_SAFE_ASSERT_RECOVER_NOOP(!manuallyActivatedNode || manuallyActivatedNode == node); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true, image()); m_d->manuallyActivatedNode = node; } } else { KisToolFreehand::beginPrimaryAction(event); } } void KisToolLazyBrush::continuePrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) return; KisToolFreehand::continuePrimaryAction(event); } void KisToolLazyBrush::endPrimaryAction(KoPointerEvent *event) { if (m_d->activateMaskMode) return; KisToolFreehand::endPrimaryAction(event); } void KisToolLazyBrush::activateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::Secondary && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; m_d->oldShowKeyStrokesValue = KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, !m_d->oldShowKeyStrokesValue, image()); KisToolFreehand::activatePrimaryAction(); } else if (action == KisTool::Third && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; m_d->oldShowColoringValue = KisLayerPropertiesIcons::nodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(); KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, !m_d->oldShowColoringValue, image()); KisToolFreehand::activatePrimaryAction(); } else { KisToolFreehand::activateAlternateAction(action); } } void KisToolLazyBrush::deactivateAlternateAction(KisTool::AlternateAction action) { if (action == KisTool::Secondary && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeEditKeyStrokes, m_d->oldShowKeyStrokesValue, image()); KisToolFreehand::deactivatePrimaryAction(); } else if (action == KisTool::Third && !m_d->activateMaskMode) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeShowColoring, m_d->oldShowColoringValue, image()); KisToolFreehand::deactivatePrimaryAction(); } else { KisToolFreehand::deactivateAlternateAction(action); } } void KisToolLazyBrush::beginAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { beginPrimaryAction(event); } else { KisToolFreehand::beginAlternateAction(event, action); } } void KisToolLazyBrush::continueAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { continuePrimaryAction(event); } else { KisToolFreehand::continueAlternateAction(event, action); } } void KisToolLazyBrush::endAlternateAction(KoPointerEvent *event, KisTool::AlternateAction action) { if (!m_d->activateMaskMode && (action == KisTool::Secondary || action == KisTool::Third)) { endPrimaryAction(event); } else { KisToolFreehand::endAlternateAction(event, action); } } void KisToolLazyBrush::explicitUserStrokeEndRequest() { if (m_d->activateMaskMode) { tryCreateColorizeMask(); } else if (colorizeMaskActive()) { KisNodeSP node = currentNode(); if (!node) return; KisLayerPropertiesIcons::setNodeProperty(node, KisLayerPropertiesIcons::colorizeNeedsUpdate, false, image()); } } QWidget * KisToolLazyBrush::createOptionWidget() { KisCanvas2 * kiscanvas = dynamic_cast(canvas()); QWidget *optionsWidget = new KisToolLazyBrushOptionsWidget(kiscanvas->viewManager()->canvasResourceProvider(), 0); optionsWidget->setObjectName(toolId() + "option widget"); // // See https://bugs.kde.org/show_bug.cgi?id=316896 // QWidget *specialSpacer = new QWidget(optionsWidget); // specialSpacer->setObjectName("SpecialSpacer"); // specialSpacer->setFixedSize(0, 0); // optionsWidget->layout()->addWidget(specialSpacer); return optionsWidget; } diff --git a/plugins/tools/tool_transform2/kis_tool_transform.cc b/plugins/tools/tool_transform2/kis_tool_transform.cc index 529bed98ef..bc72332ef5 100644 --- a/plugins/tools/tool_transform2/kis_tool_transform.cc +++ b/plugins/tools/tool_transform2/kis_tool_transform.cc @@ -1,1125 +1,1126 @@ /* * kis_tool_transform.cc -- part of Krita * * Copyright (c) 2004 Boudewijn Rempt * Copyright (c) 2005 C. Boemann * Copyright (c) 2010 Marc Pegon * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_tool_transform.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 "widgets/kis_progress_widget.h" #include "kis_transform_utils.h" #include "kis_warp_transform_strategy.h" #include "kis_cage_transform_strategy.h" #include "kis_liquify_transform_strategy.h" #include "kis_free_transform_strategy.h" #include "kis_perspective_transform_strategy.h" #include "kis_transform_mask.h" #include "kis_transform_mask_adapter.h" #include "krita_container_utils.h" #include "kis_layer_utils.h" #include #include "strokes/transform_stroke_strategy.h" KisToolTransform::KisToolTransform(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::rotateCursor()) , m_workRecursively(true) , m_warpStrategy( new KisWarpTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_cageStrategy( new KisCageTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction)) , m_liquifyStrategy( new KisLiquifyTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), m_currentArgs, m_transaction, canvas->resourceManager())) , m_freeStrategy( new KisFreeTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) , m_perspectiveStrategy( new KisPerspectiveTransformStrategy( dynamic_cast(canvas)->coordinatesConverter(), dynamic_cast(canvas)->snapGuide(), m_currentArgs, m_transaction)) { m_canvas = dynamic_cast(canvas); Q_ASSERT(m_canvas); setObjectName("tool_transform"); useCursor(KisCursor::selectCursor()); m_optionsWidget = 0; warpAction = new KisAction(i18n("Warp")); liquifyAction = new KisAction(i18n("Liquify")); cageAction = new KisAction(i18n("Cage")); freeTransformAction = new KisAction(i18n("Free")); perspectiveAction = new KisAction(i18n("Perspective")); // extra actions for free transform that are in the tool options mirrorHorizontalAction = new KisAction(i18n("Mirror Horizontal")); mirrorVericalAction = new KisAction(i18n("Mirror Vertical")); rotateNinteyCWAction = new KisAction(i18n("Rotate 90 degrees Clockwise")); rotateNinteyCCWAction = new KisAction(i18n("Rotate 90 degrees CounterClockwise")); applyTransformation = new KisAction(i18n("Apply")); resetTransformation = new KisAction(i18n("Reset")); m_contextMenu.reset(new QMenu()); connect(m_warpStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_cageStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_liquifyStrategy.data(), SIGNAL(requestCursorOutlineUpdate(QPointF)), SLOT(cursorOutlineUpdateRequested(QPointF))); connect(m_liquifyStrategy.data(), SIGNAL(requestUpdateOptionWidget()), SLOT(updateOptionWidget())); connect(m_freeStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_freeStrategy.data(), SIGNAL(requestResetRotationCenterButtons()), SLOT(resetRotationCenterButtonsRequested())); connect(m_freeStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(m_perspectiveStrategy.data(), SIGNAL(requestCanvasUpdate()), SLOT(canvasUpdateRequested())); connect(m_perspectiveStrategy.data(), SIGNAL(requestShowImageTooBig(bool)), SLOT(imageTooBigRequested(bool))); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), this, SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); } KisToolTransform::~KisToolTransform() { cancelStroke(); } void KisToolTransform::outlineChanged() { emit freeTransformChanged(); m_canvas->updateCanvas(); } void KisToolTransform::canvasUpdateRequested() { m_canvas->updateCanvas(); } void KisToolTransform::resetCursorStyle() { setFunctionalCursor(); } void KisToolTransform::resetRotationCenterButtonsRequested() { if (!m_optionsWidget) return; m_optionsWidget->resetRotationCenterButtons(); } void KisToolTransform::imageTooBigRequested(bool value) { if (!m_optionsWidget) return; m_optionsWidget->setTooBigLabelVisible(value); } KisTransformStrategyBase* KisToolTransform::currentStrategy() const { if (m_currentArgs.mode() == ToolTransformArgs::FREE_TRANSFORM) { return m_freeStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::WARP) { return m_warpStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::CAGE) { return m_cageStrategy.data(); } else if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { return m_liquifyStrategy.data(); } else /* if (m_currentArgs.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) */ { return m_perspectiveStrategy.data(); } } void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (!m_strokeId || !m_transaction.rootNode()) return; QRectF newRefRect = KisTransformUtils::imageToFlake(m_canvas->coordinatesConverter(), QRectF(0.0,0.0,1.0,1.0)); if (m_refRect != newRefRect) { m_refRect = newRefRect; currentStrategy()->externalConfigChanged(); } currentStrategy()->paint(gc); if (!m_cursorOutline.isEmpty()) { QPainterPath mappedOutline = KisTransformUtils::imageToFlakeTransform( m_canvas->coordinatesConverter()).map(m_cursorOutline); paintToolOutline(&gc, mappedOutline); } } void KisToolTransform::setFunctionalCursor() { if (overrideCursorIfNotEditable()) { return; } if (!m_strokeId) { useCursor(KisCursor::pointingHandCursor()); } else if (m_strokeId && !m_transaction.rootNode()) { // we are in the middle of stroke initialization useCursor(KisCursor::waitCursor()); } else { useCursor(currentStrategy()->getCurrentCursor()); } } void KisToolTransform::cursorOutlineUpdateRequested(const QPointF &imagePos) { QRect canvasUpdateRect; if (!m_cursorOutline.isEmpty()) { canvasUpdateRect = m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } m_cursorOutline = currentStrategy()-> getCursorOutline().translated(imagePos); if (!m_cursorOutline.isEmpty()) { canvasUpdateRect |= m_canvas->coordinatesConverter()-> imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect(); } if (!canvasUpdateRect.isEmpty()) { // grow rect a bit to follow interpolation fuzziness canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2); m_canvas->updateCanvas(canvasUpdateRect); } } void KisToolTransform::beginActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (!nodeEditable()) { event->ignore(); return; } if (!m_strokeId) { startStroke(m_currentArgs.mode(), false); } else if (m_transaction.rootNode()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->beginPrimaryAction(event); } else { result = currentStrategy()->beginAlternateAction(event, action); } if (result) { setMode(KisTool::PAINT_MODE); } } m_actuallyMoveWhileSelected = false; outlineChanged(); } void KisToolTransform::continueActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; if (!m_transaction.rootNode()) return; m_actuallyMoveWhileSelected = true; if (usePrimaryAction) { currentStrategy()->continuePrimaryAction(event); } else { currentStrategy()->continueAlternateAction(event, action); } updateOptionWidget(); outlineChanged(); } void KisToolTransform::endActionImpl(KoPointerEvent *event, bool usePrimaryAction, KisTool::AlternateAction action) { if (mode() != KisTool::PAINT_MODE) return; setMode(KisTool::HOVER_MODE); if (m_actuallyMoveWhileSelected || currentStrategy()->acceptsClicks()) { bool result = false; if (usePrimaryAction) { result = currentStrategy()->endPrimaryAction(event); } else { result = currentStrategy()->endAlternateAction(event, action); } if (result) { commitChanges(); } outlineChanged(); } updateOptionWidget(); updateApplyResetAvailability(); } QMenu* KisToolTransform::popupActionsMenu() { if (m_contextMenu) { m_contextMenu->clear(); m_contextMenu->addSection(i18n("Transform Tool Actions")); m_contextMenu->addSeparator(); // add a quick switch to different transform types m_contextMenu->addAction(freeTransformAction); m_contextMenu->addAction(perspectiveAction); m_contextMenu->addAction(warpAction); m_contextMenu->addAction(cageAction); m_contextMenu->addAction(liquifyAction); // extra options if free transform is selected if (transformMode() == FreeTransformMode) { m_contextMenu->addSeparator(); m_contextMenu->addAction(mirrorHorizontalAction); m_contextMenu->addAction(mirrorVericalAction); m_contextMenu->addAction(rotateNinteyCWAction); m_contextMenu->addAction(rotateNinteyCCWAction); } m_contextMenu->addSeparator(); m_contextMenu->addAction(applyTransformation); m_contextMenu->addAction(resetTransformation); } return m_contextMenu.data(); } void KisToolTransform::beginPrimaryAction(KoPointerEvent *event) { beginActionImpl(event, true, KisTool::NONE); } void KisToolTransform::continuePrimaryAction(KoPointerEvent *event) { continueActionImpl(event, true, KisTool::NONE); } void KisToolTransform::endPrimaryAction(KoPointerEvent *event) { endActionImpl(event, true, KisTool::NONE); } void KisToolTransform::activatePrimaryAction() { currentStrategy()->activatePrimaryAction(); setFunctionalCursor(); } void KisToolTransform::deactivatePrimaryAction() { currentStrategy()->deactivatePrimaryAction(); } void KisToolTransform::activateAlternateAction(AlternateAction action) { currentStrategy()->activateAlternateAction(action); setFunctionalCursor(); } void KisToolTransform::deactivateAlternateAction(AlternateAction action) { currentStrategy()->deactivateAlternateAction(action); } void KisToolTransform::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { beginActionImpl(event, false, action); } void KisToolTransform::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { continueActionImpl(event, false, action); } void KisToolTransform::endAlternateAction(KoPointerEvent *event, AlternateAction action) { endActionImpl(event, false, action); } void KisToolTransform::mousePressEvent(KoPointerEvent *event) { KisTool::mousePressEvent(event); } void KisToolTransform::mouseMoveEvent(KoPointerEvent *event) { QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point); cursorOutlineUpdateRequested(mousePos); if (this->mode() != KisTool::PAINT_MODE) { currentStrategy()->hoverActionCommon(event); setFunctionalCursor(); KisTool::mouseMoveEvent(event); return; } } void KisToolTransform::mouseReleaseEvent(KoPointerEvent *event) { KisTool::mouseReleaseEvent(event); } void KisToolTransform::applyTransform() { slotApplyTransform(); } KisToolTransform::TransformToolMode KisToolTransform::transformMode() const { TransformToolMode mode = FreeTransformMode; switch (m_currentArgs.mode()) { case ToolTransformArgs::FREE_TRANSFORM: mode = FreeTransformMode; break; case ToolTransformArgs::WARP: mode = WarpTransformMode; break; case ToolTransformArgs::CAGE: mode = CageTransformMode; break; case ToolTransformArgs::LIQUIFY: mode = LiquifyTransformMode; break; case ToolTransformArgs::PERSPECTIVE_4POINT: mode = PerspectiveTransformMode; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } return mode; } double KisToolTransform::translateX() const { return m_currentArgs.transformedCenter().x(); } double KisToolTransform::translateY() const { return m_currentArgs.transformedCenter().y(); } double KisToolTransform::rotateX() const { return m_currentArgs.aX(); } double KisToolTransform::rotateY() const { return m_currentArgs.aY(); } double KisToolTransform::rotateZ() const { return m_currentArgs.aZ(); } double KisToolTransform::scaleX() const { return m_currentArgs.scaleX(); } double KisToolTransform::scaleY() const { return m_currentArgs.scaleY(); } double KisToolTransform::shearX() const { return m_currentArgs.shearX(); } double KisToolTransform::shearY() const { return m_currentArgs.shearY(); } KisToolTransform::WarpType KisToolTransform::warpType() const { switch(m_currentArgs.warpType()) { case KisWarpTransformWorker::AFFINE_TRANSFORM: return AffineWarpType; case KisWarpTransformWorker::RIGID_TRANSFORM: return RigidWarpType; case KisWarpTransformWorker::SIMILITUDE_TRANSFORM: return SimilitudeWarpType; default: return RigidWarpType; } } double KisToolTransform::warpFlexibility() const { return m_currentArgs.alpha(); } int KisToolTransform::warpPointDensity() const { return m_currentArgs.numPoints(); } void KisToolTransform::setTransformMode(KisToolTransform::TransformToolMode newMode) { ToolTransformArgs::TransformMode mode = ToolTransformArgs::FREE_TRANSFORM; switch (newMode) { case FreeTransformMode: mode = ToolTransformArgs::FREE_TRANSFORM; break; case WarpTransformMode: mode = ToolTransformArgs::WARP; break; case CageTransformMode: mode = ToolTransformArgs::CAGE; break; case LiquifyTransformMode: mode = ToolTransformArgs::LIQUIFY; break; case PerspectiveTransformMode: mode = ToolTransformArgs::PERSPECTIVE_4POINT; break; default: KIS_ASSERT_RECOVER_NOOP(0 && "unexpected transform mode"); } if( mode != m_currentArgs.mode() ) { if( newMode == FreeTransformMode ) { m_optionsWidget->slotSetFreeTransformModeButtonClicked( true ); } else if( newMode == WarpTransformMode ) { m_optionsWidget->slotSetWarpModeButtonClicked( true ); } else if( newMode == CageTransformMode ) { m_optionsWidget->slotSetCageModeButtonClicked( true ); } else if( newMode == LiquifyTransformMode ) { m_optionsWidget->slotSetLiquifyModeButtonClicked( true ); } else if( newMode == PerspectiveTransformMode ) { m_optionsWidget->slotSetPerspectiveModeButtonClicked( true ); } emit transformModeChanged(); } } void KisToolTransform::setRotateX( double rotation ) { m_currentArgs.setAX( normalizeAngle(rotation) ); } void KisToolTransform::setRotateY( double rotation ) { m_currentArgs.setAY( normalizeAngle(rotation) ); } void KisToolTransform::setRotateZ( double rotation ) { m_currentArgs.setAZ( normalizeAngle(rotation) ); } void KisToolTransform::setWarpType( KisToolTransform::WarpType type ) { switch( type ) { case RigidWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::RIGID_TRANSFORM); break; case AffineWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::AFFINE_TRANSFORM); break; case SimilitudeWarpType: m_currentArgs.setWarpType(KisWarpTransformWorker::SIMILITUDE_TRANSFORM); break; default: break; } } void KisToolTransform::setWarpFlexibility( double flexibility ) { m_currentArgs.setAlpha( flexibility ); } void KisToolTransform::setWarpPointDensity( int density ) { m_optionsWidget->slotSetWarpDensity(density); } void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode) { m_currentArgs = KisTransformUtils::resetArgsForMode(mode, m_currentArgs.filterId(), m_transaction); initGuiAfterTransformMode(); } void KisToolTransform::initGuiAfterTransformMode() { currentStrategy()->externalConfigChanged(); outlineChanged(); updateOptionWidget(); updateApplyResetAvailability(); setFunctionalCursor(); } void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice) { QImage origImg; m_selectedPortionCache = previewDevice; QTransform thumbToImageTransform; const int maxSize = 2000; QRect srcRect(m_transaction.originalRect().toAlignedRect()); int x, y, w, h; srcRect.getRect(&x, &y, &w, &h); if (m_selectedPortionCache) { if (w > maxSize || h > maxSize) { qreal scale = qreal(maxSize) / (w > h ? w : h); QTransform scaleTransform = QTransform::fromScale(scale, scale); QRect thumbRect = scaleTransform.mapRect(m_transaction.originalRect()).toAlignedRect(); origImg = m_selectedPortionCache-> createThumbnail(thumbRect.width(), thumbRect.height(), srcRect, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = scaleTransform.inverted(); } else { origImg = m_selectedPortionCache->convertToQImage(0, x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); thumbToImageTransform = QTransform(); } } // init both strokes since the thumbnail is initialized only once // during the stroke m_freeStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_perspectiveStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_warpStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_cageStrategy->setThumbnailImage(origImg, thumbToImageTransform); m_liquifyStrategy->setThumbnailImage(origImg, thumbToImageTransform); } void KisToolTransform::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); if (currentNode()) { m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); } startStroke(ToolTransformArgs::FREE_TRANSFORM, false); } void KisToolTransform::deactivate() { endStroke(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisToolTransform::requestUndoDuringStroke() { if (!m_strokeId || !m_transaction.rootNode()) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolTransform::requestStrokeEnd() { endStroke(); } void KisToolTransform::requestStrokeCancellation() { if (!m_transaction.rootNode() || m_currentArgs.isIdentity()) { cancelStroke(); } else { slotResetTransform(); } } void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode, bool forceReset) { Q_ASSERT(!m_strokeId); // set up and null checks before we do anything KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), this->canvas()->resourceManager()); KisNodeSP currentNode = resources->currentNode(); if (!currentNode || !currentNode->isEditable()) return; m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); m_currentArgs = ToolTransformArgs(); // some layer types cannot be transformed. Give a message and return if a user tries it if (currentNode->inherits("KisColorizeMask") || currentNode->inherits("KisFileLayer") || currentNode->inherits("KisCloneLayer")) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", - "Layer type cannot use the transform tool"), + (currentNode->inherits("KisColorizeMask") ? + "Layer type cannot use the transform tool": "Use transform mask instead")), koIcon("object-locked"), 4000, KisFloatingMessage::High); return; } if (m_optionsWidget) { m_workRecursively = m_optionsWidget->workRecursively() || !currentNode->paintDevice(); } KisSelectionSP selection = resources->activeSelection(); /** * When working with transform mask, selections are not * taken into account. */ if (selection && dynamic_cast(currentNode.data())) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Selections are not used when editing transform masks "), QIcon(), 4000, KisFloatingMessage::Low); selection = 0; } TransformStrokeStrategy *strategy = new TransformStrokeStrategy(mode, m_workRecursively, m_currentArgs.filterId(), forceReset, currentNode, selection, image().data(), image().data()); connect(strategy, SIGNAL(sigPreviewDeviceReady(KisPaintDeviceSP)), SLOT(slotPreviewDeviceGenerated(KisPaintDeviceSP))); connect(strategy, SIGNAL(sigTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*)), SLOT(slotTransactionGenerated(TransformTransactionProperties, ToolTransformArgs, void*))); // save unique identifier of the stroke so we could // recognize it when sigTransactionGenerated() is // received (theoretically, the user can start two // strokes at the same time, if he is quick enough) m_strokeStrategyCookie = strategy; m_strokeId = image()->startStroke(strategy); KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty()); slotPreviewDeviceGenerated(0); } void KisToolTransform::endStroke() { if (!m_strokeId) return; if (m_transaction.rootNode() && !m_currentArgs.isIdentity()) { image()->addJob(m_strokeId, new TransformStrokeStrategy::TransformAllData(m_currentArgs)); } image()->endStroke(m_strokeId); m_strokeStrategyCookie = 0; m_strokeId.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::slotTransactionGenerated(TransformTransactionProperties transaction, ToolTransformArgs args, void *strokeStrategyCookie) { if (!m_strokeId || strokeStrategyCookie != m_strokeStrategyCookie) return; if (transaction.transformedNodes().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); cancelStroke(); return; } m_transaction = transaction; m_currentArgs = args; m_transaction.setCurrentConfigLocation(&m_currentArgs); KIS_SAFE_ASSERT_RECOVER_NOOP(m_changesTracker.isEmpty()); commitChanges(); initGuiAfterTransformMode(); if (m_transaction.hasInvisibleNodes()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Invisible sublayers will also be transformed. Lock layers if you do not want them to be transformed "), QIcon(), 4000, KisFloatingMessage::Low); } } void KisToolTransform::slotPreviewDeviceGenerated(KisPaintDeviceSP device) { if (device && device->exactBounds().isEmpty()) { KisCanvas2 *kisCanvas = dynamic_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in transformation tool", "Cannot transform empty layer "), QIcon(), 1000, KisFloatingMessage::Medium); cancelStroke(); } else { initThumbnailImage(device); initGuiAfterTransformMode(); } } void KisToolTransform::cancelStroke() { if (!m_strokeId) return; image()->cancelStroke(m_strokeId); m_strokeStrategyCookie = 0; m_strokeId.clear(); m_changesTracker.reset(); m_transaction = TransformTransactionProperties(QRectF(), &m_currentArgs, KisNodeSP(), {}); outlineChanged(); } void KisToolTransform::commitChanges() { if (!m_strokeId || !m_transaction.rootNode()) return; m_changesTracker.commitConfig(toQShared(m_currentArgs.clone())); } void KisToolTransform::slotTrackerChangedConfig(KisToolChangesTrackerDataSP status) { const ToolTransformArgs *newArgs = dynamic_cast(status.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newArgs); *m_transaction.currentConfig() = *newArgs; slotUiChangedConfig(); updateOptionWidget(); } QList KisToolTransform::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } QWidget* KisToolTransform::createOptionWidget() { if (!m_canvas) return 0; m_optionsWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, m_workRecursively, 0); Q_CHECK_PTR(m_optionsWidget); m_optionsWidget->setObjectName(toolId() + " option widget"); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); connect(m_optionsWidget, SIGNAL(sigConfigChanged()), this, SLOT(slotUiChangedConfig())); connect(m_optionsWidget, SIGNAL(sigApplyTransform()), this, SLOT(slotApplyTransform())); connect(m_optionsWidget, SIGNAL(sigResetTransform()), this, SLOT(slotResetTransform())); connect(m_optionsWidget, SIGNAL(sigRestartTransform()), this, SLOT(slotRestartTransform())); connect(m_optionsWidget, SIGNAL(sigEditingFinished()), this, SLOT(slotEditingFinished())); connect(mirrorHorizontalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipX())); connect(mirrorVericalAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotFlipY())); connect(rotateNinteyCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCW())); connect(rotateNinteyCCWAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(slotRotateCCW())); connect(warpAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToWarpType())); connect(perspectiveAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToPerspectiveType())); connect(freeTransformAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToFreeTransformType())); connect(liquifyAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToLiquifyType())); connect(cageAction, SIGNAL(triggered(bool)), this, SLOT(slotUpdateToCageType())); connect(applyTransformation, SIGNAL(triggered(bool)), this, SLOT(slotApplyTransform())); connect(resetTransformation, SIGNAL(triggered(bool)), this, SLOT(slotResetTransform())); updateOptionWidget(); return m_optionsWidget; } void KisToolTransform::updateOptionWidget() { if (!m_optionsWidget) return; if (!currentNode()) { m_optionsWidget->setEnabled(false); return; } else { m_optionsWidget->setEnabled(true); m_optionsWidget->updateConfig(m_currentArgs); } } void KisToolTransform::updateApplyResetAvailability() { if (m_optionsWidget) { m_optionsWidget->setApplyResetDisabled(m_currentArgs.isIdentity()); } } void KisToolTransform::slotUiChangedConfig() { if (mode() == KisTool::PAINT_MODE) return; currentStrategy()->externalConfigChanged(); if (m_currentArgs.mode() == ToolTransformArgs::LIQUIFY) { m_currentArgs.saveLiquifyTransformMode(); } outlineChanged(); updateApplyResetAvailability(); } void KisToolTransform::slotApplyTransform() { QApplication::setOverrideCursor(KisCursor::waitCursor()); endStroke(); QApplication::restoreOverrideCursor(); } void KisToolTransform::slotResetTransform() { if (!m_strokeId || !m_transaction.rootNode()) return; if (m_currentArgs.continuedTransform()) { ToolTransformArgs::TransformMode savedMode = m_currentArgs.mode(); /** * Our reset transform button can be used for two purposes: * * 1) Reset current transform to the initial one, which was * loaded from the previous user action. * * 2) Reset transform frame to infinity when the frame is unchanged */ const bool transformDiffers = !m_currentArgs.continuedTransform()->isSameMode(m_currentArgs); if (transformDiffers && m_currentArgs.continuedTransform()->mode() == savedMode) { m_currentArgs.restoreContinuedState(); initGuiAfterTransformMode(); slotEditingFinished(); } else { KisNodeSP root = m_transaction.rootNode() ? m_transaction.rootNode() : image()->root(); cancelStroke(); startStroke(savedMode, true); KIS_ASSERT_RECOVER_NOOP(!m_currentArgs.continuedTransform()); } } else { initTransformMode(m_currentArgs.mode()); slotEditingFinished(); } } void KisToolTransform::slotRestartTransform() { if (!m_strokeId || !m_transaction.rootNode()) return; KisNodeSP root = m_transaction.rootNode(); KIS_ASSERT_RECOVER_RETURN(root); // the stroke is guaranteed to be started by an 'if' above ToolTransformArgs savedArgs(m_currentArgs); cancelStroke(); startStroke(savedArgs.mode(), true); } void KisToolTransform::slotEditingFinished() { commitChanges(); } void KisToolTransform::slotUpdateToWarpType() { setTransformMode(KisToolTransform::TransformToolMode::WarpTransformMode); } void KisToolTransform::slotUpdateToPerspectiveType() { setTransformMode(KisToolTransform::TransformToolMode::PerspectiveTransformMode); } void KisToolTransform::slotUpdateToFreeTransformType() { setTransformMode(KisToolTransform::TransformToolMode::FreeTransformMode); } void KisToolTransform::slotUpdateToLiquifyType() { setTransformMode(KisToolTransform::TransformToolMode::LiquifyTransformMode); } void KisToolTransform::slotUpdateToCageType() { setTransformMode(KisToolTransform::TransformToolMode::CageTransformMode); } void KisToolTransform::setShearY(double shear) { m_optionsWidget->slotSetShearY(shear); } void KisToolTransform::setShearX(double shear) { m_optionsWidget->slotSetShearX(shear); } void KisToolTransform::setScaleY(double scale) { m_optionsWidget->slotSetScaleY(scale); } void KisToolTransform::setScaleX(double scale) { m_optionsWidget->slotSetScaleX(scale); } void KisToolTransform::setTranslateY(double translation) { m_optionsWidget->slotSetTranslateY(translation); } void KisToolTransform::setTranslateX(double translation) { m_optionsWidget->slotSetTranslateX(translation); } diff --git a/plugins/tools/tool_transform2/kis_transform_utils.cpp b/plugins/tools/tool_transform2/kis_transform_utils.cpp index 1fbbd525be..8a30d79d36 100644 --- a/plugins/tools/tool_transform2/kis_transform_utils.cpp +++ b/plugins/tools/tool_transform2/kis_transform_utils.cpp @@ -1,490 +1,490 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_transform_utils.h" #include #include #include #include "tool_transform_args.h" #include "kis_paint_device.h" #include "kis_algebra_2d.h" #include "transform_transaction_properties.h" struct TransformTransactionPropertiesRegistrar { TransformTransactionPropertiesRegistrar() { qRegisterMetaType("TransformTransactionProperties"); } }; static TransformTransactionPropertiesRegistrar __registrar1; struct ToolTransformArgsRegistrar { ToolTransformArgsRegistrar() { qRegisterMetaType("ToolTransformArgs"); } }; static ToolTransformArgsRegistrar __registrar2; struct QPainterPathRegistrar { QPainterPathRegistrar() { qRegisterMetaType("QPainterPath"); } }; static QPainterPathRegistrar __registrar3; const int KisTransformUtils::rotationHandleVisualRadius = 12; const int KisTransformUtils::rotationHandleRadius = 8; const int KisTransformUtils::handleVisualRadius = 12; const int KisTransformUtils::handleRadius = 8; QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter) { return converter->imageToDocumentTransform() * converter->documentToFlakeTransform(); } qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter) { QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius)); return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y(); } qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) { return KoUnit::approxTransformScale(t); } qreal KisTransformUtils::scaleFromPerspectiveMatrixX(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(1.0, 0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::scaleFromPerspectiveMatrixY(const QTransform &t, const QPointF &basePt) { const QPointF pt = basePt + QPointF(0, 1.0); return kisDistance(t.map(pt), t.map(basePt)); } qreal KisTransformUtils::effectiveSize(const QRectF &rc) { return 0.5 * (rc.width() + rc.height()); } bool KisTransformUtils::thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect) { return KisAlgebra2D::minDimension(resultThumbTransform.mapRect(originalImageRect)) < 32; } QRectF handleRectImpl(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint, qreal *dOutX, qreal *dOutY) { const qreal handlesExtraScaleX = KisTransformUtils::scaleFromPerspectiveMatrixX(t, basePoint); const qreal handlesExtraScaleY = KisTransformUtils::scaleFromPerspectiveMatrixY(t, basePoint); const qreal maxD = 0.2 * KisTransformUtils::effectiveSize(limitingRect); const qreal dX = qMin(maxD, radius / handlesExtraScaleX); const qreal dY = qMin(maxD, radius / handlesExtraScaleY); QRectF handleRect(-0.5 * dX, -0.5 * dY, dX, dY); if (dOutX) { *dOutX = dX; } if (dOutY) { *dOutY = dY; } return handleRect; } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, qreal *dOutX, qreal *dOutY) { return handleRectImpl(radius, t, limitingRect, limitingRect.center(), dOutX, dOutY); } QRectF KisTransformUtils::handleRect(qreal radius, const QTransform &t, const QRectF &limitingRect, const QPointF &basePoint) { return handleRectImpl(radius, t, limitingRect, basePoint, 0, 0); } QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r) { QPointF center = r.center(); QPointF t = p - center; r.translate(- center); if (t.y() != 0) { if (t.x() != 0) { double slope = t.y() / t.x(); if (t.x() < r.left()) { t.setY(r.left() * slope); t.setX(r.left()); } else if (t.x() > r.right()) { t.setY(r.right() * slope); t.setX(r.right()); } if (t.y() < r.top()) { t.setX(r.top() / slope); t.setY(r.top()); } else if (t.y() > r.bottom()) { t.setX(r.bottom() / slope); t.setY(r.bottom()); } } else { if (t.y() < r.top()) t.setY(r.top()); else if (t.y() > r.bottom()) t.setY(r.bottom()); } } else { if (t.x() < r.left()) t.setX(r.left()); else if (t.x() > r.right()) t.setX(r.right()); } t += center; return t; } KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args) { TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y()); SC = QTransform::fromScale(args.scaleX(), args.scaleY()); S.shear(0, args.shearY()); S.shear(args.shearX(), 0); if (args.mode() == ToolTransformArgs::FREE_TRANSFORM) { P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0)); P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0)); P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1)); projectedP = P.toTransform(args.cameraPos().z()); } else if (args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { projectedP = args.flattenedPerspectiveTransform(); P = QMatrix4x4(projectedP); } QPointF translation = args.transformedCenter(); T = QTransform::fromTranslate(translation.x(), translation.y()); } QTransform KisTransformUtils::MatricesPack::finalTransform() const { return TS * SC * S * projectedP * T; } bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, const MatricesPack &m) { bool imageTooBig = false; QMatrix4x4 unprojectedMatrix = QMatrix4x4(m.T) * m.P * QMatrix4x4(m.TS * m.SC * m.S); QVector points; points << bounds.topLeft(); points << bounds.topRight(); points << bounds.bottomRight(); points << bounds.bottomLeft(); Q_FOREACH (const QPointF &pt, points) { QVector4D v(pt.x(), pt.y(), 0, 1); v = unprojectedMatrix * v; qreal z = v.z() / v.w(); imageTooBig = z > 1024.0; if (imageTooBig) { break; } } return imageTooBig; } #include #include #include #include #include KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs &config, KisPaintDeviceSP device, KoUpdaterPtr updater, QVector3D *transformedCenter /* OUT */) { { KisTransformWorker t(0, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), 0, // set X and Y translation 0, // to null for calculation 0, config.filter()); *transformedCenter = QVector3D(t.transform().map(config.originalCenter())); } QPointF translation = config.transformedCenter() - (*transformedCenter).toPointF(); KisTransformWorker transformWorker(device, config.scaleX(), config.scaleY(), config.shearX(), config.shearY(), config.originalCenter().x(), config.originalCenter().y(), config.aZ(), (int)(translation.x()), (int)(translation.y()), updater, config.filter()); return transformWorker; } void KisTransformUtils::transformDevice(const ToolTransformArgs &config, KisPaintDeviceSP device, KisProcessingVisitor::ProgressHelper *helper) { if (config.mode() == ToolTransformArgs::WARP) { KoUpdaterPtr updater = helper->updater(); KisWarpTransformWorker worker(config.warpType(), device, config.origPoints(), config.transfPoints(), config.alpha(), updater); worker.run(); } else if (config.mode() == ToolTransformArgs::CAGE) { KoUpdaterPtr updater = helper->updater(); KisCageTransformWorker worker(device, config.origPoints(), updater, config.pixelPrecision()); worker.prepareTransform(); worker.setTransformedCage(config.transfPoints()); worker.run(); - } else if (config.mode() == ToolTransformArgs::LIQUIFY) { + } else if (config.mode() == ToolTransformArgs::LIQUIFY && config.liquifyWorker()) { KoUpdaterPtr updater = helper->updater(); //FIXME: Q_UNUSED(updater); config.liquifyWorker()->run(device); } else { QVector3D transformedCenter; KoUpdaterPtr updater1 = helper->updater(); KoUpdaterPtr updater2 = helper->updater(); KisTransformWorker transformWorker = createTransformWorker(config, device, updater1, &transformedCenter); transformWorker.run(); if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) { KisPerspectiveTransformWorker perspectiveWorker(device, config.transformedCenter(), config.aX(), config.aY(), config.cameraPos().z(), updater2); perspectiveWorker.run(); } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) { QTransform T = QTransform::fromTranslate(config.transformedCenter().x(), config.transformedCenter().y()); KisPerspectiveTransformWorker perspectiveWorker(device, T.inverted() * config.flattenedPerspectiveTransform() * T, updater2); perspectiveWorker.run(); } } } QRect KisTransformUtils::needRect(const ToolTransformArgs &config, const QRect &rc, const QRect &srcBounds) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, config.pixelPrecision()); worker.setTransformedCage(config.transfPoints()); result = worker.approxNeedRect(rc, srcBounds); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxNeedRect(rc, srcBounds) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } QRect KisTransformUtils::changeRect(const ToolTransformArgs &config, const QRect &rc) { QRect result = rc; if (config.mode() == ToolTransformArgs::WARP) { KisWarpTransformWorker worker(config.warpType(), 0, config.origPoints(), config.transfPoints(), config.alpha(), 0); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::CAGE) { KisCageTransformWorker worker(0, config.origPoints(), 0, config.pixelPrecision()); worker.setTransformedCage(config.transfPoints()); result = worker.approxChangeRect(rc); } else if (config.mode() == ToolTransformArgs::LIQUIFY) { result = config.liquifyWorker() ? config.liquifyWorker()->approxChangeRect(rc) : rc; } else { KIS_ASSERT_RECOVER_NOOP(0 && "this works for non-affine transformations only!"); } return result; } KisTransformUtils::AnchorHolder::AnchorHolder(bool enabled, ToolTransformArgs *config) : m_enabled(enabled), m_config(config) { if (!m_enabled) return; m_staticPoint = m_config->originalCenter() + m_config->rotationCenterOffset(); const KisTransformUtils::MatricesPack m(*m_config); m_oldStaticPointInView = m.finalTransform().map(m_staticPoint); } KisTransformUtils::AnchorHolder::~AnchorHolder() { if (!m_enabled) return; const KisTransformUtils::MatricesPack m(*m_config); const QPointF newStaticPointInView = m.finalTransform().map(m_staticPoint); const QPointF diff = m_oldStaticPointInView - newStaticPointInView; m_config->setTransformedCenter(m_config->transformedCenter() + diff); } void KisTransformUtils::setDefaultWarpPoints(int pointsPerLine, const TransformTransactionProperties *transaction, ToolTransformArgs *config) { static const int DEFAULT_POINTS_PER_LINE = 3; if (pointsPerLine < 0) { pointsPerLine = DEFAULT_POINTS_PER_LINE; } int nbPoints = pointsPerLine * pointsPerLine; QVector origPoints(nbPoints); QVector transfPoints(nbPoints); qreal gridSpaceX, gridSpaceY; if (nbPoints == 1) { //there is actually no grid origPoints[0] = transaction->originalCenterGeometric(); transfPoints[0] = transaction->originalCenterGeometric(); } else if (nbPoints > 1) { gridSpaceX = transaction->originalRect().width() / (pointsPerLine - 1); gridSpaceY = transaction->originalRect().height() / (pointsPerLine - 1); double y = transaction->originalRect().top(); for (int i = 0; i < pointsPerLine; ++i) { double x = transaction->originalRect().left(); for (int j = 0 ; j < pointsPerLine; ++j) { origPoints[i * pointsPerLine + j] = QPointF(x, y); transfPoints[i * pointsPerLine + j] = QPointF(x, y); x += gridSpaceX; } y += gridSpaceY; } } config->setDefaultPoints(nbPoints > 0); config->setPoints(origPoints, transfPoints); } ToolTransformArgs KisTransformUtils::resetArgsForMode(ToolTransformArgs::TransformMode mode, const QString &filterId, const TransformTransactionProperties &transaction) { ToolTransformArgs args; args.setOriginalCenter(transaction.originalCenterGeometric()); args.setTransformedCenter(transaction.originalCenterGeometric()); args.setFilterId(filterId); if (mode == ToolTransformArgs::FREE_TRANSFORM) { args.setMode(ToolTransformArgs::FREE_TRANSFORM); } else if (mode == ToolTransformArgs::WARP) { args.setMode(ToolTransformArgs::WARP); KisTransformUtils::setDefaultWarpPoints(-1, &transaction, &args); args.setEditingTransformPoints(false); } else if (mode == ToolTransformArgs::CAGE) { args.setMode(ToolTransformArgs::CAGE); args.setEditingTransformPoints(true); } else if (mode == ToolTransformArgs::LIQUIFY) { args.setMode(ToolTransformArgs::LIQUIFY); const QRect srcRect = transaction.originalRect().toAlignedRect(); if (!srcRect.isEmpty()) { args.initLiquifyTransformMode(transaction.originalRect().toAlignedRect()); } } else if (mode == ToolTransformArgs::PERSPECTIVE_4POINT) { args.setMode(ToolTransformArgs::PERSPECTIVE_4POINT); } return args; } diff --git a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp index 42d405feae..b5e26db9b5 100644 --- a/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp +++ b/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp @@ -1,717 +1,711 @@ /* * Copyright (c) 2013 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "transform_stroke_strategy.h" #include #include "kundo2commandextradata.h" #include "kis_node_progress_proxy.h" #include #include #include #include #include #include #include #include #include "kis_transform_mask_adapter.h" #include "kis_transform_utils.h" #include "kis_abstract_projection_plane.h" #include "kis_recalculate_transform_mask_job.h" #include "kis_projection_leaf.h" #include "kis_modify_transform_mask_command.h" #include "kis_sequential_iterator.h" #include "kis_selection_mask.h" #include "kis_image_config.h" #include "kis_layer_utils.h" #include #include #include "transform_transaction_properties.h" #include "krita_container_utils.h" #include "commands_new/kis_saved_commands.h" #include "kis_command_ids.h" #include "KisRunnableStrokeJobUtils.h" #include "commands_new/KisHoldUIUpdatesCommand.h" #include "KisDecoratedNodeInterface.h" TransformStrokeStrategy::TransformStrokeStrategy(ToolTransformArgs::TransformMode mode, bool workRecursively, const QString &filterId, bool forceReset, KisNodeSP rootNode, KisSelectionSP selection, KisStrokeUndoFacade *undoFacade, KisUpdatesFacade *updatesFacade) : KisStrokeStrategyUndoCommandBased(kundo2_i18n("Transform"), false, undoFacade), m_updatesFacade(updatesFacade), m_mode(mode), m_workRecursively(workRecursively), m_filterId(filterId), m_forceReset(forceReset), m_selection(selection) { KIS_SAFE_ASSERT_RECOVER_NOOP(!selection || !dynamic_cast(rootNode.data())); m_rootNode = rootNode; setMacroId(KisCommandUtils::TransformToolId); } TransformStrokeStrategy::~TransformStrokeStrategy() { } KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev) { KisPaintDeviceSP cache; if (m_selection) { QRect srcRect = m_selection->selectedExactRect(); cache = dev->createCompositionSourceDevice(); KisPainter gc(cache); gc.setSelection(m_selection); gc.bitBlt(srcRect.topLeft(), dev, srcRect); } else { cache = dev->createCompositionSourceDevice(dev); } return cache; } bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); return m_devicesCacheHash.contains(src.data()); } void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache) { QMutexLocker l(&m_devicesCacheMutex); m_devicesCacheHash.insert(src.data(), cache); } KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src) { QMutexLocker l(&m_devicesCacheMutex); KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data()); if (!cache) { warnKrita << "WARNING: Transform Stroke: the device is absent in cache!"; } return cache; } bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const { return m_selection && (device == m_selection->pixelSelection().data() || device == m_selection->projection().data()); } void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data) { TransformData *td = dynamic_cast(data); ClearSelectionData *csd = dynamic_cast(data); PreparePreviewData *ppd = dynamic_cast(data); TransformAllData *runAllData = dynamic_cast(data); if (runAllData) { // here we only save the passed args, actual // transformation will be performed during // finish job m_savedTransformArgs = runAllData->config; } else if (ppd) { KisNodeSP rootNode = m_rootNode; KisNodeList processedNodes = m_processedNodes; KisPaintDeviceSP previewDevice; if (rootNode->childCount() || !rootNode->paintDevice()) { if (KisTransformMask* tmask = dynamic_cast(rootNode.data())) { previewDevice = createDeviceCache(tmask->buildPreviewDevice()); KIS_SAFE_ASSERT_RECOVER(!m_selection) { m_selection = 0; } } else if (KisGroupLayer *group = dynamic_cast(rootNode.data())) { const QRect bounds = group->image()->bounds(); KisImageSP clonedImage = new KisImage(0, bounds.width(), bounds.height(), group->colorSpace(), "transformed_image"); KisGroupLayerSP clonedGroup = dynamic_cast(group->clone().data()); // In case the group is pass-through, it needs to be disabled for the preview, // otherwise it will crash (no parent for a preview leaf). // Also it needs to be done before setting the root layer for clonedImage. // Result: preview for pass-through group is the same as for standard group // (i.e. filter layers in the group won't affect the layer stack for a moment). clonedGroup->setPassThroughMode(false); clonedImage->setRootLayer(clonedGroup); QQueue linearizedSrcNodes; KisLayerUtils::recursiveApplyNodes(rootNode, [&linearizedSrcNodes] (KisNodeSP node) { linearizedSrcNodes.enqueue(node); }); KisLayerUtils::recursiveApplyNodes(KisNodeSP(clonedGroup), [&linearizedSrcNodes, processedNodes] (KisNodeSP node) { KisNodeSP srcNode = linearizedSrcNodes.dequeue(); if (!processedNodes.contains(srcNode)) { node->setVisible(false); } }); clonedImage->refreshGraph(); KisLayerUtils::refreshHiddenAreaAsync(clonedImage, clonedGroup, clonedImage->bounds()); KisLayerUtils::forceAllDelayedNodesUpdate(clonedGroup); clonedImage->waitForDone(); previewDevice = createDeviceCache(clonedImage->projection()); previewDevice->setDefaultBounds(group->projection()->defaultBounds()); // we delete the cloned image in GUI thread to ensure // no signals are still pending makeKisDeleteLaterWrapper(clonedImage)->deleteLater(); } else { rootNode->projectionLeaf()->explicitlyRegeneratePassThroughProjection(); previewDevice = createDeviceCache(rootNode->projection()); } } else { KisPaintDeviceSP cacheDevice = createDeviceCache(rootNode->paintDevice()); if (dynamic_cast(rootNode.data())) { KIS_SAFE_ASSERT_RECOVER (cacheDevice->colorSpace()->colorModelId() == GrayAColorModelID && cacheDevice->colorSpace()->colorDepthId() == Integer8BitsColorDepthID) { cacheDevice->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id())); } previewDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); const QRect srcRect = cacheDevice->exactBounds(); KisSequentialConstIterator srcIt(cacheDevice, srcRect); KisSequentialIterator dstIt(previewDevice, srcRect); const int pixelSize = previewDevice->colorSpace()->pixelSize(); KisImageConfig cfg(true); KoColor pixel(cfg.selectionOverlayMaskColor(), previewDevice->colorSpace()); const qreal coeff = 1.0 / 255.0; const qreal baseOpacity = 0.5; while (srcIt.nextPixel() && dstIt.nextPixel()) { qreal gray = srcIt.rawDataConst()[0]; qreal alpha = srcIt.rawDataConst()[1]; pixel.setOpacity(quint8(gray * alpha * baseOpacity * coeff)); memcpy(dstIt.rawData(), pixel.data(), pixelSize); } } else { previewDevice = cacheDevice; } putDeviceCache(rootNode->paintDevice(), cacheDevice); } emit sigPreviewDeviceReady(previewDevice); - } else if(td) { + } + else if (td) { if (td->destination == TransformData::PAINT_DEVICE) { QRect oldExtent = td->node->extent(); KisPaintDeviceSP device = td->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { KisPaintDeviceSP cachedPortion = getDeviceCache(device); Q_ASSERT(cachedPortion); KisTransaction transaction(device); KisProcessingVisitor::ProgressHelper helper(td->node); transformAndMergeDevice(td->config, cachedPortion, device, &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); td->node->setDirty(oldExtent | td->node->extent()); } else if (KisExternalLayer *extLayer = dynamic_cast(td->node.data())) { if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM || (td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT && extLayer->supportsPerspectiveTransform())) { QVector3D transformedCenter; KisTransformWorker w = KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter); QTransform t = w.transform(); runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (KisTransformMask *transformMask = dynamic_cast(td->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisTransformMaskAdapter(td->config)))), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (m_selection) { /** * We use usual transaction here, because we cannot calsulate * transformation for perspective and warp workers. */ KisTransaction transaction(m_selection->pixelSelection()); KisProcessingVisitor::ProgressHelper helper(td->node); KisTransformUtils::transformDevice(td->config, m_selection->pixelSelection(), &helper); runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::CONCURRENT, KisStrokeJobData::NORMAL); } } else if (csd) { KisPaintDeviceSP device = csd->node->paintDevice(); if (device && !checkBelongsToSelection(device)) { if (!haveDeviceInCache(device)) { putDeviceCache(device, createDeviceCache(device)); } clearSelection(device); /** * Selection masks might have an overlay enabled, we should disable that */ if (KisSelectionMask *mask = dynamic_cast(csd->node.data())) { KisSelectionSP selection = mask->selection(); if (selection) { selection->setVisible(false); m_deactivatedSelections.append(selection); mask->setDirty(); } } } else if (KisExternalLayer *externalLayer = dynamic_cast(csd->node.data())) { externalLayer->projectionLeaf()->setTemporaryHiddenFromRendering(true); externalLayer->setDirty(); m_hiddenProjectionLeaves.append(csd->node); } else if (KisTransformMask *transformMask = dynamic_cast(csd->node.data())) { runAndSaveCommand(KUndo2CommandSP( new KisModifyTransformMaskCommand(transformMask, KisTransformMaskParamsInterfaceSP( new KisDumbTransformMaskParams(true)))), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } } else { KisStrokeStrategyUndoCommandBased::doStrokeCallback(data); } } void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device) { KisTransaction transaction(device); if (m_selection) { device->clearSelection(m_selection); } else { QRect oldExtent = device->extent(); device->clear(); device->setDirty(oldExtent); } runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()), KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::NORMAL); } void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config, KisPaintDeviceSP src, KisPaintDeviceSP dst, KisProcessingVisitor::ProgressHelper *helper) { KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0; KisTransformUtils::transformDevice(config, src, helper); if (src != dst) { QRect mergeRect = src->extent(); KisPainter painter(dst); painter.setProgress(mergeUpdater); painter.bitBlt(mergeRect.topLeft(), src, mergeRect); painter.end(); } } struct TransformExtraData : public KUndo2CommandExtraData { ToolTransformArgs savedTransformArgs; KisNodeSP rootNode; KisNodeList transformedNodes; KUndo2CommandExtraData* clone() const override { return new TransformExtraData(*this); } }; void TransformStrokeStrategy::postProcessToplevelCommand(KUndo2Command *command) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_savedTransformArgs); TransformExtraData *data = new TransformExtraData(); data->savedTransformArgs = *m_savedTransformArgs; data->rootNode = m_rootNode; data->transformedNodes = m_processedNodes; command->setExtraData(data); KisSavedMacroCommand *macroCommand = dynamic_cast(command); KIS_SAFE_ASSERT_RECOVER_NOOP(macroCommand); if (m_overriddenCommand && macroCommand) { macroCommand->setOverrideInfo(m_overriddenCommand, m_skippedWhileMergeCommands); } KisStrokeStrategyUndoCommandBased::postProcessToplevelCommand(command); } bool TransformStrokeStrategy::fetchArgsFromCommand(const KUndo2Command *command, ToolTransformArgs *args, KisNodeSP *rootNode, KisNodeList *transformedNodes) { const TransformExtraData *data = dynamic_cast(command->extraData()); if (data) { *args = data->savedTransformArgs; *rootNode = data->rootNode; *transformedNodes = data->transformedNodes; } return bool(data); } QList TransformStrokeStrategy::fetchNodesList(ToolTransformArgs::TransformMode mode, KisNodeSP root, bool recursive) { QList result; auto fetchFunc = [&result, mode, root] (KisNodeSP node) { if (node->isEditable(node == root) && (!node->inherits("KisShapeLayer") || mode == ToolTransformArgs::FREE_TRANSFORM) && !node->inherits("KisFileLayer") && (!node->inherits("KisTransformMask") || node == root)) { result << node; } }; if (recursive) { KisLayerUtils::recursiveApplyNodes(root, fetchFunc); } else { fetchFunc(root); } return result; } bool TransformStrokeStrategy::tryInitArgsFromNode(KisNodeSP node, ToolTransformArgs *args) { bool result = false; if (KisTransformMaskSP mask = dynamic_cast(node.data())) { KisTransformMaskParamsInterfaceSP savedParams = mask->transformParams(); KisTransformMaskAdapter *adapter = dynamic_cast(savedParams.data()); if (adapter) { *args = adapter->transformArgs(); result = true; } } return result; } bool TransformStrokeStrategy::tryFetchArgsFromCommandAndUndo(ToolTransformArgs *outArgs, ToolTransformArgs::TransformMode mode, KisNodeSP currentNode, KisNodeList selectedNodes, QVector *undoJobs) { bool result = false; const KUndo2Command *lastCommand = undoFacade()->lastExecutedCommand(); KisNodeSP oldRootNode; KisNodeList oldTransformedNodes; ToolTransformArgs args; if (lastCommand && TransformStrokeStrategy::fetchArgsFromCommand(lastCommand, &args, &oldRootNode, &oldTransformedNodes) && args.mode() == mode && oldRootNode == currentNode) { if (KritaUtils::compareListsUnordered(oldTransformedNodes, selectedNodes)) { args.saveContinuedState(); *outArgs = args; const KisSavedMacroCommand *command = dynamic_cast(lastCommand); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(command, false); // the jobs are fetched as !shouldGoToHistory, // so there is no need to put them into // m_skippedWhileMergeCommands command->getCommandExecutionJobs(undoJobs, true, false); m_overriddenCommand = command; result = true; } } return result; } void TransformStrokeStrategy::initStrokeCallback() { KisStrokeStrategyUndoCommandBased::initStrokeCallback(); if (m_selection) { m_selection->setVisible(false); m_deactivatedSelections.append(m_selection); } KisSelectionMaskSP overlaySelectionMask = dynamic_cast(m_rootNode->graphListener()->graphOverlayNode()); if (overlaySelectionMask) { overlaySelectionMask->setDecorationsVisible(false); m_deactivatedOverlaySelectionMask = overlaySelectionMask; } ToolTransformArgs initialTransformArgs; m_processedNodes = fetchNodesList(m_mode, m_rootNode, m_workRecursively); bool argsAreInitialized = false; QVector lastCommandUndoJobs; if (!m_forceReset && tryFetchArgsFromCommandAndUndo(&initialTransformArgs, m_mode, m_rootNode, m_processedNodes, &lastCommandUndoJobs)) { argsAreInitialized = true; } else if (!m_forceReset && tryInitArgsFromNode(m_rootNode, &initialTransformArgs)) { argsAreInitialized = true; } QVector extraInitJobs; extraInitJobs << new Data(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::INITIALIZING), false, KisStrokeJobData::BARRIER); extraInitJobs << lastCommandUndoJobs; KritaUtils::addJobSequential(extraInitJobs, [this]() { /** * We must request shape layers to rerender areas outside image bounds */ KisLayerUtils::forceAllHiddenOriginalsUpdate(m_rootNode); }); KritaUtils::addJobBarrier(extraInitJobs, [this]() { /** * We must ensure that the currently selected subtree * has finished all its updates. */ KisLayerUtils::forceAllDelayedNodesUpdate(m_rootNode); }); /// Disable all decorated nodes to generate outline /// and preview correctly. We will enable them back /// as soon as preview generation is finished. KritaUtils::addJobBarrier(extraInitJobs, [this]() { Q_FOREACH (KisNodeSP node, m_processedNodes) { KisDecoratedNodeInterface *decoratedNode = dynamic_cast(node.data()); if (decoratedNode && decoratedNode->decorationsVisible()) { decoratedNode->setDecorationsVisible(false); m_disabledDecoratedNodes << decoratedNode; } } }); KritaUtils::addJobBarrier(extraInitJobs, [this, initialTransformArgs, argsAreInitialized]() mutable { QRect srcRect; if (m_selection) { srcRect = m_selection->selectedExactRect(); } else { srcRect = QRect(); Q_FOREACH (KisNodeSP node, m_processedNodes) { // group layers may have a projection of layers // that are locked and will not be transformed if (node->inherits("KisGroupLayer")) continue; if (const KisTransformMask *mask = dynamic_cast(node.data())) { srcRect |= mask->sourceDataBounds(); } else if (const KisSelectionMask *mask = dynamic_cast(node.data())) { srcRect |= mask->selection()->selectedExactRect(); } else { srcRect |= node->exactBounds(); } } } TransformTransactionProperties transaction(srcRect, &initialTransformArgs, m_rootNode, m_processedNodes); if (!argsAreInitialized) { initialTransformArgs = KisTransformUtils::resetArgsForMode(m_mode, m_filterId, transaction); } this->m_initialTransformArgs = initialTransformArgs; emit this->sigTransactionGenerated(transaction, initialTransformArgs, this); }); extraInitJobs << new PreparePreviewData(); Q_FOREACH (KisNodeSP node, m_processedNodes) { extraInitJobs << new ClearSelectionData(node); } /// recover back visibility of decorated nodes KritaUtils::addJobBarrier(extraInitJobs, [this]() { Q_FOREACH (KisDecoratedNodeInterface *decoratedNode, m_disabledDecoratedNodes) { decoratedNode->setDecorationsVisible(true); } m_disabledDecoratedNodes.clear(); }); extraInitJobs << new Data(toQShared(new KisHoldUIUpdatesCommand(m_updatesFacade, KisCommandUtils::FlipFlopCommand::FINALIZING)), false, KisStrokeJobData::BARRIER); if (!lastCommandUndoJobs.isEmpty()) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_overriddenCommand); for (auto it = extraInitJobs.begin(); it != extraInitJobs.end(); ++it) { (*it)->setCancellable(false); } } addMutatedJobs(extraInitJobs); } void TransformStrokeStrategy::finishStrokeImpl(bool applyTransform, const ToolTransformArgs &args) { /** * Since our finishStrokeCallback() initiates new jobs, * cancellation request may come even after * finishStrokeCallback() (cancellations may be called * until there are no jobs left in the stroke's queue). * * Therefore we should check for double-entry here and * make sure the finilizing jobs are no cancellable. */ if (m_finalizingActionsStarted) return; m_finalizingActionsStarted = true; QVector mutatedJobs; if (applyTransform) { Q_FOREACH (KisNodeSP node, m_processedNodes) { mutatedJobs << new TransformData(TransformData::PAINT_DEVICE, args, node); } mutatedJobs << new TransformData(TransformData::SELECTION, args, m_rootNode); } KritaUtils::addJobBarrier(mutatedJobs, [this, applyTransform]() { Q_FOREACH (KisSelectionSP selection, m_deactivatedSelections) { selection->setVisible(true); } Q_FOREACH (KisNodeSP node, m_hiddenProjectionLeaves) { node->projectionLeaf()->setTemporaryHiddenFromRendering(false); } if (m_deactivatedOverlaySelectionMask) { m_deactivatedOverlaySelectionMask->selection()->setVisible(true); m_deactivatedOverlaySelectionMask->setDirty(); } if (applyTransform) { KisStrokeStrategyUndoCommandBased::finishStrokeCallback(); } else { KisStrokeStrategyUndoCommandBased::cancelStrokeCallback(); } }); for (auto it = mutatedJobs.begin(); it != mutatedJobs.end(); ++it) { (*it)->setCancellable(false); } addMutatedJobs(mutatedJobs); } void TransformStrokeStrategy::finishStrokeCallback() { if (!m_savedTransformArgs || m_savedTransformArgs->isIdentity()) { cancelStrokeCallback(); return; } finishStrokeImpl(true, *m_savedTransformArgs); } void TransformStrokeStrategy::cancelStrokeCallback() { - const bool shouldRecoverSavedInitialState = - !m_initialTransformArgs.isIdentity(); - - if (shouldRecoverSavedInitialState) { - m_savedTransformArgs = m_initialTransformArgs; - } - - finishStrokeImpl(shouldRecoverSavedInitialState, *m_savedTransformArgs); + finishStrokeImpl(!m_initialTransformArgs.isIdentity(), m_initialTransformArgs); }